Skip to content

Commit 2465d22

Browse files
committed
remove legact pendingNotificationClickEvents
1 parent 4c2728e commit 2465d22

14 files changed

Lines changed: 120 additions & 244 deletions

File tree

__test__/unit/pushSubscription/nativePermissionChange.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
99
import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers';
1010
import { MockServiceWorker } from '__test__/support/mocks/MockServiceWorker';
11-
import { db } from 'src/shared/database/client';
11+
import { db, getOptionsValue } from 'src/shared/database/client';
1212
import { setAppState as setDBAppState } from 'src/shared/database/config';
1313
import * as PermissionUtils from 'src/shared/helpers/permissions';
1414
import Emitter from 'src/shared/libraries/Emitter';
@@ -78,8 +78,9 @@ describe('Notification Types are set correctly on subscription change', () => {
7878
await MainHelper.checkAndTriggerNotificationPermissionChanged();
7979

8080
// should update the db
81-
const dbPermission = (await db.get('Options', 'notificationPermission'))
82-
?.value;
81+
const dbPermission = await getOptionsValue<NotificationPermission>(
82+
'notificationPermission',
83+
);
8384
expect(dbPermission).toBe(NotificationPermission.Granted);
8485
expect(permChangeListener).toHaveBeenCalledWith(true);
8586
expect(permChangeStringListener).toHaveBeenCalledWith('granted');
@@ -88,8 +89,7 @@ describe('Notification Types are set correctly on subscription change', () => {
8889

8990
describe('checkAndTriggerSubscriptionChanged', async () => {
9091
const setAppState = async (appState: Partial<AppState>) => {
91-
const currentAppState = (await db.get('Options', 'appState'))
92-
?.value as AppState;
92+
const currentAppState = (await getOptionsValue<AppState>('appState'))!;
9393
await setDBAppState({
9494
...currentAppState,
9595
...appState,

src/core/modelRepo/ModelStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ export abstract class ModelStore<
161161
protected async load(): Promise<void> {
162162
if (!this.modelName) return;
163163

164-
const jsonArray = await db.getAll(this.modelName);
164+
const jsonArray = (await db.getAll(this.modelName)) as unknown as DBModel[];
165165

166166
const shouldRePersist = this.models.length > 0;
167167

168168
for (let index = jsonArray.length - 1; index >= 0; index--) {
169-
const newModel = this.create(jsonArray[index] as DBModel);
169+
const newModel = this.create(jsonArray[index]);
170170
if (!newModel) continue;
171171

172172
this.models.unshift(newModel);

src/onesignal/NotificationsNamespace.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
WrongTypeArgumentError,
66
} from 'src/shared/errors/common';
77
import { isValidUrl } from 'src/shared/helpers/validators';
8-
import { fireStoredNotificationClicks } from 'src/shared/listeners';
98
import type {
109
NotificationEventName,
1110
NotificationEventTypeMap,
@@ -125,10 +124,6 @@ export default class NotificationsNamespace extends EventListenerBase {
125124
listener: (obj: NotificationEventTypeMap[K]) => void,
126125
): void {
127126
OneSignal.emitter.on(event, listener);
128-
129-
if (event === 'click') {
130-
fireStoredNotificationClicks();
131-
}
132127
}
133128

134129
removeEventListener<K extends NotificationEventName>(

src/shared/database/client.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { openDB } from 'idb';
1+
import { openDB, type StoreNames } from 'idb';
22
import { ONESIGNAL_SESSION_KEY } from '../session/constants';
33
import {
44
DATABASE_NAME,
55
LegacyModelName,
66
ModelName,
77
VERSION,
88
} from './constants';
9-
import type { IndexedDBSchema } from './types';
9+
import type { IdKey, IndexedDBSchema, OptionKey } from './types';
1010
import {
1111
migrateModelNameSubscriptionsTableForV6,
1212
migrateOutcomesNotificationClickedTableForV5,
@@ -77,33 +77,57 @@ export const getDb = async () => {
7777
return dbInstance;
7878
};
7979

80+
type StoreName = StoreNames<IndexedDBSchema>;
81+
8082
// Export db object with the same API as before
8183
export const db = {
82-
async get(storeName: keyof IndexedDBSchema, key?: string) {
84+
async get<K extends StoreName>(
85+
storeName: K,
86+
key: string,
87+
): Promise<IndexedDBSchema[K]['value'] | undefined> {
8388
const _db = await getDb();
84-
return key ? _db.get(storeName as any, key) : _db.getAll(storeName as any);
89+
return _db.get(storeName, key);
8590
},
86-
async getAll(storeName: keyof IndexedDBSchema) {
91+
async getAll<K extends StoreName>(
92+
storeName: K,
93+
): Promise<IndexedDBSchema[K]['value'][]> {
8794
const _db = await getDb();
88-
return _db.getAll(storeName as any);
95+
return _db.getAll(storeName);
8996
},
90-
async put(storeName: keyof IndexedDBSchema, value: any) {
97+
async put<K extends StoreName>(
98+
storeName: K,
99+
value: IndexedDBSchema[K]['value'],
100+
) {
91101
const _db = await getDb();
92-
return _db.put(storeName as any, value);
102+
return _db.put(storeName, value);
93103
},
94-
async delete(storeName: keyof IndexedDBSchema, key: string) {
104+
async delete<K extends StoreName>(storeName: K, key: string) {
95105
const _db = await getDb();
96-
return _db.delete(storeName as any, key);
106+
return _db.delete(storeName, key);
97107
},
98-
async clear(storeName: keyof IndexedDBSchema) {
108+
async clear<K extends StoreName>(storeName: K) {
99109
const _db = await getDb();
100-
return _db.clear(storeName as any);
110+
return _db.clear(storeName);
101111
},
102112
get objectStoreNames() {
103113
return dbInstance?.objectStoreNames || [];
104114
},
105115
};
106116

117+
export const getOptionsValue = async <T extends unknown>(
118+
key: OptionKey,
119+
): Promise<T | null> => {
120+
const result = await db.get('Options', key);
121+
if (result && 'value' in result) return result.value as T;
122+
return null;
123+
};
124+
125+
export const getIdsValue = async <T>(key: IdKey): Promise<T | null> => {
126+
const result = await db.get('Ids', key);
127+
if (result && 'id' in result) return result.id as T;
128+
return null;
129+
};
130+
107131
export const getCurrentSession = async () => {
108132
return (await db.get('Sessions', ONESIGNAL_SESSION_KEY)) ?? null;
109133
};

src/shared/database/config.ts

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,26 @@
1-
import {
2-
type NotificationClickForOpenHandlingSchema,
3-
notificationClickFromDatabase,
4-
} from '../helpers/serializer';
5-
import {
6-
AppState,
7-
type PendingNotificationClickEvents,
8-
} from '../models/AppState';
9-
import { db } from './client';
1+
import { AppState } from '../models/AppState';
2+
import { db, getIdsValue, getOptionsValue } from './client';
103

114
export const getDBAppConfig = async () => {
125
const config: any = {};
13-
const appIdStr: string = (await db.get('Ids', 'appId'))?.id as string;
6+
const appIdStr = await getIdsValue<string>('appId');
147
config.appId = appIdStr;
15-
config.vapidPublicKey = (await db.get('Options', 'vapidPublicKey'))?.value;
8+
config.vapidPublicKey = await getOptionsValue<string>('vapidPublicKey');
169
return config;
1710
};
1811

19-
const getAllPendingNotificationClickEvents =
20-
async (): Promise<PendingNotificationClickEvents> => {
21-
const clickedNotifications: PendingNotificationClickEvents = {};
22-
const eventsFromDb = await db.getAll('NotificationOpened');
23-
for (const eventFromDb of eventsFromDb) {
24-
const event = notificationClickFromDatabase(eventFromDb);
25-
const url = event.result.url;
26-
if (!url) {
27-
continue;
28-
}
29-
clickedNotifications[url] = event;
30-
}
31-
return clickedNotifications;
32-
};
33-
3412
export const getAppState = async (): Promise<AppState> => {
3513
const state = new AppState();
36-
state.defaultNotificationUrl = (await db.get('Options', 'defaultUrl'))
37-
?.value as string;
38-
state.defaultNotificationTitle = (await db.get('Options', 'defaultTitle'))
39-
?.value as string;
40-
state.lastKnownPushEnabled = (await db.get('Options', 'isPushEnabled'))
41-
?.value as boolean;
42-
state.pendingNotificationClickEvents =
43-
await getAllPendingNotificationClickEvents();
14+
state.defaultNotificationUrl = await getOptionsValue<string>('defaultUrl');
15+
state.defaultNotificationTitle =
16+
await getOptionsValue<string>('defaultTitle');
17+
state.lastKnownPushEnabled = await getOptionsValue<boolean>('isPushEnabled');
4418

4519
// lastKnown<PushId|PushToken|OptedIn> are used to track changes to the user's subscription
4620
// state. Displayed in the `current` & `previous` fields of the `subscriptionChange` event.
47-
state.lastKnownPushId = (await db.get('Options', 'lastPushId'))
48-
?.value as string;
49-
state.lastKnownPushToken = (await db.get('Options', 'lastPushToken'))
50-
?.value as string;
51-
state.lastKnownOptedIn = (await db.get('Options', 'lastOptedIn'))
52-
?.value as boolean;
21+
state.lastKnownPushId = await getOptionsValue<string>('lastPushId');
22+
state.lastKnownPushToken = await getOptionsValue<string>('lastPushToken');
23+
state.lastKnownOptedIn = await getOptionsValue<boolean>('lastOptedIn');
5324
return state;
5425
};
5526

@@ -91,35 +62,8 @@ export const setAppState = async (appState: AppState) => {
9162
key: 'lastOptedIn',
9263
value: appState.lastKnownOptedIn,
9364
});
94-
95-
if (appState.pendingNotificationClickEvents) {
96-
const clickedNotificationUrls = Object.keys(
97-
appState.pendingNotificationClickEvents,
98-
);
99-
for (const url of clickedNotificationUrls) {
100-
const notificationDetails =
101-
appState.pendingNotificationClickEvents[
102-
url as keyof typeof appState.pendingNotificationClickEvents
103-
];
104-
105-
if (notificationDetails) {
106-
await db.put('NotificationOpened', {
107-
url: url,
108-
data: (notificationDetails as any).data,
109-
timestamp: (notificationDetails as any).timestamp,
110-
} as NotificationClickForOpenHandlingSchema);
111-
} else if (notificationDetails === null) {
112-
// If we get an object like:
113-
// { "http://site.com/page": null}
114-
// It means we need to remove that entry
115-
await db.delete('NotificationOpened', url);
116-
}
117-
}
118-
}
11965
};
12066

12167
export const getConsentGiven = async () => {
122-
return (await db.get('Options', 'consentGiven'))?.value as
123-
| boolean
124-
| undefined;
68+
return await getOptionsValue<boolean>('consentGiven');
12569
};

src/shared/database/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ModelName } from './constants';
1111

1212
export type ModelNameType = (typeof ModelName)[keyof typeof ModelName];
1313

14-
type IdKey = 'appId' | 'registrationId' | 'userId' | 'jwtToken';
14+
export type IdKey = 'appId' | 'registrationId' | 'userId' | 'jwtToken';
1515

1616
export type OptionKey =
1717
| 'appState'
@@ -124,7 +124,7 @@ export interface IndexedDBSchema extends DBSchema {
124124
key: OptionKey;
125125
value: {
126126
key: OptionKey;
127-
value: boolean | number | string | AppState | undefined;
127+
value: boolean | number | string | AppState | undefined | null;
128128
};
129129
};
130130

@@ -175,7 +175,8 @@ export interface IndexedDBSchema extends DBSchema {
175175
value: {
176176
modelId: string;
177177
modelName: 'operations';
178-
[key: string]: any;
178+
name: string;
179+
[key: string]: unknown;
179180
};
180181
};
181182
}

src/shared/helpers/init.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AppConfig } from '../config/types';
33
import type { ContextInterface } from '../context/types';
44
import { db } from '../database/client';
55
import { getSubscription, setSubscription } from '../database/subscription';
6+
import type { OptionKey } from '../database/types';
67
import Log from '../libraries/Log';
78
import { CustomLinkManager } from '../managers/CustomLinkManager';
89
import { NotificationPermission } from '../models/NotificationPermission';
@@ -306,13 +307,16 @@ export async function saveInitOptions() {
306307
) {
307308
opPromises.push(
308309
db.put('Options', {
309-
key: `webhooks.${event}`,
310+
key: `webhooks.${event}` as OptionKey,
310311
value: webhookOptions[event as keyof typeof webhookOptions],
311312
}),
312313
);
313314
} else {
314315
opPromises.push(
315-
db.put('Options', { key: `webhooks.${event}`, value: false }),
316+
db.put('Options', {
317+
key: `webhooks.${event}` as OptionKey,
318+
value: false,
319+
}),
316320
);
317321
}
318322
});

src/shared/helpers/permissions.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { db } from '../database/client';
1+
import { db, getOptionsValue } from '../database/client';
22
import OneSignalEvent from '../services/OneSignalEvent';
33

44
// This flag prevents firing the NOTIFICATION_PERMISSION_CHANGED_AS_STRING event twice
@@ -24,8 +24,7 @@ export const triggerNotificationPermissionChanged = async (force = false) => {
2424
const privateTriggerNotificationPermissionChanged = async (force: boolean) => {
2525
const newPermission: NotificationPermission =
2626
await OneSignal.context.permissionManager.getPermissionStatus();
27-
const previousPermission: NotificationPermission = await db.get(
28-
'Options',
27+
const previousPermission = await getOptionsValue<NotificationPermission>(
2928
'notificationPermission',
3029
);
3130

@@ -47,7 +46,7 @@ const privateTriggerNotificationPermissionChanged = async (force: boolean) => {
4746
};
4847

4948
const triggerBooleanPermissionChangeEvent = (
50-
previousPermission: NotificationPermission,
49+
previousPermission: NotificationPermission | null,
5150
newPermission: NotificationPermission,
5251
force: boolean,
5352
): void => {

src/shared/listeners.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
2-
import * as eventListeners from 'src/shared/listeners';
2+
import OneSignal from 'src/onesignal/OneSignal';
3+
import type { Mock } from 'vitest';
34

4-
describe('Notification Listeners', () => {
5-
beforeEach(async () => {
6-
await TestEnvironment.initialize();
7-
});
5+
let emitterSpy: Mock;
86

9-
afterEach(() => {
10-
vi.resetAllMocks();
11-
});
7+
beforeEach(async () => {
8+
await TestEnvironment.initialize();
9+
emitterSpy = vi.spyOn(OneSignal.emitter, 'on');
10+
});
11+
12+
afterEach(() => {
13+
vi.resetAllMocks();
14+
});
1215

13-
test('Adding click listener fires internal EventHelper', async () => {
14-
const stub = vi.spyOn(eventListeners, 'fireStoredNotificationClicks');
15-
// @ts-expect-error - listener doesnt matter
16-
OneSignal.Notifications.addEventListener('click', null);
17-
expect(stub).toHaveBeenCalledTimes(1);
18-
});
16+
test('Adding click listener fires internal EventHelper', async () => {
17+
OneSignal.Notifications.addEventListener('click', () => {});
18+
expect(emitterSpy).toHaveBeenCalledTimes(1);
1919
});

0 commit comments

Comments
 (0)