From 89fec0d5f2825fb1a74506c24898b488f0cc50b6 Mon Sep 17 00:00:00 2001 From: OneSignal Date: Fri, 29 May 2026 23:50:22 +0000 Subject: [PATCH] build: sync with web-shim-codegen v3.0.8 --- index.test.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- index.ts | 18 +++++++++++++++- package.json | 2 +- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/index.test.ts b/index.test.ts index 2cf8fd35..17a8f6ab 100644 --- a/index.test.ts +++ b/index.test.ts @@ -1,10 +1,19 @@ -import { describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; + import OneSignal from './index'; +const APP_ID = '123456'; + const originalDocument = window.document; const documentSpy = vi.spyOn(window, 'document', 'get'); -const APP_ID = '123456'; +beforeAll(() => { + // jsdom lacks PushSubscriptionOptions; stub it so init()'s push-support check passes. + function PushSubscriptionOptionsStub() {} + PushSubscriptionOptionsStub.prototype = { applicationServerKey: undefined }; + (globalThis as unknown as { PushSubscriptionOptions: unknown }).PushSubscriptionOptions = + PushSubscriptionOptionsStub; +}); const init = vi.fn(); // @ts-expect-error - mocking OneSignal class that comes from the cdn @@ -50,3 +59,47 @@ describe('React OneSignal', () => { ); }); }); + +describe('init() rejects instead of hanging', () => { + let OneSignalModule: typeof import('./index').default; + + beforeEach(async () => { + vi.resetModules(); + window.OneSignalDeferred = []; + // addSDKScript() short-circuits if #onesignal-sdk already exists in the DOM. + document.querySelectorAll('script#onesignal-sdk').forEach((el) => el.remove()); + OneSignalModule = (await import('./index')).default; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test('rejects when the browser does not support Web Push', async () => { + const originalPushSubscriptionOptions = ( + globalThis as unknown as { PushSubscriptionOptions: unknown } + ).PushSubscriptionOptions; + (globalThis as unknown as { PushSubscriptionOptions: unknown }).PushSubscriptionOptions = + undefined; + window.safari = undefined; + + try { + await expect(OneSignalModule.init({ appId: APP_ID })).rejects.toThrow( + 'This browser does not support Web Push notifications.', + ); + } finally { + (globalThis as unknown as { PushSubscriptionOptions: unknown }).PushSubscriptionOptions = + originalPushSubscriptionOptions; + } + }); + + test('rejects when the SDK script fails to load', async () => { + const initPromise = OneSignalModule.init({ appId: APP_ID }); + + const scriptElement = document.getElementById('onesignal-sdk') as HTMLScriptElement | null; + expect(scriptElement).not.toBeNull(); + scriptElement?.onerror?.(new Event('error')); + + await expect(initPromise).rejects.toThrow('OneSignal script failed to load.'); + }); +}); diff --git a/index.ts b/index.ts index 9bc9487e..3f5a2464 100644 --- a/index.ts +++ b/index.ts @@ -4,6 +4,7 @@ const DEFAULT_SCRIPT_SRC = let isOneSignalInitialized = false; let isOneSignalScriptFailed = false; +let pendingInitReject: ((reason?: unknown) => void) | undefined; if (typeof window !== 'undefined') { window.OneSignalDeferred = window.OneSignalDeferred || []; @@ -23,6 +24,8 @@ declare global { function handleOnError() { isOneSignalScriptFailed = true; + pendingInitReject?.(new Error('OneSignal script failed to load.')); + pendingInitReject = undefined; } function addSDKScript(scriptSrc?: string) { @@ -106,6 +109,14 @@ const init = (options: IInitObject): Promise => { return Promise.reject(`Document is not defined.`); } + // Required: the CDN script silently exits on unsupported browsers without + // draining OneSignalDeferred, so init() would hang forever otherwise. + if (!isPushNotificationsSupported()) { + return Promise.reject( + new Error('This browser does not support Web Push notifications.'), + ); + } + // Handle both disabled and disable keys for welcome notification if (options.welcomeNotification?.disabled !== undefined) { options.welcomeNotification.disable = options.welcomeNotification.disabled; @@ -114,13 +125,18 @@ const init = (options: IInitObject): Promise => { addSDKScript(options.scriptSrc); return new Promise((resolve, reject) => { + pendingInitReject = reject; window.OneSignalDeferred?.push((OneSignal) => { OneSignal.init(options) .then(() => { isOneSignalInitialized = true; + pendingInitReject = undefined; resolve(); }) - .catch(reject); + .catch((err) => { + pendingInitReject = undefined; + reject(err); + }); }); }); }; diff --git a/package.json b/package.json index f39b0384..bc3dc75a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-onesignal", - "version": "3.5.3", + "version": "3.5.2", "description": "React OneSignal Module: Make it easy to integrate OneSignal with your React App!", "contributors": [ {