Skip to content
Open
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
57 changes: 55 additions & 2 deletions index.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.');
});
});
18 changes: 17 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || [];
Expand All @@ -23,6 +24,8 @@ declare global {

function handleOnError() {
isOneSignalScriptFailed = true;
pendingInitReject?.(new Error('OneSignal script failed to load.'));
pendingInitReject = undefined;
}

function addSDKScript(scriptSrc?: string) {
Expand Down Expand Up @@ -106,6 +109,14 @@ const init = (options: IInitObject): Promise<void> => {
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;
Expand All @@ -114,13 +125,18 @@ const init = (options: IInitObject): Promise<void> => {
addSDKScript(options.scriptSrc);

return new Promise<void>((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);
});
});
});
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": [
{
Expand Down