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
4 changes: 4 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

`react-native-onesignal` `5.4.x` registers its iOS TurboModule through the `codegenConfig.ios.modulesProvider` field, which was introduced in React Native `0.79`. Apps on React Native `0.76`–`0.78` (including Expo SDK `52`) will hit `Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'OneSignal' could not be found` at runtime when the New Architecture is enabled. Stay on `5.3.x` until you can move to React Native `0.79+` (Expo SDK `53+`); `5.3.x` uses the legacy bridge module and works on the New Architecture via the interop layer.

#### Notification `rawPayload` on Android

Notification `rawPayload` is now an object on Android, matching iOS. Remove any `JSON.parse(notification.rawPayload)` calls and access its properties directly.

#### ⚠️ Migration Advisory for current OneSignal customers

Our new [user-centric APIs and v5.x.x SDKs](https://onesignal.com/blog/unify-your-users-across-channels-and-devices/) offer an improved user and data management experience. However, they may not be at 1:1 feature parity with our previous versions yet.
Expand Down
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ dependencies {
}

testImplementation 'junit:junit:4.12'
testImplementation 'org.json:json:20240303'
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.onesignal.debug.internal.logging.Logging;
import com.onesignal.inAppMessages.IInAppMessage;
import com.onesignal.inAppMessages.IInAppMessageClickEvent;
import com.onesignal.inAppMessages.IInAppMessageClickResult;
Expand Down Expand Up @@ -126,11 +127,22 @@ public static HashMap<String, Object> convertNotificationToMap(INotification not
}
notificationHash.put("actionButtons", actionButtons);
}
notificationHash.put("rawPayload", notification.getRawPayload());
notificationHash.put("rawPayload", convertRawPayloadToHashMap(notification.getRawPayload()));

return notificationHash;
}

static HashMap<String, Object> convertRawPayloadToHashMap(String rawPayload) {
if (rawPayload == null || rawPayload.trim().isEmpty()) return new HashMap<>();

try {
return convertJSONObjectToHashMap(new JSONObject(rawPayload));
} catch (JSONException exception) {
Comment thread
fadi-george marked this conversation as resolved.
Resolved
Logging.error("Failed to parse notification raw payload", exception);
return new HashMap<>();
}
}

private static HashMap<String, Object> convertInAppMessageToMap(IInAppMessage message) {
HashMap<String, Object> hash = new HashMap<>();
hash.put("messageId", message.getMessageId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.onesignal.rnonesignalandroid;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.HashMap;
import java.util.List;
import org.junit.Test;

public class RNUtilsTest {
@Test
public void convertRawPayloadToHashMapConvertsNestedJson() {
HashMap<String, Object> payload = RNUtils.convertRawPayloadToHashMap(
"{\"message\":\"hello\",\"count\":2,\"enabled\":true,\"nested\":{\"value\":\"inside\"},"
+ "\"items\":[1,{\"name\":\"item\"}]}");

assertEquals("hello", payload.get("message"));
assertEquals(2, payload.get("count"));
assertEquals(true, payload.get("enabled"));

HashMap<String, Object> nested = (HashMap<String, Object>) payload.get("nested");
assertEquals("inside", nested.get("value"));

List<Object> items = (List<Object>) payload.get("items");
assertEquals(1, items.get(0));
assertEquals("item", ((HashMap<String, Object>) items.get(1)).get("name"));
}

@Test
public void convertRawPayloadToHashMapReturnsEmptyMapForEmptyPayload() {
assertTrue(RNUtils.convertRawPayloadToHashMap("").isEmpty());
assertTrue(RNUtils.convertRawPayloadToHashMap(" ").isEmpty());
assertTrue(RNUtils.convertRawPayloadToHashMap(null).isEmpty());
}

@Test
public void convertRawPayloadToHashMapReturnsEmptyMapForInvalidPayload() {
assertTrue(RNUtils.convertRawPayloadToHashMap("not-json").isEmpty());
assertTrue(RNUtils.convertRawPayloadToHashMap("[1, 2]").isEmpty());
}
}
12 changes: 12 additions & 0 deletions examples/demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ When you want to forcefully reload, for example to reset the state of your app,
- **Android**: Press the <kbd>R</kbd> key twice or select **"Reload"** from the **Dev Menu**, accessed via <kbd>Ctrl</kbd> + <kbd>M</kbd> (Windows/Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (macOS).
- **iOS**: Press <kbd>R</kbd> in iOS Simulator.

## Testing iOS Action Button Icons

The iOS demo bundles shared action button icons in both the `demo` and
`OneSignalNotificationServiceExtension` targets. Create an image notification
in the OneSignal dashboard with these action buttons:

- **Save Story** with icon `template-bookmark-icon`
- **Share** with icon `template-share-icon`

Enter the icon names without the `.png` extension. On an iOS 15 or newer
physical device, expand the notification to verify that both icons render.

## Congratulations! :tada:

You've successfully run and modified your React Native App. :partying_face:
Expand Down
2 changes: 1 addition & 1 deletion examples/demo/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions examples/demo/ios/demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
A10000000000000000000007 /* libPods-OneSignalWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A20000000000000000000010 /* libPods-OneSignalWidgetExtension.a */; };
A10000000000000000000008 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A20000000000000000000009 /* WidgetKit.framework */; };
A10000000000000000000009 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2000000000000000000000A /* SwiftUI.framework */; };
B10000000000000000000001 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B20000000000000000000001 /* template-bookmark-icon.png */; };
B10000000000000000000002 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B20000000000000000000002 /* template-share-icon.png */; };
B10000000000000000000003 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B20000000000000000000001 /* template-bookmark-icon.png */; };
B10000000000000000000004 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B20000000000000000000002 /* template-share-icon.png */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -82,6 +86,8 @@
A2000000000000000000000F /* libPods-OneSignalNotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OneSignalNotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A20000000000000000000010 /* libPods-OneSignalWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OneSignalWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A20000000000000000000011 /* demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = demo.entitlements; path = demo/demo.entitlements; sourceTree = "<group>"; };
B20000000000000000000001 /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = "template-bookmark-icon.png"; path = "demo/template-bookmark-icon.png"; sourceTree = "<group>"; };
B20000000000000000000002 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = "template-share-icon.png"; path = "demo/template-share-icon.png"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -124,6 +130,8 @@
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
A20000000000000000000011 /* demo.entitlements */,
B20000000000000000000001 /* template-bookmark-icon.png */,
B20000000000000000000002 /* template-share-icon.png */,
);
name = demo;
sourceTree = "<group>";
Expand Down Expand Up @@ -317,13 +325,17 @@
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
5F8F7D5C84F8947D278D5B06 /* PrivacyInfo.xcprivacy in Resources */,
B10000000000000000000001 /* template-bookmark-icon.png in Resources */,
B10000000000000000000002 /* template-share-icon.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
A70000000000000000000001 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B10000000000000000000003 /* template-bookmark-icon.png in Resources */,
B10000000000000000000004 /* template-share-icon.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Binary file added examples/demo/ios/demo/template-bookmark-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/demo/ios/demo/template-share-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions examples/demo/src/hooks/useOneSignal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,35 @@ function useOneSignalState(): UseOneSignalReturn {

const handleNotificationClick = (e: NotificationClickEvent) => {
console.log(`Notification click: ${e.notification.title ?? ''}`);

// uncomment to see the full event object
// console.log('[OneSignal] click event: ', e);
};

const handleForegroundWillDisplay = (e: NotificationWillDisplayEvent) => {
console.log(`Notification foregroundWillDisplay: ${e.getNotification().title ?? ''}`);

// uncomment to see the full event object
// console.log('[OneSignal] will display event: ', e.getNotification());

// uncomment to test preventing the default display behavior
// e.preventDefault();

// can call this after preventDefault (within ~25 seconds) to force display of notification
// e.getNotification().display();

// example with a delay (assumes preventDefault was called)
// console.log('[OneSignal] forcing display of notification in x seconds');
// let seconds = 24;
// const interval = setInterval(() => {
// seconds--;
// console.log(`[OneSignal] displaying notification in ${seconds} seconds`);
// if (seconds <= 0) {
// console.log('[OneSignal] displaying notification');
// e.getNotification().display();
// clearInterval(interval);
// }
// }, 1000);
};

const pushSubHandler = (event: PushSubscriptionChangedState) => {
Expand Down
28 changes: 4 additions & 24 deletions src/OSNotification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('OSNotification', () => {
test('should initialize with optional common properties as undefined', () => {
const notificationData = {
body: 'Test body',
rawPayload: '',
rawPayload: {},
notificationId: 'id-123',
};
const notification = new OSNotification(notificationData);
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('OSNotification', () => {
collapseId: 'collapse-1',
fromProjectNumber: '123456789',
smallIconAccentColor: 'FFFF0000',
lockScreenVisibility: '1',
lockScreenVisibility: 1,
androidNotificationId: 456,
};
const notification = new OSNotification(androidData);
Expand All @@ -93,7 +93,7 @@ describe('OSNotification', () => {
expect(notification.collapseId).toBe('collapse-1');
expect(notification.fromProjectNumber).toBe('123456789');
expect(notification.smallIconAccentColor).toBe('FFFF0000');
expect(notification.lockScreenVisibility).toBe('1');
expect(notification.lockScreenVisibility).toBe(1);
expect(notification.androidNotificationId).toBe(456);
});

Expand Down Expand Up @@ -198,7 +198,7 @@ describe('OSNotification', () => {
});
});

describe('rawPayload types', () => {
describe('rawPayload', () => {
test('should accept object as rawPayload', () => {
const notificationData = {
...baseNotificationData,
Expand All @@ -212,16 +212,6 @@ describe('OSNotification', () => {
});
});

test('should accept string as rawPayload', () => {
const notificationData = {
...baseNotificationData,
rawPayload: '{"key":"value"}',
};
const notification = new OSNotification(notificationData);

expect(notification.rawPayload).toBe('{"key":"value"}');
});

test('should accept empty object as rawPayload', () => {
const notificationData = {
...baseNotificationData,
Expand All @@ -231,15 +221,5 @@ describe('OSNotification', () => {

expect(notification.rawPayload).toEqual({});
});

test('should accept empty string as rawPayload', () => {
const notificationData = {
...baseNotificationData,
rawPayload: '',
};
const notification = new OSNotification(notificationData);

expect(notification.rawPayload).toBe('');
});
});
});
8 changes: 4 additions & 4 deletions src/OSNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface BaseNotificationData {
sound?: string;
title?: string;
launchURL?: string;
rawPayload: object | string; // platform bridges return different types
rawPayload: object;
Comment thread
fadi-george marked this conversation as resolved.
Resolved
actionButtons?: object[];
additionalData?: object;
notificationId: string;
Expand All @@ -26,7 +26,7 @@ interface AndroidNotificationData extends BaseNotificationData {
collapseId?: string;
fromProjectNumber?: string;
smallIconAccentColor?: string;
lockScreenVisibility?: string;
lockScreenVisibility?: number;
androidNotificationId?: number;
}

Expand All @@ -52,7 +52,7 @@ export default class OSNotification {
sound?: string;
title?: string;
launchURL?: string;
rawPayload: object | string; // platform bridges return different types
rawPayload: object;
actionButtons?: object[];
additionalData?: object;
notificationId: string;
Expand All @@ -68,7 +68,7 @@ export default class OSNotification {
collapseId?: string;
fromProjectNumber?: string;
smallIconAccentColor?: string;
lockScreenVisibility?: string;
lockScreenVisibility?: number;
androidNotificationId?: number;
// ios only
badge?: string;
Expand Down
Loading