Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __mocks__/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const mockRNOneSignal = {
addOutcomeWithValue: vi.fn(),
displayNotification: vi.fn(),
preventDefault: vi.fn(),
trackEvent: vi.fn(),
};

const mockPlatform = {
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ dependencies {

// api is used instead of implementation so the parent :app project can access any of the OneSignal Java
// classes if needed. Such as com.onesignal.NotificationExtenderService
api 'com.onesignal:OneSignal:5.4.2'
api 'com.onesignal:OneSignal:5.6.1'

testImplementation 'junit:junit:4.12'
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ of this software and associated documentation files (the "Software"), to deal
package com.onesignal.rnonesignalandroid;

import android.content.Context;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
Expand Down Expand Up @@ -754,4 +755,9 @@ public void addListener(String eventName) {
public void removeListeners(int count) {
// Keep: Required for RN built in Event Emitter Calls.
}

@ReactMethod
public void trackEvent(String name, @Nullable ReadableMap properties) {
OneSignal.getUser().trackEvent(name, properties != null ? properties.toHashMap() : new HashMap<>());
}
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spotless {
java {
target 'android/**/*.java', 'examples/RNOneSignalTS/android/app/src/**/*.java'
targetExclude '**/build/**'
palantirJavaFormat('2.28.0')
palantirJavaFormat('2.85.0')
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
Expand Down
25 changes: 24 additions & 1 deletion examples/RNOneSignalTS/OSButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Text, View } from 'react-native';
import { Platform, Text, View } from 'react-native';
import { OneSignal } from 'react-native-onesignal';
import { renderButtonView } from './Helpers';
// Remove: import {Text, Divider} from '@react-native-material/core';
Expand Down Expand Up @@ -396,11 +396,34 @@ const OSButtons: React.FC<Props> = ({ loggingFunction, inputFieldValue }) => {
},
);

const trackEventButton = renderButtonView('Track Event', () => {
loggingFunction('Tracking event: ', 'ReactNative');
const platform = Platform.OS; // This will be 'ios' or 'android'
OneSignal.User.trackEvent(`ReactNative-${platform}-noprops`);
OneSignal.User.trackEvent(`ReactNative-${platform}`, {
someNum: 123,
someFloat: 3.14159,
someString: 'abc',
someBool: true,
someObject: {
abc: '123',
nested: {
def: '456',
},
ghi: null,
},
someArray: [1, 2],
someMixedArray: [1, '2', { abc: '123' }, null],
someNull: null,
});
});

return [
loginButton,
logoutButton,
addEmailButton,
removeEmailButton,
trackEventButton,
sendTagWithKeyButton,
deleteTagWithKeyButton,
addTagsButton,
Expand Down
34 changes: 17 additions & 17 deletions examples/RNOneSignalTS/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,48 @@ PODS:
- hermes-engine (0.82.1):
- hermes-engine/Pre-built (= 0.82.1)
- hermes-engine/Pre-built (0.82.1)
- OneSignalXCFramework (5.2.16):
- OneSignalXCFramework/OneSignalComplete (= 5.2.16)
- OneSignalXCFramework/OneSignal (5.2.16):
- OneSignalXCFramework (5.4.0):
- OneSignalXCFramework/OneSignalComplete (= 5.4.0)
- OneSignalXCFramework/OneSignal (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalExtension
- OneSignalXCFramework/OneSignalLiveActivities
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalComplete (5.2.16):
- OneSignalXCFramework/OneSignalComplete (5.4.0):
- OneSignalXCFramework/OneSignal
- OneSignalXCFramework/OneSignalInAppMessages
- OneSignalXCFramework/OneSignalLocation
- OneSignalXCFramework/OneSignalCore (5.2.16)
- OneSignalXCFramework/OneSignalExtension (5.2.16):
- OneSignalXCFramework/OneSignalCore (5.4.0)
- OneSignalXCFramework/OneSignalExtension (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalInAppMessages (5.2.16):
- OneSignalXCFramework/OneSignalInAppMessages (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalLiveActivities (5.2.16):
- OneSignalXCFramework/OneSignalLiveActivities (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalLocation (5.2.16):
- OneSignalXCFramework/OneSignalLocation (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalNotifications (5.2.16):
- OneSignalXCFramework/OneSignalNotifications (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalExtension
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalOSCore (5.2.16):
- OneSignalXCFramework/OneSignalOSCore (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOutcomes (5.2.16):
- OneSignalXCFramework/OneSignalOutcomes (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalUser (5.2.16):
- OneSignalXCFramework/OneSignalUser (5.4.0):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
Expand Down Expand Up @@ -1828,8 +1828,8 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- SocketRocket
- react-native-onesignal (5.2.17):
- OneSignalXCFramework (= 5.2.16)
- react-native-onesignal (5.3.0):
- OneSignalXCFramework (= 5.4.0)
- React (< 1.0.0, >= 0.13.0)
- react-native-safe-area-context (5.6.2):
- boost
Expand Down Expand Up @@ -2768,7 +2768,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 273e30e7fb618279934b0b95ffab60ecedb7acf5
OneSignalXCFramework: 8ed6648481bee0bd973a138fecd80331b798524f
OneSignalXCFramework: 95b6391df5a91b448003149c1a633ade42ceca1e
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a
RCTRequired: e2c574c1b45231f7efb0834936bd609d75072b63
Expand Down Expand Up @@ -2802,7 +2802,7 @@ SPEC CHECKSUMS:
React-logger: 500f2fa5697d224e63c33d913c8a4765319e19bf
React-Mapbuffer: 06d59c448da7e34eb05b3fb2189e12f6a30fec57
React-microtasksnativemodule: d1ee999dc9052e23f6488b730fa2d383a4ea40e5
react-native-onesignal: 3b6cd199ec0db87166ef7fb595715627a35b3244
react-native-onesignal: 68c8423063cc8ead827e09bc71d139c14850feaf
react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460
React-NativeModulesApple: 46690a0fe94ec28fc6fc686ec797b911d251ded0
React-oscompat: 95875e81f5d4b3c7b2c888d5bd2c9d83450d8bdb
Expand Down
5 changes: 5 additions & 0 deletions ios/RCTOneSignal/RCTOneSignalEventEmitter.m
Original file line number Diff line number Diff line change
Expand Up @@ -628,4 +628,9 @@ - (void)removeUserStateObserver {
}
}

RCT_EXPORT_METHOD(trackEvent : (NSString *)name withProperties : (
NSDictionary *_Nullable)properties) {
[OneSignal.User trackEventWithName:name properties:properties];
}

@end
2 changes: 1 addition & 1 deletion react-native-onesignal.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ Pod::Spec.new do |s|
# pod 'React', :path => '../node_modules/react-native/'

# The Native OneSignal-iOS-SDK XCFramework from cocoapods.
s.dependency 'OneSignalXCFramework', '5.2.16'
s.dependency 'OneSignalXCFramework', '5.4.0'
end
44 changes: 43 additions & 1 deletion src/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { NativeModule } from 'react-native';
import type { MockInstance } from 'vitest';
import { isNativeModuleLoaded, isValidCallback } from './helpers';
import {
isNativeModuleLoaded,
isObjectSerializable,
isValidCallback,
} from './helpers';

describe('helpers', () => {
let errorSpy: MockInstance;
Expand Down Expand Up @@ -58,4 +62,42 @@ describe('helpers', () => {
expect(result).toBe(true);
});
});

describe('isObjectSerializable', () => {
test.each([
{ description: 'an empty object', value: {} },
{ description: 'an object with string values', value: { key: 'value' } },
{ description: 'an object with number values', value: { count: 42 } },
{ description: 'an object with boolean values', value: { active: true } },
{ description: 'an object with null values', value: { data: null } },
{
description: 'a nested object',
value: { outer: { inner: 'value' } },
},
{
description: 'an object with array values',
value: { items: [1, 2, 3] },
},
])('should return true for $description', ({ value }) => {
expect(isObjectSerializable(value)).toBe(true);
});

test.each([
{ description: 'null', value: null },
{ description: 'undefined', value: undefined },
{ description: 'a string', value: 'string' },
{ description: 'a number', value: 123 },
{ description: 'a boolean', value: true },
{ description: 'an array', value: [1, 2, 3] },
{ description: 'a function', value: () => {} },
])('should return false for $description', ({ value }) => {
expect(isObjectSerializable(value)).toBe(false);
});

test('should return false for objects with circular references', () => {
const circular: Record<string, unknown> = {};
circular.self = circular;
expect(isObjectSerializable(circular)).toBe(false);
});
});
});
15 changes: 15 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,18 @@ export function isNativeModuleLoaded(module: NativeModule): boolean {

return true;
}

/**
* Returns true if the value is a JSON-serializable object.
*/
export function isObjectSerializable(value: unknown): boolean {
if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
return false;
}
try {
JSON.stringify(value);
return true;
} catch {
return false;
}
}
46 changes: 46 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,52 @@ describe('OneSignal', () => {
});
});

describe('trackEvent', () => {
test('should track event with name and properties', () => {
const properties = { key: 'value', count: 42 };
OneSignal.User.trackEvent('purchase', properties);
expect(mockRNOneSignal.trackEvent).toHaveBeenCalledWith(
'purchase',
properties,
);
});

test('should track event with just name using default empty properties', () => {
OneSignal.User.trackEvent('page_view');
expect(mockRNOneSignal.trackEvent).toHaveBeenCalledWith(
'page_view',
{},
);
});

test('should not track event if native module is not loaded', () => {
isNativeLoadedSpy.mockReturnValue(false);
OneSignal.User.trackEvent('event');
expect(mockRNOneSignal.trackEvent).not.toHaveBeenCalled();
});

test('should not track event if properties are not serializable', () => {
const circular: Record<string, unknown> = {};
circular.self = circular;
OneSignal.User.trackEvent('event', circular);
expect(errorSpy).toHaveBeenCalledWith(
'Properties must be a JSON-serializable object',
);
expect(mockRNOneSignal.trackEvent).not.toHaveBeenCalled();
});

test('should not track event if properties is not an object', () => {
OneSignal.User.trackEvent(
'event',
'invalid' as unknown as Record<string, unknown>,
);
expect(errorSpy).toHaveBeenCalledWith(
'Properties must be a JSON-serializable object',
);
expect(mockRNOneSignal.trackEvent).not.toHaveBeenCalled();
});
});

describe('Notifications', () => {
describe('hasPermission (deprecated)', () => {
test('should log deprecation warning', () => {
Expand Down
24 changes: 23 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
import type { OSNotificationPermission } from './constants/subscription';
import EventManager from './events/EventManager';
import NotificationWillDisplayEvent from './events/NotificationWillDisplayEvent';
import { isNativeModuleLoaded, isValidCallback } from './helpers';
import {
isNativeModuleLoaded,
isObjectSerializable,
isValidCallback,
} from './helpers';
import type {
InAppMessage,
InAppMessageClickEvent,
Expand Down Expand Up @@ -590,6 +594,24 @@ export namespace OneSignal {

return RNOneSignal.getTags();
}

/**
* Track custom events for the current user.
* Note: Currently, null values will be omitted for Android.
* */
export function trackEvent(
name: string,
properties: Record<string, unknown> = {},
) {
if (!isNativeModuleLoaded(RNOneSignal)) return;

if (!isObjectSerializable(properties)) {
console.error('Properties must be a JSON-serializable object');
return;
}

RNOneSignal.trackEvent(name, properties);
}
}

export namespace Notifications {
Expand Down