UNPKG

onesignal-web-sdk

Version:

Web push notifications from OneSignal.

840 lines (719 loc) 33.6 kB
import '../../support/polyfills/polyfills'; import test, { GenericTestContext, Context as AvaContext } from 'ava'; import sinon, { SinonSandbox } from 'sinon'; import timemachine from 'timemachine'; import { ServiceWorkerManager } from '../../../src/managers/ServiceWorkerManager'; import { ServiceWorkerActiveState } from '../../../src/helpers/ServiceWorkerHelper'; import { TestEnvironment, HttpHttpsEnvironment, BrowserUserAgent } from '../../support/sdk/TestEnvironment'; import Database from '../../../src/services/Database'; import Context from '../../../src/models/Context'; import { SubscriptionManager, SubscriptionManagerConfig } from '../../../src/managers/SubscriptionManager'; import { base64ToUint8Array, arrayBufferToBase64 } from '../../../src/utils/Encoding'; import PushManager from '../../support/mocks/service-workers/models/PushManager'; import PushSubscription from '../../support/mocks/service-workers/models/PushSubscription'; import PushSubscriptionOptions from '../../support/mocks/service-workers/models/PushSubscriptionOptions'; import Random from '../../support/tester/Random'; import { setBrowser } from '../../support/tester/browser'; import { SubscriptionStrategyKind } from "../../../src/models/SubscriptionStrategyKind"; import { RawPushSubscription } from '../../../src/models/RawPushSubscription'; import { IntegrationKind } from '../../../src/models/IntegrationKind'; import OneSignal from '../../../src/OneSignal'; import { ServiceWorkerRegistrationError } from '../../../src/errors/ServiceWorkerRegistrationError'; import { SubscriptionStateKind } from '../../../src/models/SubscriptionStateKind'; import { WindowEnvironmentKind } from '../../../src/models/WindowEnvironmentKind'; import SdkEnvironment from '../../../src/managers/SdkEnvironment'; import { OneSignalUtils } from '../../../src/utils/OneSignalUtils'; import { Subscription } from "../../../src/models/Subscription"; import { PushDeviceRecord } from "../../../src/models/PushDeviceRecord"; const sandbox: SinonSandbox= sinon.sandbox.create(); test.beforeEach(async t => { await TestEnvironment.initialize({ httpOrHttps: HttpHttpsEnvironment.Https }); TestEnvironment.mockInternalOneSignal(); timemachine.reset(); }); test.afterEach(function () { sandbox.restore(); }); async function testCase( /** * The browser to simulate. Chrome means using vapidPublicKey, while Firefox means using the * global onesignalVapidPublicKey. */ t: GenericTestContext<AvaContext<any>>, browser: BrowserUserAgent, vapidPublicKey: string, sharedVapidPublicKey: string, subscriptionStrategy: SubscriptionStrategyKind, onBeforeSubscriptionManagerSubscribe: (pushManager: PushManager, subscriptionManager: SubscriptionManager) => Promise<void>, onPushManagerSubscribed: ( pushManager: PushManager, spy: sinon.SinonSpy, subscriptionManager: SubscriptionManager ) => Promise<void>, ) { // Set the user agent, which determines which vapid key we use setBrowser(browser); // Create our subscription manager, which is what we're testing const manager = new SubscriptionManager(OneSignal.context, { safariWebId: undefined, appId: Random.getRandomUuid(), vapidPublicKey: vapidPublicKey, onesignalVapidPublicKey: sharedVapidPublicKey } as SubscriptionManagerConfig); // Register a mock service worker to access push subscription await navigator.serviceWorker.register('/worker.js'); const registration: any = await ServiceWorkerManager.getRegistration(); // There should be no existing subscription const existingSubscription = await registration.pushManager.getSubscription(); t.is(existingSubscription, null); if (onBeforeSubscriptionManagerSubscribe) { await onBeforeSubscriptionManagerSubscribe(registration.pushManager, manager); } // Prepare to subscribe for push, hook the call to spy on params const spy = sandbox.spy(PushManager.prototype, 'subscribe'); // Subscribe for push await manager.subscribeWithVapidKey(registration.pushManager as any, subscriptionStrategy); // Allow each test to verify mock parameters independently if (onPushManagerSubscribed) { await onPushManagerSubscribed(registration.pushManager, spy, manager); } // Restore original mocked method spy.restore(); } function generateVapidKeys() { return { uniquePublic: arrayBufferToBase64(Random.getRandomUint8Array(64).buffer), sharedPublic: arrayBufferToBase64(Random.getRandomUint8Array(64).buffer) }; } test('uses per-app VAPID public key for Chrome', async t => { const vapidKeys = generateVapidKeys(); await testCase( t, BrowserUserAgent.ChromeMacSupported, vapidKeys.uniquePublic, vapidKeys.sharedPublic, SubscriptionStrategyKind.ResubscribeExisting, null, async (pushManager, pushManagerSubscribeSpy) => { const expectedSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, // Verify using unique per-app VAPID key applicationServerKey: base64ToUint8Array(vapidKeys.uniquePublic).buffer }; t.true(pushManagerSubscribeSpy.getCall(0).calledWithExactly(expectedSubscriptionOptions)); } ); }); test('uses globally shared VAPID public key for Firefox', async t => { const vapidKeys = generateVapidKeys(); await testCase( t, BrowserUserAgent.ChromeMacSupported, vapidKeys.uniquePublic, vapidKeys.sharedPublic, SubscriptionStrategyKind.ResubscribeExisting, null, async (pushManager, pushManagerSubscribeSpy) => { const expectedSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, // Verify using shared VAPID key applicationServerKey: base64ToUint8Array(vapidKeys.sharedPublic).buffer }; t.true(pushManagerSubscribeSpy.getCall(0).calledWithExactly(expectedSubscriptionOptions)); } ); }); test('resubscribe-existing strategy uses new subscription applicationServerKey', async t => { const initialVapidKeys = generateVapidKeys(); const subsequentVapidKeys = generateVapidKeys(); const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; let unsubscribeSpy: sinon.SinonSpy; await testCase( t, BrowserUserAgent.ChromeMacSupported, subsequentVapidKeys.uniquePublic, subsequentVapidKeys.sharedPublic, SubscriptionStrategyKind.ResubscribeExisting, async (pushManager, _subscriptionManager) => { // Create an initial subscription, so subsequent subscription attempts logic is tested await pushManager.subscribe(initialSubscriptionOptions); // And spy on PushManager.unsubscribe(), because we expect the existing subscription to be unsubscribed unsubscribeSpy = sandbox.spy(PushSubscription.prototype, 'unsubscribe'); }, async (_pushManager, pushManagerSubscribeSpy) => { const newSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(subsequentVapidKeys.uniquePublic).buffer, }; // The subscription options used should always use the new subscription's options const calledSubscriptionOptions = pushManagerSubscribeSpy.getCall(0).args[0]; t.deepEqual(calledSubscriptionOptions, newSubscriptionOptions); // Unsubscribe should have been called t.true(unsubscribeSpy.calledOnce); } ); }); test( "resubscribe existing strategy unsubscribes and creates new subscription if existing subscription options are null", async t => { const initialVapidKeys = generateVapidKeys(); const subsequentVapidKeys = generateVapidKeys(); const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; const subsequentSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(subsequentVapidKeys.uniquePublic).buffer, }; let unsubscribeSpy: sinon.SinonSpy; await testCase( t, BrowserUserAgent.ChromeMacSupported, subsequentVapidKeys.uniquePublic, subsequentVapidKeys.sharedPublic, SubscriptionStrategyKind.ResubscribeExisting, async (pushManager, _subscriptionManager) => { // Create an initial subscription, so subsequent subscriptions attempt to re-use this initial // subscription's options await pushManager.subscribe(initialSubscriptionOptions); // But set the subscription's options to be null const subscription = await pushManager.getSubscription(); subscription.options = null; // And spy on PushManager.unsubscribe(), because we expect the existing subscription to be unsubscribed unsubscribeSpy = sandbox.spy(PushSubscription.prototype, 'unsubscribe'); }, async (pushManager, pushManagerSubscribeSpy) => { // The subscription options used should be our subsequent subscription's options const calledSubscriptionOptions = pushManagerSubscribeSpy.getCall(0).args[0]; t.deepEqual(calledSubscriptionOptions, subsequentSubscriptionOptions); // Unsubscribe should have been called t.true(unsubscribeSpy.calledOnce); } ); } ); test( "resubscribe existing strategy does not unsubscribes if options are not null", async t => { const subsequentVapidKeys = generateVapidKeys(); let unsubscribeSpy: sinon.SinonSpy; await testCase( t, BrowserUserAgent.ChromeMacSupported, subsequentVapidKeys.uniquePublic, subsequentVapidKeys.sharedPublic, SubscriptionStrategyKind.ResubscribeExisting, async (_pushManager, _subscriptionManager) => { // And spy on PushManager.unsubscribe(), because we expect the existing subscription to be unsubscribed unsubscribeSpy = sandbox.spy(PushSubscription.prototype, 'unsubscribe'); }, async (_pushManager, pushManagerSubscribeSpy) => { // The subscription options used should be our subsequent subscription's options const calledSubscriptionOptions = pushManagerSubscribeSpy.getCall(0).args[0]; const subsequentSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(subsequentVapidKeys.uniquePublic).buffer, }; t.deepEqual(calledSubscriptionOptions, subsequentSubscriptionOptions); // Unsubscribe should NOT have been called t.false(unsubscribeSpy.calledOnce); } ); } ); test( "null applicationServerKey throws when subscribing", async t => { const manager = new SubscriptionManager(OneSignal.context, { safariWebId: undefined, appId: Random.getRandomUuid(), vapidPublicKey: <any>undefined, // Forcing vapidPublicKey to undefined to test throwing onesignalVapidPublicKey: generateVapidKeys().sharedPublic } as SubscriptionManagerConfig); await t.throws(manager.subscribe(SubscriptionStrategyKind.SubscribeNew), Error); } ); test("registerSubscription with an existing subsription sends player update", async t => { TestEnvironment.mockInternalOneSignal(); const subscriptionManager = OneSignal.context.subscriptionManager; const deviceId = OneSignalUtils.getRandomUuid(); const pushSubscription: RawPushSubscription = new RawPushSubscription(); pushSubscription.w3cAuth = "7QdgQYTjZIeiCuLgopqeww"; pushSubscription.w3cP256dh = "BBGhFwQ146CSOWhuz-r4ItRK2cQuZ4FZNkiW7uTEpf2JsPfxqbWtQvfGf4FvnaZ35hqjkwbtUUIn8wxwhhc3O_0"; pushSubscription.w3cEndpoint = new URL("https://fcm.googleapis.com/fcm/send/c8rEdO3xSaQ:APA91bH51jGBPBVSxoZVLq-xwen6oHYmGVpyjR8qG_869A-skv1a5G9PQ5g2S5O8ujJ2y8suHaPF0psX5590qrZj_WnWbVfx2q4u2Vm6_Ofq-QGBDcomRziLzTn6uWU9wbrrmL6L5YBh"); const deviceRecord: PushDeviceRecord = PushDeviceRecord.createFromPushSubscription( OneSignal.config.appId, RawPushSubscription.deserialize(pushSubscription) ); sandbox.stub(Database, "getSubscription").resolves({ deviceId } as Subscription); sandbox.stub(subscriptionManager, "associateSubscriptionWithEmail").resolves(); sandbox.stub(SubscriptionManager, "isSafari").returns(false); sandbox.stub(Database, "setSubscription").resolves(); const playerUpdateSpy = sandbox.stub(OneSignal.context.updateManager, "sendPlayerUpdate"); const playerCreateSpy = sandbox.stub(OneSignal.context.updateManager, "sendPlayerCreate"); await subscriptionManager.registerSubscription(pushSubscription) t.is(playerUpdateSpy.calledOnce, true); t.is(playerCreateSpy.notCalled, true); const args = playerUpdateSpy.getCall(0).args; t.is(args.length, 1); t.deepEqual(args[0], deviceRecord); }); test("registerSubscription without an existing subsription sends player create", async t => { TestEnvironment.mockInternalOneSignal(); const subscriptionManager = OneSignal.context.subscriptionManager; const deviceId = OneSignalUtils.getRandomUuid(); const pushSubscription = { w3cAuth: "7QdgQYTjZIeiCuLgopqeww", w3cP256dh: "BBGhFwQ146CSOWhuz-r4ItRK2cQuZ4FZNkiW7uTEpf2JsPfxqbWtQvfGf4FvnaZ35hqjkwbtUUIn8wxwhhc3O_0", w3cEndpoint: new URL("https://fcm.googleapis.com/fcm/send/c8rEdO3xSaQ:APA91bH51jGBPBVSxoZVLq-xwen6oHYmGVpyjR8qG_869A-skv1a5G9PQ5g2S5O8ujJ2y8suHaPF0psX5590qrZj_WnWbVfx2q4u2Vm6_Ofq-QGBDcomRziLzTn6uWU9wbrrmL6L5YBh"), } as any; const deviceRecord: PushDeviceRecord = PushDeviceRecord.createFromPushSubscription( OneSignal.config.appId, pushSubscription ); let updateData: any = { notification_types: SubscriptionStateKind.Subscribed } updateData = Object.assign(updateData, deviceRecord.serialize()); sandbox.stub(Database, "getSubscription").resolves({ } as Subscription); sandbox.stub(subscriptionManager, "associateSubscriptionWithEmail").resolves(); sandbox.stub(PushDeviceRecord, "createFromPushSubscription").returns(deviceRecord); sandbox.stub(SubscriptionManager, "isSafari").returns(false); sandbox.stub(Database, "setSubscription").resolves(); const playerUpdateSpy = sandbox.stub(OneSignal.context.updateManager, "sendPlayerUpdate"); const playerCreateSpy = sandbox.stub(OneSignal.context.updateManager, "sendPlayerCreate"); await subscriptionManager.registerSubscription(pushSubscription) t.is(playerUpdateSpy.notCalled, true); t.is(playerCreateSpy.calledOnce, true); const args = playerCreateSpy.getCall(0).args; t.is(args.length, 1); t.deepEqual(args[0], deviceRecord); }); test('device ID is available after register event', async t => { const vapidKeys = generateVapidKeys(); await testCase( t, BrowserUserAgent.ChromeMacSupported, vapidKeys.uniquePublic, vapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, null, null ); const serviceWorkerRegistration = await ServiceWorkerManager.getRegistration(); const pushSubscription = await serviceWorkerRegistration.pushManager.getSubscription(); const rawPushSubscription = RawPushSubscription.setFromW3cSubscription(pushSubscription); const randomPlayerId = Random.getRandomUuid(); const registerEventPromise = new Promise(resolve => { OneSignal.emitter.on('register', async () => { const subscription = await Database.getSubscription(); t.is(subscription.deviceId, randomPlayerId); resolve(); }); }); const playerUpdateStub = sandbox.stub(OneSignal.context.updateManager, "sendPlayerUpdate").resolves(); const playerCreateStub = sandbox.stub(OneSignal.context.updateManager, "sendPlayerCreate").resolves(randomPlayerId); await OneSignal.context.subscriptionManager.registerSubscription(rawPushSubscription); t.is(playerUpdateStub.calledOnce, false); t.is(playerCreateStub.calledOnce, true); await registerEventPromise; }); test('safari 11.1+ with service worker but not pushManager', async t => { const serviceWorkerRegistration = { active: true, scope: "/", installing: null, waiting: null, pushManager: null, }; await TestEnvironment.mockInternalOneSignal(); sandbox.stub(SdkEnvironment, "getIntegration").returns(IntegrationKind.Secure); sandbox.stub(SdkEnvironment, "getWindowEnv").returns(WindowEnvironmentKind.ServiceWorker); sandbox.stub(navigator.serviceWorker, "getRegistration").returns(serviceWorkerRegistration); sandbox.stub(OneSignal.context.serviceWorkerManager, "getActiveState").returns(ServiceWorkerActiveState.WorkerA); t.is(await OneSignal.context.subscriptionManager.isSubscriptionExpiring(), false); }); test( "subscribe new strategy creates new subscription", async t => { const initialVapidKeys = generateVapidKeys(); const subsequentVapidKeys = generateVapidKeys(); const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; const subsequentSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(subsequentVapidKeys.uniquePublic).buffer, }; let initialSubscription: PushSubscription; await testCase( t, BrowserUserAgent.ChromeMacSupported, subsequentVapidKeys.uniquePublic, subsequentVapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, async (pushManager, subscriptionManager) => { // Create an initial subscription, check that our subsequent subscription is NOT the same initialSubscription = await pushManager.subscribe(initialSubscriptionOptions); }, async (pushManager, pushManagerSubscribeSpy) => { // The subscription options used should be our subsequent subscription's options const calledSubscriptionOptions = pushManagerSubscribeSpy.getCall(0).args[0]; t.deepEqual(calledSubscriptionOptions, subsequentSubscriptionOptions); const subsequentSubscription = await pushManager.getSubscription(); t.notDeepEqual(initialSubscription, subsequentSubscription) } ); } ); test( "subscribe new strategy unsubscribes existing subscription to create new subscription", async t => { const subsequentVapidKeys = generateVapidKeys(); let unsubscribeSpy: sinon.SinonSpy; await testCase( t, BrowserUserAgent.ChromeMacSupported, subsequentVapidKeys.uniquePublic, subsequentVapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, async (pushManager, _subscriptionManager) => { // Create an initial subscription, so subsequent subscriptions attempt to re-use this initial // subscription's options const initialVapidKeys = generateVapidKeys(); const subscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; await pushManager.subscribe(subscriptionOptions); // And spy on PushManager.unsubscribe(), because we expect the existing subscription to be unsubscribed unsubscribeSpy = sandbox.spy(PushSubscription.prototype, 'unsubscribe'); }, async (_pushManager, _pushManagerSubscribeSpy) => { // Unsubscribe should have been called t.true(unsubscribeSpy.calledOnce); } ); } ); test( "subscribing records created at date in UTC", async t => { const initialVapidKeys = generateVapidKeys(); const dateString = "February 26, 2018 10:04:24 UTC"; // Set the initial datetime timemachine.config({ timestamp: dateString }); const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; await testCase( t, BrowserUserAgent.ChromeMacSupported, initialVapidKeys.uniquePublic, initialVapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, null, async (pushManager, pushManagerSubscribeSpy) => { // After our subscription, check the subscription creation date was recorded const subscription = await Database.getSubscription(); // timemachine must be reset to use native Date parse API timemachine.reset(); t.deepEqual(subscription.createdAt, Date.parse(dateString)); } ); } ); async function expirationTestCase( /** * The browser to simulate. Chrome means using vapidPublicKey, while Firefox means using the * global onesignalVapidPublicKey. */ t: GenericTestContext<AvaContext<any>>, subscriptionCreationTime: number, subscriptionExpirationTime: number, expirationCheckTime: number, skipCreationDateSet: boolean, env: IntegrationKind, ) { const initialVapidKeys = generateVapidKeys(); // Force service worker active state dependency so test can run const stub = sandbox.stub(ServiceWorkerManager.prototype, "getActiveState") .resolves(ServiceWorkerActiveState.WorkerA); const integrationStub = sandbox.stub(SdkEnvironment, "getIntegration").resolves(env); const newTimeBeforeMidpoint = expirationCheckTime; // Set the initial datetime, which is used internally for the subscription created at timemachine.config({ timestamp: subscriptionCreationTime }); const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; await testCase( t, BrowserUserAgent.ChromeMacSupported, initialVapidKeys.uniquePublic, initialVapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, async (_pushManager, _subscriptionManager) => { // Set every subscription's expiration time to 30 days plus PushSubscription.prototype.expirationTime = subscriptionExpirationTime; }, async (_pushManager, _pushManagerSubscribeSpy, subscriptionManager) => { if (skipCreationDateSet) { // Unset directly await Database.put("Options", { key: "subscriptionCreatedAt", value: null }); } timemachine.config({ timestamp: expirationCheckTime }); const isExpiring = await subscriptionManager.isSubscriptionExpiring(); if (subscriptionExpirationTime) { if (env === IntegrationKind.Secure || env === IntegrationKind.SecureProxy) { // Checks in an HTTPS environment expire at the midpoint because we can silently // resubscribe (HTTPS top frame silently resubscribe, HTTPS in child frame send message to // SW to resubscribe) const midpointExpirationTime = subscriptionCreationTime + (subscriptionExpirationTime - subscriptionCreationTime) / 2; if (expirationCheckTime >= midpointExpirationTime) { t.true(isExpiring); } else { t.false(isExpiring); } } else { // Checks in an HTTP environment only expire at or after the expiration time, since we // can't silently resubscribe if (expirationCheckTime >= subscriptionExpirationTime) { t.true(isExpiring); } else { t.false(isExpiring); } } } else { return t.false(isExpiring); } } ); timemachine.reset(); stub.restore(); integrationStub.restore(); } test( "a subscription expiring in 30 days is not refreshed before the midpoint", async t => { const dateString = "February 26, 2018 10:04:24 UTC"; const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = Date.parse(dateString); const subscriptionExpirationTime = subscriptionCreationTime + THIRTY_DAYS_MS; const newTimeBeforeMidpoint = subscriptionCreationTime + (THIRTY_DAYS_MS/2) - 10; await expirationTestCase( t, subscriptionCreationTime, subscriptionExpirationTime, newTimeBeforeMidpoint, false, IntegrationKind.Secure, ); } ); test( "a subscription expiring in 30 days is refreshed at the midpoint", async t => { const dateString = "February 26, 2018 10:04:24 UTC"; const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = Date.parse(dateString); const subscriptionExpirationTime = Date.parse(dateString) + THIRTY_DAYS_MS; const newTimeAfterMidpoint = Date.parse(dateString) + (THIRTY_DAYS_MS/2); await expirationTestCase( t, subscriptionCreationTime, subscriptionExpirationTime, newTimeAfterMidpoint, false, IntegrationKind.Secure, ); } ); test( "a subscription expiring in 30 days is refreshed after the midpoint", async t => { const dateString = "February 26, 2018 10:04:24 UTC"; const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = Date.parse(dateString); const subscriptionExpirationTime = Date.parse(dateString) + THIRTY_DAYS_MS; const newTimeAfterMidpoint = Date.parse(dateString) + (THIRTY_DAYS_MS/2) + (THIRTY_DAYS_MS/2); await expirationTestCase( t, subscriptionCreationTime, subscriptionExpirationTime, newTimeAfterMidpoint, false, IntegrationKind.Secure, ); } ); test( "a subscription without a recorded creation date is always considered expired", async t => { const dateString = "February 26, 2018 10:04:24 UTC"; const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = Date.parse(dateString); const subscriptionExpirationTime = Date.parse(dateString) + THIRTY_DAYS_MS; await expirationTestCase( t, subscriptionCreationTime, subscriptionExpirationTime, subscriptionCreationTime, true, IntegrationKind.Secure, ); } ); test( "a subscription without an expiration time is never considered expired", async t => { const dateString = "February 26, 2018 10:04:24 UTC"; const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = Date.parse(dateString); await expirationTestCase( t, subscriptionCreationTime, null, subscriptionCreationTime, false, IntegrationKind.Secure, ); } ); test( "the subscription expiration time should be recorded", async t => { const initialVapidKeys = generateVapidKeys(); const expirationTime = 1519675981599; const initialSubscriptionOptions: PushSubscriptionOptions = { userVisibleOnly: true, applicationServerKey: base64ToUint8Array(initialVapidKeys.uniquePublic).buffer, }; await testCase( t, BrowserUserAgent.ChromeMacSupported, initialVapidKeys.uniquePublic, initialVapidKeys.sharedPublic, SubscriptionStrategyKind.SubscribeNew, async (_pushManager, _subscriptionManager) => { PushSubscription.prototype.expirationTime = expirationTime; }, async (_pushManager, _pushManagerSubscribeSpy) => { const subscription = await Database.getSubscription(); t.deepEqual(subscription.expirationTime, expirationTime); } ); } ); test( "for HTTP, a subscription expiring in 30 days only expires after 30 days and not before", async t => { const THIRTY_DAYS_MS = 1000 * 60 * 60 * 24 * 30; const subscriptionCreationTime = 1; await expirationTestCase( t, subscriptionCreationTime, subscriptionCreationTime + THIRTY_DAYS_MS, subscriptionCreationTime + THIRTY_DAYS_MS - 10, false, IntegrationKind.InsecureProxy, ); } ); test( "Service worker failed to install due to 403. Send a notification for the first user's session.", async t => { const context: Context = OneSignal.context; const serviceWorkerManager = context.serviceWorkerManager; const subscriptionManager = context.subscriptionManager; const sessionManager = context.sessionManager; TestEnvironment.mockInternalOneSignal(); sandbox.stub(sessionManager, "isFirstPageView").returns(true); const error403 = new ServiceWorkerRegistrationError(403, "403 Forbidden"); sandbox.stub(serviceWorkerManager, "installWorker").rejects(error403); sandbox.stub(SdkEnvironment, "getWindowEnv").returns(WindowEnvironmentKind.Host); sandbox.stub(SubscriptionManager, "isSafari").returns(false); const smSpyRegisterFailed = sandbox.spy(subscriptionManager, "registerFailedSubscription"); const smSpyRegister = sandbox.spy(subscriptionManager, "registerSubscription"); await t.throws(subscriptionManager.subscribe(SubscriptionStrategyKind.SubscribeNew), ServiceWorkerRegistrationError); t.is(smSpyRegisterFailed.calledOnce, true); t.is(smSpyRegisterFailed.getCall(0).args[0], SubscriptionStateKind.ServiceWorkerStatus403); t.is(smSpyRegister.calledOnce, true); } ); test( "Service worker failed to install due to 403. Not the first user's session, do not send a notification.", async t => { const context: Context = OneSignal.context; const serviceWorkerManager = context.serviceWorkerManager; const subscriptionManager = context.subscriptionManager; const sessionManager = context.sessionManager; TestEnvironment.mockInternalOneSignal(); sandbox.stub(sessionManager, "isFirstPageView").returns(false); const error403 = new ServiceWorkerRegistrationError(403, "403 Forbidden"); sandbox.stub(serviceWorkerManager, "installWorker").throws(error403); sandbox.stub(SdkEnvironment, "getWindowEnv").returns(WindowEnvironmentKind.Host); sandbox.stub(SubscriptionManager, "isSafari").returns(false); const smSpyRegisterFailed = sandbox.spy(subscriptionManager, "registerFailedSubscription"); const smSpyRegister = sandbox.spy(subscriptionManager, "registerSubscription"); await t.throws(subscriptionManager.subscribe(SubscriptionStrategyKind.SubscribeNew), ServiceWorkerRegistrationError); t.is(smSpyRegisterFailed.calledOnce, true); t.is(smSpyRegisterFailed.getCall(0).args[0], SubscriptionStateKind.ServiceWorkerStatus403); t.is(smSpyRegister.calledOnce, false); } ); test( "Service worker failed to install due to 404. Send a notification for the first user's session.", async t => { const context: Context = OneSignal.context; const serviceWorkerManager = context.serviceWorkerManager; const subscriptionManager = context.subscriptionManager; const sessionManager = context.sessionManager; TestEnvironment.mockInternalOneSignal(); sandbox.stub(sessionManager, "isFirstPageView").returns(true); const error404 = new ServiceWorkerRegistrationError(404, "404 Not Found"); sandbox.stub(serviceWorkerManager, "installWorker").rejects(error404); sandbox.stub(SdkEnvironment, "getWindowEnv").returns(WindowEnvironmentKind.Host); sandbox.stub(SubscriptionManager, "isSafari").returns(false); const smSpyRegisterFailed = sandbox.spy(subscriptionManager, "registerFailedSubscription"); const smSpyRegister = sandbox.spy(subscriptionManager, "registerSubscription"); await t.throws(subscriptionManager.subscribe(SubscriptionStrategyKind.SubscribeNew), ServiceWorkerRegistrationError); t.is(smSpyRegisterFailed.calledOnce, true); t.is(smSpyRegisterFailed.getCall(0).args[0], SubscriptionStateKind.ServiceWorkerStatus404); t.is(smSpyRegister.calledOnce, true); } ); test( "Service worker failed to install due to 404. Not the first user's session, do not send a notification.", async t => { const context: Context = OneSignal.context; const serviceWorkerManager = context.serviceWorkerManager; const subscriptionManager = context.subscriptionManager; const sessionManager = context.sessionManager; TestEnvironment.mockInternalOneSignal(); sandbox.stub(sessionManager, "isFirstPageView").returns(false); const error404 = new ServiceWorkerRegistrationError(404, "404 Not Found"); sandbox.stub(serviceWorkerManager, "installWorker").throws(error404); sandbox.stub(SdkEnvironment, "getWindowEnv").returns(WindowEnvironmentKind.Host); sandbox.stub(SubscriptionManager, "isSafari").returns(false); const smSpyRegisterFailed = sandbox.spy(subscriptionManager, "registerFailedSubscription"); const smSpyRegister = sandbox.spy(subscriptionManager, "registerSubscription"); await t.throws(subscriptionManager.subscribe(SubscriptionStrategyKind.SubscribeNew), ServiceWorkerRegistrationError); t.is(smSpyRegisterFailed.calledOnce, true); t.is(smSpyRegisterFailed.getCall(0).args[0], SubscriptionStateKind.ServiceWorkerStatus404); t.is(smSpyRegister.calledOnce, false); } );