From aa6f14a2a3642c462a40ab2ae37cbaf66bd3e2e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 8 May 2026 17:45:18 +0000 Subject: [PATCH 01/10] Update Android SDK to 5.9.0 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index e8e218c0..c6dcee31 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -39,7 +39,7 @@ dependencies { // Exclude OkHttp from OneSignal's transitive deps: the otel module pulls in OkHttp 5.x // (via opentelemetry-exporter-sender-okhttp) which is binary-incompatible with React Native's // networking stack (okhttp3.internal.Util removed in 5.x). React Native already provides OkHttp 4.x. - api('com.onesignal:OneSignal:5.8.1') { + api('com.onesignal:OneSignal:5.9.0') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } From 383c6df6bc2cf36470fff8bf4d2d5305b572f91e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 8 May 2026 17:45:20 +0000 Subject: [PATCH 02/10] Release 5.4.6 --- .../main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java | 2 +- ios/RCTOneSignal/RCTOneSignal.mm | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java index 27e0904d..448af37d 100644 --- a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java +++ b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java @@ -230,7 +230,7 @@ public void invalidate() { @Override public void initialize(String appId) { OneSignalWrapper.setSdkType("reactnative"); - OneSignalWrapper.setSdkVersion("050405"); + OneSignalWrapper.setSdkVersion("050406"); if (oneSignalInitDone) { Logging.debug("Already initialized the OneSignal React-Native SDK", null); diff --git a/ios/RCTOneSignal/RCTOneSignal.mm b/ios/RCTOneSignal/RCTOneSignal.mm index ccc176ec..6a2db226 100644 --- a/ios/RCTOneSignal/RCTOneSignal.mm +++ b/ios/RCTOneSignal/RCTOneSignal.mm @@ -23,7 +23,7 @@ - (void)initOneSignal:(NSDictionary *)launchOptions { return; OneSignalWrapper.sdkType = @"reactnative"; - OneSignalWrapper.sdkVersion = @"050405"; + OneSignalWrapper.sdkVersion = @"050406"; // initialize the SDK with a nil app ID so cold start click listeners can be // triggered [OneSignal initialize:nil withLaunchOptions:launchOptions]; diff --git a/package.json b/package.json index 50769926..061118fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-onesignal", - "version": "5.4.5", + "version": "5.4.6", "description": "React Native OneSignal SDK", "keywords": [ "android", From 4fc68715d27abc26f4f99c0f37a26bdb34694c2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 13 May 2026 00:06:27 +0000 Subject: [PATCH 03/10] Update Android SDK to 5.9.1 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index c6dcee31..292dc97b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -39,7 +39,7 @@ dependencies { // Exclude OkHttp from OneSignal's transitive deps: the otel module pulls in OkHttp 5.x // (via opentelemetry-exporter-sender-okhttp) which is binary-incompatible with React Native's // networking stack (okhttp3.internal.Util removed in 5.x). React Native already provides OkHttp 4.x. - api('com.onesignal:OneSignal:5.9.0') { + api('com.onesignal:OneSignal:5.9.1') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } From db7792b7cb20856dbc7962853eb3dbfdf2649474 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 15 May 2026 22:41:14 +0000 Subject: [PATCH 04/10] Update Android SDK to 5.9.2 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 292dc97b..7e32d134 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -39,7 +39,7 @@ dependencies { // Exclude OkHttp from OneSignal's transitive deps: the otel module pulls in OkHttp 5.x // (via opentelemetry-exporter-sender-okhttp) which is binary-incompatible with React Native's // networking stack (okhttp3.internal.Util removed in 5.x). React Native already provides OkHttp 4.x. - api('com.onesignal:OneSignal:5.9.1') { + api('com.onesignal:OneSignal:5.9.2') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } From 43eff907b44e5e9e23b6fda3e6f386080b884e83 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Wed, 20 May 2026 16:45:30 -0700 Subject: [PATCH 05/10] chore(demo): retry on invalid_player_ids error --- .../demo/src/services/OneSignalApiService.ts | 76 ++++++++++++------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/examples/demo/src/services/OneSignalApiService.ts b/examples/demo/src/services/OneSignalApiService.ts index ce03cc12..80229b72 100644 --- a/examples/demo/src/services/OneSignalApiService.ts +++ b/examples/demo/src/services/OneSignalApiService.ts @@ -75,35 +75,59 @@ class OneSignalApiService { subscriptionId: string, extra: Record, ): Promise { - try { - const body = { - app_id: this._appId, - include_subscription_ids: [subscriptionId], - headings, - contents, - ...extra, - }; - - const response = await fetch('https://onesignal.com/api/v1/notifications', { - method: 'POST', - headers: { - Accept: 'application/vnd.onesignal.v1+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - const text = await response.text(); - console.error(`Send notification failed: ${text}`); + const body = { + app_id: this._appId, + include_subscription_ids: [subscriptionId], + headings, + contents, + ...extra, + }; + + const maxAttempts = 3; + + // Retry on `invalid_player_ids` to absorb the brief race where the + // subscription has been created locally but is not yet visible to the + // /notifications endpoint. + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const response = await fetch('https://onesignal.com/api/v1/notifications', { + method: 'POST', + headers: { + Accept: 'application/vnd.onesignal.v1+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const text = await response.text(); + console.error(`Send notification failed: ${text}`); + return false; + } + + const data = (await response.json().catch(() => undefined)) as + | { errors?: { invalid_player_ids?: unknown } } + | undefined; + const invalidIds = data?.errors?.invalid_player_ids; + if (Array.isArray(invalidIds) && invalidIds.length > 0) { + if (attempt < maxAttempts) { + await new Promise((resolve) => setTimeout(() => resolve(), 3_000 * attempt)); + continue; + } + console.error( + `Send notification failed: invalid_player_ids ${JSON.stringify(invalidIds)}`, + ); + return false; + } + + return true; + } catch (err) { + console.error(`Send notification error: ${String(err)}`); return false; } - - return true; - } catch (err) { - console.error(`Send notification error: ${String(err)}`); - return false; } + + return false; } async updateLiveActivity( From d536f620dc21d9937e9bd89ad6a5a2ef8a9e10a0 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Wed, 20 May 2026 19:42:31 -0700 Subject: [PATCH 06/10] chore(demo): remove E2E_MODE masking --- .github/actions/setup-demo/action.yml | 1 - examples/demo/.env.example | 1 - examples/demo/src/components/sections/AppSection.tsx | 3 +-- examples/demo/src/components/sections/PushSection.tsx | 3 +-- examples/demo/src/utils/maskValue.ts | 10 ---------- examples/demo/types/env.d.ts | 1 - 6 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 examples/demo/src/utils/maskValue.ts diff --git a/.github/actions/setup-demo/action.yml b/.github/actions/setup-demo/action.yml index 708be879..9b52b677 100644 --- a/.github/actions/setup-demo/action.yml +++ b/.github/actions/setup-demo/action.yml @@ -54,5 +54,4 @@ runs: run: | echo "ONESIGNAL_APP_ID=${{ inputs.onesignal-app-id }}" > .env echo "ONESIGNAL_API_KEY=${{ inputs.onesignal-api-key }}" >> .env - echo "E2E_MODE=true" >> .env echo "ONESIGNAL_ANDROID_CHANNEL_ID=7ec2ece9-c538-4656-9516-1316f48a005c" >> .env diff --git a/examples/demo/.env.example b/examples/demo/.env.example index 389f07e0..10c390f0 100644 --- a/examples/demo/.env.example +++ b/examples/demo/.env.example @@ -1,7 +1,6 @@ # Default App ID (used if ONESIGNAL_APP_ID is empty): 77e32082-ea27-42e3-a898-c72e141824ef ONESIGNAL_APP_ID=your-onesignal-app-id ONESIGNAL_API_KEY=your-onesignal-api-key -E2E_MODE=false # Optional: Android Notification Channel ID for the WITH SOUND test notification. # Create one in your OneSignal dashboard under Settings > Android Notification Categories. diff --git a/examples/demo/src/components/sections/AppSection.tsx b/examples/demo/src/components/sections/AppSection.tsx index 525b2ee9..03915d37 100644 --- a/examples/demo/src/components/sections/AppSection.tsx +++ b/examples/demo/src/components/sections/AppSection.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { View, Text, TouchableOpacity, Linking, StyleSheet } from 'react-native'; import { AppColors, AppTextStyles, AppTheme, AppSpacing } from '../../theme'; -import { maskValue } from '../../utils/maskValue'; import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; @@ -33,7 +32,7 @@ export default function AppSection({ ellipsizeMode="middle" testID="app_id_value" > - {maskValue(appId)} + {appId} diff --git a/examples/demo/src/components/sections/PushSection.tsx b/examples/demo/src/components/sections/PushSection.tsx index ea3fec02..56e52c78 100644 --- a/examples/demo/src/components/sections/PushSection.tsx +++ b/examples/demo/src/components/sections/PushSection.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { AppColors, AppTextStyles, AppTheme, AppSpacing } from '../../theme'; -import { maskValue } from '../../utils/maskValue'; import ActionButton from '../ActionButton'; import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; @@ -35,7 +34,7 @@ export default function PushSection({ ellipsizeMode="middle" testID="push_id_value" > - {maskValue(pushSubscriptionId ?? '—')} + {pushSubscriptionId ?? '—'} diff --git a/examples/demo/src/utils/maskValue.ts b/examples/demo/src/utils/maskValue.ts deleted file mode 100644 index bab93102..00000000 --- a/examples/demo/src/utils/maskValue.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { E2E_MODE } from '@env'; - -const MASK_CHAR = '•'; - -export function maskValue(value: string): string { - if (E2E_MODE === 'true' && value !== '—') { - return MASK_CHAR.repeat(value.length); - } - return value; -} diff --git a/examples/demo/types/env.d.ts b/examples/demo/types/env.d.ts index 6ee94c30..2cfe0569 100644 --- a/examples/demo/types/env.d.ts +++ b/examples/demo/types/env.d.ts @@ -2,5 +2,4 @@ declare module '@env' { export const ONESIGNAL_APP_ID: string; export const ONESIGNAL_API_KEY: string; export const ONESIGNAL_ANDROID_CHANNEL_ID: string; - export const E2E_MODE: string; } From edc026db73e2d6aed72f65ad0e19022f9ababba8 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Wed, 20 May 2026 19:59:49 -0700 Subject: [PATCH 07/10] chore(demo): display OneSignal ID in push section Co-authored-by: Cursor --- .../demo/src/components/sections/PushSection.tsx | 14 ++++++++++++++ examples/demo/src/hooks/useOneSignal.ts | 6 ++++++ examples/demo/src/screens/HomeScreen.tsx | 1 + 3 files changed, 21 insertions(+) diff --git a/examples/demo/src/components/sections/PushSection.tsx b/examples/demo/src/components/sections/PushSection.tsx index 56e52c78..a4e51b04 100644 --- a/examples/demo/src/components/sections/PushSection.tsx +++ b/examples/demo/src/components/sections/PushSection.tsx @@ -7,6 +7,7 @@ import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; interface Props { + oneSignalId: string | undefined; pushSubscriptionId: string | undefined; isPushEnabled: boolean; hasNotificationPermission: boolean; @@ -16,6 +17,7 @@ interface Props { } export default function PushSection({ + oneSignalId, pushSubscriptionId, isPushEnabled, hasNotificationPermission, @@ -26,6 +28,18 @@ export default function PushSection({ return ( + + OneSignal ID + + {oneSignalId ?? '—'} + + + Push ID (undefined); + const [oneSignalId, setOneSignalId] = useState(undefined); const [pushSubscriptionId, setPushSubscriptionId] = useState(undefined); const [isPushEnabled, setIsPushEnabled] = useState(false); const [hasNotificationPermission, setHasNotificationPermission] = useState(false); @@ -207,6 +209,8 @@ function useOneSignalState(): UseOneSignalReturn { `User changed: onesignalId=${nextOnesignalId ?? 'null'}, externalId=${event.current.externalId ?? 'null'}`, ); + setOneSignalId(nextOnesignalId ?? undefined); + if (nextOnesignalId === null) { return; } @@ -280,6 +284,7 @@ function useOneSignalState(): UseOneSignalReturn { setIsReady(true); const initialOnesignalId = await OneSignal.User.getOnesignalId(); + setOneSignalId(initialOnesignalId ?? undefined); if (initialOnesignalId) { await fetchUserDataFromApi(); } @@ -535,6 +540,7 @@ function useOneSignalState(): UseOneSignalReturn { consentRequired, privacyConsentGiven, externalUserId, + oneSignalId, pushSubscriptionId, isPushEnabled, hasNotificationPermission, diff --git a/examples/demo/src/screens/HomeScreen.tsx b/examples/demo/src/screens/HomeScreen.tsx index 38538ceb..9f5b4741 100644 --- a/examples/demo/src/screens/HomeScreen.tsx +++ b/examples/demo/src/screens/HomeScreen.tsx @@ -69,6 +69,7 @@ export default function HomeScreen() { /> Date: Fri, 22 May 2026 15:10:55 -0700 Subject: [PATCH 08/10] chore(demo): remove OneSignal ID from push section --- .../demo/src/components/sections/PushSection.tsx | 14 -------------- examples/demo/src/screens/HomeScreen.tsx | 6 +++--- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/examples/demo/src/components/sections/PushSection.tsx b/examples/demo/src/components/sections/PushSection.tsx index a4e51b04..56e52c78 100644 --- a/examples/demo/src/components/sections/PushSection.tsx +++ b/examples/demo/src/components/sections/PushSection.tsx @@ -7,7 +7,6 @@ import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; interface Props { - oneSignalId: string | undefined; pushSubscriptionId: string | undefined; isPushEnabled: boolean; hasNotificationPermission: boolean; @@ -17,7 +16,6 @@ interface Props { } export default function PushSection({ - oneSignalId, pushSubscriptionId, isPushEnabled, hasNotificationPermission, @@ -28,18 +26,6 @@ export default function PushSection({ return ( - - OneSignal ID - - {oneSignalId ?? '—'} - - - Push ID (null); useEffect(() => { - if (os.isReady) os.promptPush(); - }, [os.isReady, os.promptPush]); + if (isReady) promptPush(); + }, [isReady, promptPush]); const showTooltipModal = (key: string) => { const tooltip = TooltipHelper.getInstance().getTooltip(key); @@ -69,7 +70,6 @@ export default function HomeScreen() { /> Date: Fri, 22 May 2026 22:26:46 -0700 Subject: [PATCH 09/10] chore(demo): broaden transient send failure detection --- .../demo/src/services/OneSignalApiService.ts | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/demo/src/services/OneSignalApiService.ts b/examples/demo/src/services/OneSignalApiService.ts index 80229b72..fc5a592d 100644 --- a/examples/demo/src/services/OneSignalApiService.ts +++ b/examples/demo/src/services/OneSignalApiService.ts @@ -5,6 +5,17 @@ import { UserData, userDataFromJson } from '../models/UserData'; const DEFAULT_ANDROID_CHANNEL_ID = 'b3b015d9-c050-4042-8548-dcc34aa44aa4'; +function isTransientSendFailure(data: unknown): boolean { + if (!data || typeof data !== 'object') return false; + const record = data as { id?: unknown; errors?: unknown }; + const errors = record.errors; + const hasErrors = + (Array.isArray(errors) && errors.length > 0) || + (errors != null && typeof errors === 'object' && Object.keys(errors).length > 0); + const missingId = typeof record.id !== 'string' || record.id.length === 0; + return hasErrors || missingId; +} + class OneSignalApiService { private static _instance: OneSignalApiService; private _appId: string = ''; @@ -85,9 +96,13 @@ class OneSignalApiService { const maxAttempts = 3; - // Retry on `invalid_player_ids` to absorb the brief race where the - // subscription has been created locally but is not yet visible to the - // /notifications endpoint. + // Retry while the OneSignal backend hasn't yet indexed the freshly + // created subscription. The /notifications endpoint reports this race in + // a few different shapes, all of which return HTTP 200: + // {"errors":{"invalid_player_ids":[...]}} + // {"id":"","errors":["All included players are not subscribed"]} + // {"id":"","errors":[...]} + // Treat any 200 response without a real notification id as transient. for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { const response = await fetch('https://onesignal.com/api/v1/notifications', { @@ -105,18 +120,13 @@ class OneSignalApiService { return false; } - const data = (await response.json().catch(() => undefined)) as - | { errors?: { invalid_player_ids?: unknown } } - | undefined; - const invalidIds = data?.errors?.invalid_player_ids; - if (Array.isArray(invalidIds) && invalidIds.length > 0) { + const data = await response.json().catch(() => undefined); + if (isTransientSendFailure(data)) { if (attempt < maxAttempts) { await new Promise((resolve) => setTimeout(() => resolve(), 3_000 * attempt)); continue; } - console.error( - `Send notification failed: invalid_player_ids ${JSON.stringify(invalidIds)}`, - ); + console.error(`Send notification failed: ${JSON.stringify(data)}`); return false; } From 0d87b157c12b4d0f73e263a15c23073491a43e4c Mon Sep 17 00:00:00 2001 From: Fadi George Date: Sat, 23 May 2026 11:35:40 -0700 Subject: [PATCH 10/10] chore(demo): replace hitSlop with styled info button --- examples/demo/src/components/SectionCard.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/demo/src/components/SectionCard.tsx b/examples/demo/src/components/SectionCard.tsx index 18f60ca9..6ccd5ba0 100644 --- a/examples/demo/src/components/SectionCard.tsx +++ b/examples/demo/src/components/SectionCard.tsx @@ -19,7 +19,7 @@ export default function SectionCard({ title, children, onInfoTap, sectionKey, st {onInfoTap && ( @@ -50,6 +50,13 @@ const styles = StyleSheet.create({ letterSpacing: 0.5, textTransform: 'uppercase', }, + infoButton: { + width: 32, + height: 32, + alignItems: 'center', + justifyContent: 'center', + marginRight: -11, + }, infoIcon: { fontSize: 18, color: AppColors.osGrey500,