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
29 changes: 29 additions & 0 deletions __test__/unit/push/registerForPush.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as InitHelper from '../../../src/shared/helpers/init';
import { registerForPush } from '../../../src/shared/helpers/subscription';
import OneSignalEvent from '../../../src/shared/services/OneSignalEvent';
import { NotificationType } from '../../../src/shared/subscriptions/constants';
import { SubscriptionStrategyKind } from '../../../src/shared/models/SubscriptionStrategyKind';
import { TestEnvironment } from '../../support/environment/TestEnvironment';

//stub dismisshelper
Expand Down Expand Up @@ -48,4 +51,30 @@ describe('Register for push', () => {
);
expect(spy).toHaveBeenCalledTimes(1);
});

test('registerForPush passes notificationTypesOverride to _subscribe', async () => {
const subscribeSpy = vi
.spyOn(OneSignal._context._subscriptionManager, '_subscribe')
.mockRejectedValue(new Error('mock stop'));

await registerForPush(NotificationType._UserOptedOut);

expect(subscribeSpy).toHaveBeenCalledWith(
SubscriptionStrategyKind._ResubscribeExisting,
NotificationType._UserOptedOut,
);
});

test('registerForPush calls _subscribe without override when none provided', async () => {
const subscribeSpy = vi
.spyOn(OneSignal._context._subscriptionManager, '_subscribe')
.mockRejectedValue(new Error('mock stop'));

await registerForPush();

expect(subscribeSpy).toHaveBeenCalledWith(
SubscriptionStrategyKind._ResubscribeExisting,
undefined,
);
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
},
{
"path": "./build/releases/OneSignalSDK.page.es6.js",
"limit": "46.44 kB",
"limit": "46.56 kB",
"gzip": true
},
{
Expand Down
34 changes: 33 additions & 1 deletion src/shared/helpers/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import { CustomLinkManager } from '../managers/CustomLinkManager';
import { SubscriptionStrategyKind } from '../models/SubscriptionStrategyKind';
import { limitStorePut } from '../services/limitStore';
import OneSignalEvent from '../services/OneSignalEvent';
import { NotificationType } from '../subscriptions/constants';
import { IS_SERVICE_WORKER } from '../utils/env';
import { once } from '../utils/utils';
import { getAppId } from './main';
import { incrementPageViewCount } from './pageview';
import { triggerNotificationPermissionChanged } from './permissions';
import { registerForPush } from './subscription';

let _didAppIdMigrate = false;

export async function internalInit() {
Log._debug('Called internalInit()');

Expand Down Expand Up @@ -83,6 +86,10 @@ async function sessionInit(): Promise<void> {
await setSubscription(subscription);
await handleAutoResubscribe(isOptedOut);

if (_didAppIdMigrate && isOptedOut) {
await createOptedOutSubscriptionForMigration();
}

const isSubscribed =
await OneSignal._context._subscriptionManager._isPushNotificationsEnabled();
// saves isSubscribed to IndexedDb. used for require user interaction functionality
Expand Down Expand Up @@ -368,7 +375,9 @@ export async function initSaveState(overridingPageTitle?: string) {
const config: AppConfig = OneSignal.config!;

const previousAppId = await getIdsValue<string>('appId');
if (previousAppId && previousAppId !== appId) {
_didAppIdMigrate = !!(previousAppId && previousAppId !== appId);

if (_didAppIdMigrate) {
Log._info(
`OneSignal: App ID changed from ${previousAppId} to ${appId}. Clearing stale state for migration.`,
);
Expand All @@ -390,6 +399,29 @@ export async function initSaveState(overridingPageTitle?: string) {
Log._info(`OneSignal: Set pageTitle to be '${pageTitle}'.`);
}

async function createOptedOutSubscriptionForMigration() {
const currentPermission: NotificationPermission =
await OneSignal._context._permissionManager._getNotificationPermission(
OneSignal._context._appConfig.safariWebId,
);
if (currentPermission !== 'granted') {
return;
}

Log._info(
'App ID migration: creating unsubscribed subscription for opted-out user.',
);

// Call _subscribe directly instead of registerForPush to avoid
// _registerSubscription (which resets optedOut to false) and
// checkAndTriggerSubscriptionChanged (which would read that false
// value and overwrite notification_types back to _Subscribed).
await OneSignal._context._subscriptionManager._subscribe(
SubscriptionStrategyKind._ResubscribeExisting,
NotificationType._UserOptedOut,
);
Comment on lines +419 to +422
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to suggestions if there's a better way to achieve this.

}

async function handleAutoResubscribe(isOptedOut: boolean) {
Log._info('handleAutoResubscribe', {
autoResubscribe: OneSignal.config?.userConfig.autoResubscribe,
Expand Down
10 changes: 8 additions & 2 deletions src/shared/helpers/subscription.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { SubscriptionType } from 'src/shared/subscriptions/constants';
import type { SubscriptionTypeValue } from 'src/shared/subscriptions/types';
import type {
NotificationTypeValue,
SubscriptionTypeValue,
} from 'src/shared/subscriptions/types';
import Log from '../libraries/Log';
import { checkAndTriggerSubscriptionChanged } from '../listeners';
import { Subscription } from '../models/Subscription';
Expand All @@ -8,14 +11,17 @@ import { IS_SERVICE_WORKER } from '../utils/env';
import { incrementPageViewCount } from './pageview';
import { triggerNotificationPermissionChanged } from './permissions';

export async function registerForPush(): Promise<Subscription | null> {
export async function registerForPush(
notificationTypesOverride?: NotificationTypeValue,
): Promise<Subscription | null> {
const context = OneSignal._context;
let subscription: Subscription | null = null;

if (IS_SERVICE_WORKER) throw new Error('Unsupported environment');
try {
const rawSubscription = await context._subscriptionManager._subscribe(
SubscriptionStrategyKind._ResubscribeExisting,
notificationTypesOverride,
);
subscription =
await context._subscriptionManager._registerSubscription(rawSubscription);
Expand Down
54 changes: 54 additions & 0 deletions src/shared/managers/SubscriptionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
MockServiceWorker,
} from '__test__/support/mocks/MockServiceWorker';
import { setPushToken } from '../database/subscription';
import { NotificationType } from '../subscriptions/constants';
import { IDManager } from './IDManager';
import {
SubscriptionManagerPage,
Expand Down Expand Up @@ -145,6 +146,59 @@ describe('SubscriptionManager', () => {

await vi.waitUntil(() => updateSubscriptionFn.mock.calls.length > 0);
});

test('should apply notificationTypesOverride when creating a new model', async () => {
setCreateUserResponse();
const rawSubscription = getRawPushSubscription();
await setPushToken(rawSubscription.w3cEndpoint?.toString());

await updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
NotificationType._UserOptedOut,
);

const subModels = OneSignal._coreDirector._subscriptionModelStore._list();
expect(subModels.length).toBe(1);
expect(subModels[0]._notification_types).toBe(
NotificationType._UserOptedOut,
);
expect(subModels[0].enabled).toBe(false);

await vi.waitUntil(() => createUserFn.mock.calls.length > 0);
expect(createUserFn).toHaveBeenCalledWith(
expect.objectContaining({
subscriptions: [
expect.objectContaining({
notification_types: NotificationType._UserOptedOut,
enabled: false,
}),
],
}),
);
});

test('should ignore notificationTypesOverride for existing models', async () => {
setUpdateSubscriptionResponse({ subscriptionId: '123' });
const rawSubscription = getRawPushSubscription();
await setPushToken(rawSubscription.w3cEndpoint?.toString());

await setupSubModelStore({
id: '123',
token: rawSubscription.w3cEndpoint?.toString(),
onesignalId: EXTERNAL_ID,
});

await updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
NotificationType._UserOptedOut,
);

const updatedPushModel =
(await OneSignal._coreDirector._getPushSubscriptionModel())!;
expect(updatedPushModel._notification_types).not.toBe(
NotificationType._UserOptedOut,
);
});
});
});

Expand Down
17 changes: 15 additions & 2 deletions src/shared/managers/subscription/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function executeCallback<T>(callback?: (...args: any[]) => T, ...args: any[]) {

export const updatePushSubscriptionModelWithRawSubscription = async (
rawPushSubscription: RawPushSubscription,
notificationTypesOverride?: NotificationTypeValue,
) => {
// incase a login op was called before user accepts the notifcations permissions, we need to wait for it to finish
// otherwise there would be two login ops in the same bucket for LoginOperationExecutor which would error
Expand All @@ -62,6 +63,11 @@ export const updatePushSubscriptionModelWithRawSubscription = async (
OneSignal._coreDirector._generatePushSubscriptionModel(
rawPushSubscription,
);
if (notificationTypesOverride !== undefined) {
pushModel._notification_types = notificationTypesOverride;
pushModel.enabled =
notificationTypesOverride === NotificationType._Subscribed;
}
return createUserOnServer();
}
// for users with data failed to create a user or user + subscription on the server
Expand Down Expand Up @@ -236,6 +242,7 @@ export class SubscriptionManagerPage extends SubscriptionManagerBase<ContextInte
*/
public async _subscribe(
subscriptionStrategy: SubscriptionStrategyKindValue,
notificationTypesOverride?: NotificationTypeValue,
): Promise<RawPushSubscription> {
let rawPushSubscription: RawPushSubscription;

Expand All @@ -258,7 +265,10 @@ export class SubscriptionManagerPage extends SubscriptionManagerBase<ContextInte

if (useSafariLegacyPush()) {
rawPushSubscription = await this._subscribeSafari();
await updatePushSubscriptionModelWithRawSubscription(rawPushSubscription);
await updatePushSubscriptionModelWithRawSubscription(
rawPushSubscription,
notificationTypesOverride,
);
/* Now that permissions have been granted, install the service worker */
Log._info('Installing SW on Safari');
try {
Expand All @@ -270,7 +280,10 @@ export class SubscriptionManagerPage extends SubscriptionManagerBase<ContextInte
} else {
rawPushSubscription =
await this._subscribeFcmFromPage(subscriptionStrategy);
await updatePushSubscriptionModelWithRawSubscription(rawPushSubscription);
await updatePushSubscriptionModelWithRawSubscription(
rawPushSubscription,
notificationTypesOverride,
);
}

return rawPushSubscription;
Expand Down
Loading