Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion __test__/unit/models/deliveryPlatformKind.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { DeliveryPlatformKind } from '../../../src/shared/models/DeliveryPlatfor
describe('DeliveryPlatformKind', () => {
test('delivery platform constants should be correct', async () => {
expect(DeliveryPlatformKind.ChromeLike).toBe(5);
expect(DeliveryPlatformKind.SafariLegacy).toBe(7);
expect(DeliveryPlatformKind.Firefox).toBe(8);
expect(DeliveryPlatformKind.Email).toBe(11);
expect(DeliveryPlatformKind.Edge).toBe(12);
Expand Down
4 changes: 3 additions & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare global {
OneSignal: _OneSignal;
OneSignalDeferred?: OneSignalDeferredLoadedCallback[];
__oneSignalSdkLoadCount?: number;
safari?: {};
safari?: {
pushNotification?: {};
};
}
}
5 changes: 1 addition & 4 deletions src/page/userModel/FuturePushSubscriptionRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ export default class FuturePushSubscriptionRecord implements Serializable {
}

private _getToken(subscription: RawPushSubscription): string | undefined {
if (subscription.w3cEndpoint) {
return subscription.w3cEndpoint.toString();
}
return subscription.safariDeviceToken;
return subscription.w3cEndpoint?.toString();
}

serialize() {
Expand Down
11 changes: 10 additions & 1 deletion src/page/utils/BrowserSupportsPush.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
// Checks if the browser supports push notifications by checking if specific
// classes and properties on them exist
export function isPushNotificationsSupported() {
return supportsVapidPush();
return supportsVapidPush() || supportsSafariLegacyPush();
}

// Allow app to run with legacy safari push notifications. If safari version is newer then
// the subscription will ported in SubscriptionManager _updatePushSubscriptionModelWithRawSubscription
export function supportsSafariLegacyPush(): boolean {
return (
typeof window.safari !== 'undefined' &&
typeof window.safari.pushNotification !== 'undefined'
);
}

// Does the browser support the standard Push API
Expand Down
1 change: 0 additions & 1 deletion src/shared/helpers/EventHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default class EventHelper {
}

static async checkAndTriggerSubscriptionChanged() {
console.log('checkAndTriggerSubscriptionChanged');
OneSignalUtils.logMethodCall('checkAndTriggerSubscriptionChanged');
const context: ContextSWInterface = OneSignal.context;
// isPushEnabled = subscribed && is not opted out
Expand Down
124 changes: 68 additions & 56 deletions src/shared/managers/SubscriptionManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { APP_ID, DUMMY_EXTERNAL_ID } from '__test__/support/constants';
import TestContext from '__test__/support/environment/TestContext';
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
import { setupSubModelStore } from '__test__/support/environment/TestEnvironmentHelpers';
import {
createUserFn,
setCreateUserResponse,
} from '__test__/support/helpers/requests';
mockUserAgent,
setupSubModelStore,
} from '__test__/support/environment/TestEnvironmentHelpers';
import {
getSubscriptionFn,
MockServiceWorker,
} from '__test__/support/mocks/MockServiceWorker';
import BrowserUserAgent from '__test__/support/models/BrowserUserAgent';
import { SubscriptionType } from 'src/core/types/subscription';
import UserDirector from 'src/onesignal/UserDirector';
import ContextSW from '../models/ContextSW';
import { RawPushSubscription } from '../models/RawPushSubscription';
import Database from '../services/Database';
import { IDManager } from './IDManager';
import {
SubscriptionManager,
Expand All @@ -29,19 +32,24 @@ const getRawSubscription = (): RawPushSubscription => {
rawSubscription.w3cAuth = 'auth';
rawSubscription.w3cP256dh = 'p256dh';
rawSubscription.w3cEndpoint = new URL('https://example.com/endpoint');
// @ts-expect-error - legacy property
rawSubscription.safariDeviceToken = 'safariDeviceToken';
return rawSubscription;
};

const createUserOnServerSpy = vi
.spyOn(UserDirector, 'createUserOnServer')
.mockResolvedValue();

describe('SubscriptionManager', () => {
beforeEach(async () => {
vi.resetModules();
await TestEnvironment.initialize();
await Database.clear();
});

describe('updatePushSubscriptionModelWithRawSubscription', () => {
test('should create the push subscription model if it does not exist', async () => {
setCreateUserResponse();
const context = new ContextSW(TestContext.getFakeMergedConfig());
const subscriptionManager = new SubscriptionManager(context, subConfig);
const rawSubscription = getRawSubscription();
Expand All @@ -55,17 +63,15 @@ describe('SubscriptionManager', () => {
token: rawSubscription.w3cEndpoint?.toString(),
});

// @ts-expect-error - private method
await subscriptionManager._updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
);

subModels = await OneSignal.coreDirector.subscriptionModelStore.list();
expect(subModels.length).toBe(1);

const id = subModels[0].id;
expect(IDManager.isLocalId(id)).toBe(true);
expect(subModels[0].toJSON()).toEqual({
id,
device_model: '',
device_os: 56,
enabled: true,
Expand All @@ -77,28 +83,7 @@ describe('SubscriptionManager', () => {
web_p256: rawSubscription.w3cP256dh,
});

await vi.waitUntil(() => createUserFn.mock.calls.length > 0);
expect(createUserFn).toHaveBeenCalledWith({
identity: {},
properties: {
language: 'en',
timezone_id: 'America/Los_Angeles',
},
refresh_device_metadata: true,
subscriptions: [
{
device_model: '',
device_os: 56,
enabled: true,
notification_types: 1,
sdk: '1',
token: rawSubscription.w3cEndpoint?.toString(),
type: 'ChromePush',
web_auth: rawSubscription.w3cAuth,
web_p256: rawSubscription.w3cP256dh,
},
],
});
expect(createUserOnServerSpy).toHaveBeenCalled();
});

test('should create user if push subscription model does not have an id', async () => {
Expand All @@ -110,9 +95,6 @@ describe('SubscriptionManager', () => {
getSubscriptionFn.mockResolvedValue({
endpoint: rawSubscription.w3cEndpoint?.toString(),
});
setCreateUserResponse({
externalId: 'some-external-id',
});

const context = new ContextSW(TestContext.getFakeMergedConfig());
const subscriptionManager = new SubscriptionManager(context, subConfig);
Expand All @@ -128,38 +110,17 @@ describe('SubscriptionManager', () => {
onesignalId: identityModel.onesignalId,
});

// @ts-expect-error - private method
await subscriptionManager._updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
);

// should not call generatePushSubscriptionModelSpy
expect(generatePushSubscriptionModelSpy).not.toHaveBeenCalled();

expect(createUserFn).toHaveBeenCalledWith({
identity: {
external_id: 'some-external-id',
},
properties: {
language: 'en',
timezone_id: 'America/Los_Angeles',
},
refresh_device_metadata: true,
subscriptions: [
{
device_model: '',
device_os: 56,
enabled: true,
notification_types: 1,
sdk: '1',
token: rawSubscription.w3cEndpoint?.toString(),
type: 'ChromePush',
},
],
});
expect(createUserOnServerSpy).toHaveBeenCalled();
});

test('should update the push subscription model if it already exists', async () => {
setCreateUserResponse();
const context = new ContextSW(TestContext.getFakeMergedConfig());
const subscriptionManager = new SubscriptionManager(context, subConfig);
const rawSubscription = getRawSubscription();
Expand All @@ -176,6 +137,7 @@ describe('SubscriptionManager', () => {
pushModel.web_auth = 'old-web-auth';
pushModel.web_p256 = 'old-web-p256';

// @ts-expect-error - private method
await subscriptionManager._updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
);
Expand All @@ -188,10 +150,60 @@ describe('SubscriptionManager', () => {
expect(updatedPushModel.web_auth).toBe(rawSubscription.w3cAuth);
expect(updatedPushModel.web_p256).toBe(rawSubscription.w3cP256dh);
});

test('should port legacy safari push to new format', async () => {
const rawSubscription = getRawSubscription();
getSubscriptionFn.mockResolvedValue({
endpoint: rawSubscription.w3cEndpoint?.toString(),
});

const context = new ContextSW(TestContext.getFakeMergedConfig());
const subscriptionManager = new SubscriptionManager(context, subConfig);

// setting up legacy push model
await OneSignal.database.setTokenAndId({
token: 'old-token',
});
const pushModel = await setupSubModelStore({
id: '123',
token: 'old-token',
onesignalId: DUMMY_EXTERNAL_ID,
});
pushModel.type = SubscriptionType.SafariLegacyPush;

// mock agent to safari with vapid push support
mockUserAgent({
userAgent: BrowserUserAgent.SafariSupportedMac121,
});

// @ts-expect-error - private method
await subscriptionManager._updatePushSubscriptionModelWithRawSubscription(
rawSubscription,
);

// should update push model with new token, type, web_auth, and web_p256
const updatedPushModel =
(await OneSignal.coreDirector.getPushSubscriptionModel())!;
expect(updatedPushModel.type).toBe(SubscriptionType.SafariPush);
expect(updatedPushModel.token).toBe(
rawSubscription.w3cEndpoint!.toString(),
);
expect(updatedPushModel.web_auth).toBe(rawSubscription.w3cAuth);
expect(updatedPushModel.web_p256).toBe(rawSubscription.w3cP256dh);
});
});
});

Object.defineProperty(global.navigator, 'serviceWorker', {
value: new MockServiceWorker(),
writable: true,
});

Object.defineProperty(global, 'PushSubscriptionOptions', {
value: {
prototype: {
applicationServerKey: 'test',
},
},
writable: true,
});
8 changes: 2 additions & 6 deletions src/shared/managers/SubscriptionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PushPermissionNotGrantedError, {
PushPermissionNotGrantedErrorReason,
} from '../errors/PushPermissionNotGrantedError';
import ServiceWorkerRegistrationError from '../errors/ServiceWorkerRegistrationError';
import Environment from '../helpers/Environment';
import { ServiceWorkerActiveState } from '../helpers/ServiceWorkerHelper';
import Log from '../libraries/Log';
import { ContextSWInterface } from '../models/ContextSW';
Expand Down Expand Up @@ -153,11 +154,7 @@ export class SubscriptionManager {
private async _updatePushSubscriptionModelWithRawSubscription(
rawPushSubscription: RawPushSubscription,
) {
console.log('updatePushSubscriptionModelWithRawSubscription', {
rawPushSubscription,
});
const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel();
console.log('pushModel', { pushModel });

// EventHelper checkAndTriggerSubscriptionChanged is called before this function when permission is granted and so
// it will save the push token/id to the database so we don't need to save the token afer generating
Expand All @@ -176,12 +173,11 @@ export class SubscriptionManager {

// for legacy safari push, switch to new format (e.g. old token 'ebsm3...' to -> https://web.push.apple.com/... with populated web_auth and web_p256)
if (pushModel.type === SubscriptionType.SafariLegacyPush) {
if (!window.Notification) return;
if (!Environment.useSafariVapidPush()) return;
await Database.setTokenAndId({
token: serializedSubscriptionRecord.token,
id: pushModel.id,
});
pushModel.setProperty('type', SubscriptionType.SafariPush);
}

// update existing push subscription model
Expand Down
2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
define: {
__API_ORIGIN__: JSON.stringify('onesignal.com'),
__API_TYPE__: JSON.stringify('staging'),
__API_TYPE__: JSON.stringify('production'),
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the purpose of this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just for the tests and will affect the api url is. It doesnt matter too much in this case since we use msw to mock the requests.

__BUILD_ORIGIN__: JSON.stringify('onesignal.com'),
__BUILD_TYPE__: JSON.stringify('production'),
__IS_HTTPS__: JSON.stringify(true),
Expand Down
Loading