Skip to content

Commit 284ea70

Browse files
authored
Merge pull request #1077 from OneSignal/user-model/fix-update-existing-pushsub-on-token-changes-insead-of-creating-new
[User Model] [Fix] Update existing subscription on token changes instead of creating new
2 parents 39598fb + b9d041a commit 284ea70

File tree

11 files changed

+117
-103
lines changed

11 files changed

+117
-103
lines changed

__test__/support/helpers/core.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { CoreDelta } from "../../../src/core/models/CoreDeltas";
55
import { SupportedSubscription, SubscriptionType } from "../../../src/core/models/SubscriptionModels";
66
import { ModelName } from "../../../src/core/models/SupportedModels";
77
import { DUMMY_MODEL_ID, DUMMY_PUSH_TOKEN, DUMMY_SUBSCRIPTION_ID } from "../constants";
8-
8+
import CoreModule from "../../../src/core/CoreModule";
9+
import { CoreModuleDirector } from "../../../src/core/CoreModuleDirector";
910

1011
export function generateNewSubscription(modelId = '0000000000') {
1112
return new OSModel<SupportedSubscription>(
@@ -40,6 +41,13 @@ export function getDummyPushSubscriptionOSModel(): OSModel<SupportedSubscription
4041
}, DUMMY_MODEL_ID);
4142
}
4243

44+
// Requirement: Test must also call TestEnvironment.initialize();
45+
export async function getCoreModuleDirector(): Promise<CoreModuleDirector> {
46+
const coreModule = new CoreModule();
47+
await coreModule.init();
48+
return new CoreModuleDirector(coreModule);
49+
}
50+
4351
export const passIfBroadcastNTimes = (target: number, broadcastCount: number, pass: () => void) => {
4452
if (broadcastCount === target) {
4553
pass();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { CoreModuleDirector } from "../../../src/core/CoreModuleDirector";
2+
import { OSModel } from "../../../src/core/modelRepo/OSModel";
3+
import { SupportedSubscription } from "../../../src/core/models/SubscriptionModels";
4+
import { TestEnvironment } from "../../support/environment/TestEnvironment";
5+
import { getCoreModuleDirector, getDummyPushSubscriptionOSModel } from "../../support/helpers/core";
6+
7+
describe('CoreModuleDirector tests', () => {
8+
beforeEach(() => {
9+
TestEnvironment.initialize();
10+
});
11+
describe('getPushSubscriptionModel', () => {
12+
beforeEach(() => {
13+
jest.resetAllMocks();
14+
});
15+
16+
async function getPushSubscriptionModel(): Promise<OSModel<SupportedSubscription> | undefined> {
17+
return (await getCoreModuleDirector()).getPushSubscriptionModel();
18+
}
19+
20+
test('returns undefined when it find no push records', async () => {
21+
expect(await getPushSubscriptionModel()).toBe(undefined);
22+
});
23+
24+
test('returns current subscription when available', async () => {
25+
const pushModelCurrent = getDummyPushSubscriptionOSModel();
26+
test.stub(CoreModuleDirector.prototype, "getPushSubscriptionModelByCurrentToken", Promise.resolve(pushModelCurrent));
27+
expect(await getPushSubscriptionModel()).toBe(pushModelCurrent);
28+
});
29+
30+
test('returns last known subscription when current is unavailable', async () => {
31+
const pushModelLastKnown = getDummyPushSubscriptionOSModel();
32+
test.stub(CoreModuleDirector.prototype, "getPushSubscriptionModelByLastKnownToken", Promise.resolve(pushModelLastKnown));
33+
expect(await getPushSubscriptionModel()).toBe(pushModelLastKnown);
34+
});
35+
36+
test('returns current subscription over last known', async () => {
37+
const pushModelCurrent = getDummyPushSubscriptionOSModel();
38+
test.stub(CoreModuleDirector.prototype, "getPushSubscriptionModelByCurrentToken", Promise.resolve(pushModelCurrent));
39+
40+
const pushModelLastKnown = getDummyPushSubscriptionOSModel();
41+
test.stub(CoreModuleDirector.prototype, "getPushSubscriptionModelByLastKnownToken", Promise.resolve(pushModelLastKnown));
42+
43+
expect(await getPushSubscriptionModel()).toBe(pushModelCurrent);
44+
});
45+
});
46+
});

src/core/CoreModuleDirector.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,25 +160,39 @@ export class CoreModuleDirector {
160160
return modelStores.pushSubscriptions.models as { [key: string]: OSModel<SupportedSubscription> };
161161
}
162162

163+
private async getPushSubscriptionModelByCurrentToken()
164+
:Promise<OSModel<SupportedSubscription> | undefined> {
165+
logMethodCall("CoreModuleDirector.getPushSubscriptionModelByCurrentToken");
166+
const pushToken = await MainHelper.getCurrentPushToken();
167+
if (pushToken) {
168+
return this.getSubscriptionOfTypeWithToken(ModelName.PushSubscriptions, pushToken);
169+
}
170+
return undefined;
171+
}
172+
173+
// Browser may return a different PushToken value, use the last-known value as a fallback.
174+
// - This happens if you disable notification permissions then re-enable them.
175+
private async getPushSubscriptionModelByLastKnownToken()
176+
:Promise<OSModel<SupportedSubscription> | undefined> {
177+
logMethodCall("CoreModuleDirector.getPushSubscriptionModelByLastKnownToken");
178+
const { lastKnownPushToken } = await Database.getAppState();
179+
if (lastKnownPushToken) {
180+
return this.getSubscriptionOfTypeWithToken(ModelName.PushSubscriptions, lastKnownPushToken);
181+
}
182+
return undefined;
183+
}
184+
163185
/**
164186
* Gets the current push subscription model for the current browser.
165187
* @returns The push subscription model for the current browser, or undefined if no push subscription exists.
166188
*/
167-
// TO DO: make synchronous by making getting the current push token synchronous
168-
public async getCurrentPushSubscriptionModel(): Promise<OSModel<SupportedSubscription> | undefined> {
189+
public async getPushSubscriptionModel()
190+
:Promise<OSModel<SupportedSubscription> | undefined> {
169191
logMethodCall("CoreModuleDirector.getPushSubscriptionModel");
170-
let pushToken = await MainHelper.getCurrentPushToken();
171-
172-
if (!pushToken) {
173-
const appState = await Database.getAppState();
174-
pushToken = appState.lastKnownPushToken;
175-
176-
if (!pushToken) {
177-
Log.debug("No push token found, returning undefined");
178-
return undefined;
179-
}
180-
}
181-
return this.getSubscriptionOfTypeWithToken(ModelName.PushSubscriptions, pushToken);
192+
return (
193+
await this.getPushSubscriptionModelByCurrentToken() ||
194+
await this.getPushSubscriptionModelByLastKnownToken()
195+
);
182196
}
183197

184198
public getIdentityModel(): OSModel<SupportedIdentity> | undefined {
@@ -199,7 +213,7 @@ export class CoreModuleDirector {
199213
logMethodCall("CoreModuleDirector.getAllSubscriptionsModels");
200214
const emailSubscriptions = this.getEmailSubscriptionModels();
201215
const smsSubscriptions = this.getSmsSubscriptionModels();
202-
const pushSubscription = await this.getCurrentPushSubscriptionModel();
216+
const pushSubscription = await this.getPushSubscriptionModel();
203217

204218
const subscriptions = Object.values(emailSubscriptions)
205219
.concat(Object.values(smsSubscriptions))

src/onesignal/PushSubscriptionNamespace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default class PushSubscriptionNamespace extends EventListenerBase {
2929
this._permission = permission;
3030
this._token = subscription.subscriptionToken;
3131

32-
OneSignal.coreDirector.getCurrentPushSubscriptionModel()
32+
OneSignal.coreDirector.getPushSubscriptionModel()
3333
.then((pushModel: OSModel<SupportedSubscription> | undefined) => {
3434
if (pushModel && isCompleteSubscriptionObject(pushModel.data)) {
3535
this._id = pushModel.data.id;

src/page/managers/LoginManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default class LoginManager {
4848
return;
4949
}
5050

51-
const pushSubModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
51+
const pushSubModel = await OneSignal.coreDirector.getPushSubscriptionModel();
5252
let currentPushSubscriptionId;
5353

5454
if (pushSubModel && isCompleteSubscriptionObject(pushSubModel.data)) {
@@ -73,7 +73,7 @@ export default class LoginManager {
7373
}
7474
};
7575

76-
const pushSubscription = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
76+
const pushSubscription = await OneSignal.coreDirector.getPushSubscriptionModel();
7777
if (pushSubscription) {
7878
userData.subscriptions = [pushSubscription.data];
7979
}
@@ -122,7 +122,7 @@ export default class LoginManager {
122122
// before, logging out, process anything waiting in the delta queue so it's not lost
123123
OneSignal.coreDirector.forceDeltaQueueProcessingOnAllExecutors();
124124
UserDirector.resetUserMetaProperties();
125-
const pushSubModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
125+
const pushSubModel = await OneSignal.coreDirector.getPushSubscriptionModel();
126126
await OneSignal.coreDirector.resetModelRepoAndCache();
127127
// add the push subscription model back to the repo since we need at least 1 sub to create a new user
128128
OneSignal.coreDirector.add(ModelName.PushSubscriptions, pushSubModel as OSModel<SupportedModel>, false);

src/shared/helpers/EventHelper.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,25 @@ export default class EventHelper {
3232

3333
const appState = await Database.getAppState();
3434
const { lastKnownPushEnabled, lastKnownPushId, lastKnownPushToken, lastKnownOptedIn } = appState;
35+
36+
const currentPushToken = await MainHelper.getCurrentPushToken();
37+
38+
const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel();
39+
const pushSubscriptionId = pushModel?.data?.id;
40+
3541
const didStateChange = (
3642
lastKnownPushEnabled === null ||
37-
isPushEnabled !== lastKnownPushEnabled
43+
isPushEnabled !== lastKnownPushEnabled ||
44+
currentPushToken !== lastKnownPushToken ||
45+
pushSubscriptionId !== lastKnownPushId
3846
);
3947
if (!didStateChange) {
4048
return;
4149
}
42-
Log.info(
43-
`The user's subscription state changed from ` +
44-
`${lastKnownPushEnabled === null ? '(not stored)' : lastKnownPushEnabled}${isPushEnabled}`
45-
);
4650

4751
// update notification_types via core module
48-
await context.subscriptionManager.updateNotificationTypes();
49-
50-
const currentPushToken = await MainHelper.getCurrentPushToken();
51-
const pushModel: OSModel<SubscriptionModel> = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
52-
const pushSubscriptionId = pushModel.data?.id;
52+
await context.subscriptionManager.updateNotificationTypes()
5353

54-
await Database.setIsPushEnabled(isPushEnabled);
5554
appState.lastKnownPushEnabled = isPushEnabled;
5655
appState.lastKnownPushToken = currentPushToken;
5756
appState.lastKnownPushId = pushSubscriptionId;
@@ -71,7 +70,7 @@ export default class EventHelper {
7170
optedIn: isOptedIn,
7271
}
7372
};
74-
73+
Log.info("Push Subscription state changed: ", change);
7574
EventHelper.triggerSubscriptionChanged(change);
7675
}
7776

src/shared/helpers/SubscriptionHelper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ export default class SubscriptionHelper {
120120
static getRawPushSubscriptionForLegacySafari(safariWebId: string): RawPushSubscription {
121121
const subscription = new RawPushSubscription();
122122

123-
const { deviceToken: existingDeviceToken } = window.safari.pushNotification.permission(safariWebId);
124-
subscription.existingSafariDeviceToken = existingDeviceToken;
123+
const { deviceToken } = window.safari.pushNotification.permission(safariWebId);
124+
subscription.setFromSafariSubscription(deviceToken);
125125

126126
return subscription;
127127
}

src/shared/managers/SubscriptionManager.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class SubscriptionManager {
163163
}
164164

165165
private async _updatePushSubscriptionModelWithRawSubscription(rawPushSubscription: RawPushSubscription) {
166-
const pushModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
166+
const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel();
167167

168168
if (!pushModel) {
169169
OneSignal.coreDirector.generatePushSubscriptionModel(rawPushSubscription);
@@ -206,11 +206,10 @@ export class SubscriptionManager {
206206
}
207207

208208
async updatePushSubscriptionNotificationTypes(notificationTypes: SubscriptionStateKind): Promise<void> {
209-
const pushModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
209+
const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel();
210210
if (!pushModel) {
211-
throw new OneSignalError(
212-
`Cannot update notification_types for push subscription model because it does not exist.`
213-
);
211+
Log.info("No Push Subscription yet to update notification_types.");
212+
return;
214213
}
215214

216215
pushModel.set("notification_types", notificationTypes);
@@ -366,8 +365,6 @@ export class SubscriptionManager {
366365
}
367366

368367
const { deviceToken: existingDeviceToken } = window.safari.pushNotification.permission(this.config.safariWebId);
369-
pushSubscriptionDetails.existingSafariDeviceToken = existingDeviceToken;
370-
371368
if (!existingDeviceToken) {
372369
/*
373370
We're about to show the Safari native permission request. It can fail for a number of
@@ -605,10 +602,6 @@ export class SubscriptionManager {
605602

606603
// Create our own custom object from the browser's native PushSubscription object
607604
const pushSubscriptionDetails = RawPushSubscription.setFromW3cSubscription(newPushSubscription);
608-
if (existingPushSubscription) {
609-
pushSubscriptionDetails.existingW3cPushSubscription =
610-
RawPushSubscription.setFromW3cSubscription(existingPushSubscription);
611-
}
612605
return pushSubscriptionDetails;
613606
}
614607

@@ -800,7 +793,7 @@ export class SubscriptionManager {
800793
private async getSubscriptionStateForSecure(): Promise<PushSubscriptionState> {
801794
const { optedOut, subscriptionToken } = await Database.getSubscription();
802795

803-
const pushSubscriptionOSModel: OSModel<SupportedSubscription> | undefined = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
796+
const pushSubscriptionOSModel: OSModel<SupportedSubscription> | undefined = await OneSignal.coreDirector.getPushSubscriptionModel();
804797
const isValidPushSubscription = isCompleteSubscriptionObject(pushSubscriptionOSModel?.data) && !!pushSubscriptionOSModel?.onesignalId;
805798

806799
if (Environment.useSafariLegacyPush()) {

src/shared/managers/UpdateManager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class UpdateManager {
6161
return;
6262
}
6363

64-
const subscriptionModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
64+
const subscriptionModel = await OneSignal.coreDirector.getPushSubscriptionModel();
6565

6666
if (subscriptionModel?.data.notification_types !== SubscriptionStateKind.Subscribed &&
6767
OneSignal.config?.enableOnSession !== true) {
@@ -151,7 +151,7 @@ export class UpdateManager {
151151

152152
public async sendOutcomeDirect(appId: string, notificationIds: string[], outcomeName: string, value?: number) {
153153
logMethodCall("sendOutcomeDirect");
154-
const pushSubscriptionModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
154+
const pushSubscriptionModel = await OneSignal.coreDirector.getPushSubscriptionModel();
155155

156156
if (pushSubscriptionModel && isCompleteSubscriptionObject(pushSubscriptionModel?.data)) {
157157
const outcomeRequestData: OutcomeRequestData = {
@@ -175,7 +175,7 @@ export class UpdateManager {
175175

176176
public async sendOutcomeInfluenced(appId: string, notificationIds: string[], outcomeName: string, value?: number) {
177177
logMethodCall("sendOutcomeInfluenced");
178-
const pushSubscriptionModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
178+
const pushSubscriptionModel = await OneSignal.coreDirector.getPushSubscriptionModel();
179179

180180
if (pushSubscriptionModel && isCompleteSubscriptionObject(pushSubscriptionModel?.data)) {
181181
const outcomeRequestData: OutcomeRequestData = {
@@ -199,7 +199,7 @@ export class UpdateManager {
199199

200200
public async sendOutcomeUnattributed(appId: string, outcomeName: string, value?: number) {
201201
logMethodCall("sendOutcomeUnattributed");
202-
const pushSubscriptionModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
202+
const pushSubscriptionModel = await OneSignal.coreDirector.getPushSubscriptionModel();
203203

204204
if (pushSubscriptionModel && isCompleteSubscriptionObject(pushSubscriptionModel?.data)) {
205205
const outcomeRequestData: OutcomeRequestData = {

src/shared/managers/sessionManager/SessionManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class SessionManager implements ISessionManager {
9292

9393
async _getOneSignalAndSubscriptionIds(): Promise<{ onesignalId: string; subscriptionId: string }> {
9494
const identityModel = OneSignal.coreDirector.getIdentityModel();
95-
const pushSubscriptionModel = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
95+
const pushSubscriptionModel = await OneSignal.coreDirector.getPushSubscriptionModel();
9696

9797
if (!identityModel || !identityModel.onesignalId) {
9898
throw new OneSignalError("Abort _getOneSignalAndSubscriptionIds: no identity");
@@ -340,7 +340,7 @@ export class SessionManager implements ISessionManager {
340340
return;
341341
}
342342

343-
const pushSubscription = await OneSignal.coreDirector.getCurrentPushSubscriptionModel();
343+
const pushSubscription = await OneSignal.coreDirector.getPushSubscriptionModel();
344344
if (pushSubscription?.data.notification_types !== SubscriptionStateKind.Subscribed &&
345345
OneSignal.config?.enableOnSession !== true) {
346346
return;

0 commit comments

Comments
 (0)