diff --git a/__test__/unit/pushSubscription/nativePermissionChange.test.ts b/__test__/unit/pushSubscription/nativePermissionChange.test.ts index e07725f61..daa598c30 100644 --- a/__test__/unit/pushSubscription/nativePermissionChange.test.ts +++ b/__test__/unit/pushSubscription/nativePermissionChange.test.ts @@ -8,7 +8,7 @@ import { import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; import { MockServiceWorker } from '__test__/support/mocks/MockServiceWorker'; -import { db, getOptionsValue } from 'src/shared/database/client'; +import { clearStore, db, getOptionsValue } from 'src/shared/database/client'; import { setAppState as setDBAppState } from 'src/shared/database/config'; import * as PermissionUtils from 'src/shared/helpers/permissions'; import Emitter from 'src/shared/libraries/Emitter'; @@ -31,8 +31,8 @@ describe('Notification Types are set correctly on subscription change', () => { }); afterEach(async () => { - await db.clear('subscriptions'); - await db.clear('Options'); + await clearStore('subscriptions'); + await clearStore('Options'); }); const setDbPermission = async (permission: NotificationPermission) => { diff --git a/package.json b/package.json index e7a318362..71392ee1d 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ }, { "path": "./build/releases/OneSignalSDK.page.es6.js", - "limit": "52.13 kB", + "limit": "52.153 kB", "gzip": true }, { "path": "./build/releases/OneSignalSDK.sw.js", - "limit": "14.54 kB", + "limit": "14.564 kB", "gzip": true }, { diff --git a/src/shared/database/client.test.ts b/src/shared/database/client.test.ts index d30b33050..2f05165f6 100644 --- a/src/shared/database/client.test.ts +++ b/src/shared/database/client.test.ts @@ -1,12 +1,11 @@ import { APP_ID, EXTERNAL_ID, ONESIGNAL_ID } from '__test__/constants'; +import type * as idb from 'idb'; import { deleteDB, type IDBPDatabase } from 'idb'; import { SubscriptionType } from '../subscriptions/constants'; import { closeDb, getDb } from './client'; import { DATABASE_NAME } from './constants'; import type { IndexedDBSchema } from './types'; -vi.useRealTimers; - beforeEach(async () => { await closeDb(); await deleteDB(DATABASE_NAME); @@ -317,3 +316,43 @@ describe('migrations', () => { }); }); }); + +test('should reopen db when terminated', async () => { + // mocking to keep track of the terminated callback + vi.resetModules(); + let terminatedCallback = vi.hoisted(() => vi.fn(() => false)); + + const openFn = vi.hoisted(() => vi.fn()); + const deleteDatabaseFn = vi.hoisted(() => vi.fn()); + + vi.mock('idb', async (importOriginal) => { + const actual = (await importOriginal()) as typeof idb; + return { + ...actual, + openDB: openFn.mockImplementation((name, version, callbacks) => { + terminatedCallback = callbacks!.terminated!; + return actual.openDB(name, version, callbacks); + }), + deleteDB: deleteDatabaseFn.mockImplementation((name) => { + terminatedCallback(); + return actual.deleteDB(name); + }), + }; + }); + + const { db } = await vi.importActual('./client'); + expect(openFn).toHaveBeenCalledTimes(1); // initial open + + await db.put('Options', { key: 'userConsent', value: true }); + + // real world db.close() will trigger the terminated callback + deleteDB(DATABASE_NAME); + + // terminate callback should reopen the db + expect(openFn).toHaveBeenCalledTimes(2); + + expect(await db.get('Options', 'userConsent')).toEqual({ + key: 'userConsent', + value: true, + }); +}); diff --git a/src/shared/database/client.ts b/src/shared/database/client.ts index 4763a1321..a75caa431 100644 --- a/src/shared/database/client.ts +++ b/src/shared/database/client.ts @@ -10,6 +10,7 @@ import { migrateOutcomesNotificationReceivedTableForV5, } from './upgrade'; +let terminated = false; const open = async (version = VERSION) => { return openDB(DATABASE_NAME, version, { upgrade(_db, oldVersion, newVersion, transaction) { @@ -67,7 +68,7 @@ const open = async (version = VERSION) => { } // TODO: next version delete NotificationOpened table - + terminated = false; if (!IS_SERVICE_WORKER && typeof OneSignal !== 'undefined') { OneSignal._isNewVisitor = true; } @@ -75,6 +76,13 @@ const open = async (version = VERSION) => { blocked() { Log.debug('IndexedDB: Blocked event'); }, + terminated() { + // reopen if db was terminated + if (!terminated) { + terminated = true; + getDb(); + } + }, }); }; let dbPromise = open(); @@ -109,12 +117,10 @@ export const db = { ) { return (await dbPromise).delete(storeName, key); }, - async clear(storeName: K) { - return (await dbPromise).clear(storeName); - }, - async close() { - return (await dbPromise).close(); - }, +}; + +export const clearStore = async (storeName: K) => { + return (await dbPromise).clear(storeName); }; export const getObjectStoreNames = async () => { @@ -146,7 +152,7 @@ export const cleanupCurrentSession = async () => { export const clearAll = async () => { const objectStoreNames = await getObjectStoreNames(); for (const storeName of objectStoreNames) { - await db.clear(storeName); + await clearStore(storeName); } }; diff --git a/src/shared/helpers/service-worker.ts b/src/shared/helpers/service-worker.ts index 8c14cf025..4cdaf2b08 100755 --- a/src/shared/helpers/service-worker.ts +++ b/src/shared/helpers/service-worker.ts @@ -6,6 +6,7 @@ import OneSignalApiSW from '../api/OneSignalApiSW'; import { encodeHashAsUriComponent } from '../context/helpers'; import { cleanupCurrentSession, + clearStore, db, getCurrentSession, } from '../database/client'; @@ -243,7 +244,7 @@ async function finalizeSession( await Promise.all([ cleanupCurrentSession(), - db.clear('Outcomes.NotificationClicked'), + clearStore('Outcomes.NotificationClicked'), ]); Log.debug( 'Finalize session finished',