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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
test:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
container:
image: python:2.7.18-buster

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:14.21.1
FROM node:22
WORKDIR /sdk
COPY package.json .
RUN yarn
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"bowser": "github:OneSignal/bowser#fix-android8-opr6-build-detection",
"jsdom": "^9.12.0",
"jsonp": "github:OneSignal/jsonp#onesignal",
"node-sass": "^4.9.0",
"npm-css": "https://registry.npmjs.org/npm-css/-/npm-css-0.2.3.tgz",
"postcss-discard-comments": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz",
"postcss-filter-plugins": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz",
"sass": "^1.90.0",
"tslib": "^1.9.0",
"validator": "https://registry.npmjs.org/validator/-/validator-6.0.0.tgz"
},
Expand Down Expand Up @@ -40,7 +40,7 @@
"jest": "jest --coverage"
},
"config": {
"sdkVersion": "151606"
"sdkVersion": "151607"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -88,7 +88,7 @@
"nyc": "^11.1.0",
"postcss-loader": "^2.0.6",
"prettier": "^2.7.1",
"sass-loader": "^6.0.6",
"sass-loader": "8.0.2",
"sinon": "^2.4.1",
"svgo": "^0.7.2",
"text-encoding": "^0.6.4",
Expand All @@ -98,7 +98,7 @@
"uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "^4.3.0",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-cli": "^2.0.13"
"webpack-cli": "3.3.12"
},
"ava": {
"extensions": [
Expand Down Expand Up @@ -150,5 +150,6 @@
"maxSize": "9 kB",
"compression": "gzip"
}
]
}
],
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
4 changes: 2 additions & 2 deletions src/libraries/WorkerMessenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class WorkerMessenger {
Log.debug(`(${location.origin}) [Worker Messenger] Page is now listening for messages.`);
}

onWorkerMessageReceivedFromPage(event: ServiceWorkerMessageEvent) {
onWorkerMessageReceivedFromPage(event: ExtendableMessageEvent) {
const data: WorkerMessengerMessage = event.data;

/* If this message doesn't contain our expected fields, discard the message */
Expand Down Expand Up @@ -235,7 +235,7 @@ export class WorkerMessenger {
message topic. If no one is listening to the message, it is discarded;
otherwise, the listener callback is executed.
*/
onPageMessageReceivedFromServiceWorker(event: ServiceWorkerMessageEvent) {
onPageMessageReceivedFromServiceWorker(event: ExtendableMessageEvent) {
const data: WorkerMessengerMessage = event.data;

/* If this message doesn't contain our expected fields, discard the message */
Expand Down
35 changes: 34 additions & 1 deletion src/service-worker/ServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { NotificationReceived, NotificationClicked } from "../models/Notificatio
import { cancelableTimeout } from "../helpers/sw/CancelableTimeout";
import { DeviceRecord } from '../models/DeviceRecord';
import { awaitableTimeout } from "../utils/AwaitableTimeout";
import { AppConfig } from "src/models/AppConfig";

declare var self: ServiceWorkerGlobalScope & OSServiceWorkerFields;

Expand Down Expand Up @@ -156,6 +157,11 @@ export class ServiceWorker {
return appId;
}

static async getAppConfig(): Promise<AppConfig> {
const appId = await ServiceWorker.getAppId();
return await ConfigHelper.getAppConfig({ appId }, OneSignalApiSW.downloadServerAppConfig);
}

static setupMessageListeners() {
ServiceWorker.workerMessenger.on(WorkerMessengerCommand.WorkerVersion, _ => {
Log.debug('[Service Worker] Received worker version message.');
Expand Down Expand Up @@ -750,8 +756,35 @@ export class ServiceWorker {

// Use the user-provided default URL if one exists
const { defaultNotificationUrl: dbDefaultNotificationUrl } = await Database.getAppState();
if (dbDefaultNotificationUrl)
if (dbDefaultNotificationUrl) {
launchUrl = dbDefaultNotificationUrl;
}
else {
// There was an issue with legacy HTTP integrations where the defaultNotificationUrl
// was never saved to the DB. To account for this, we try to get the default URL
// from the app config on notification click and save it to the DB for future use.
// Choosing between notification received and notification clicked to do this logic,
// we decided on notification clicked to avoid doing extra api call when the user
// may never click the notification.
try {
const config = await ServiceWorker.getAppConfig();
const defaultNotificationUrlFromConfig = config.origin;
if (defaultNotificationUrlFromConfig) {
launchUrl = defaultNotificationUrlFromConfig;

if (!dbDefaultNotificationUrl) {
// intentionally not awaiting this promise to not block notification click handling
Database.setAppState({ defaultNotificationUrl: defaultNotificationUrlFromConfig }).catch(e => {
Log.error("Failed to save default notification url to db", e);
});
}
}
} catch (e) {
Log.error("Failed to get app config to determine default notification url", e);
}


}

// If the user clicked an action button, use the URL provided by the action button
// Unless the action button URL is null
Expand Down
4 changes: 2 additions & 2 deletions src/services/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export default class Database {
return state;
}

async setAppState(appState: AppState) {
async setAppState(appState: Partial<AppState>) {
if (appState.defaultNotificationUrl)
await this.put("Options", { key: "defaultUrl", value: appState.defaultNotificationUrl });
if (appState.defaultNotificationTitle || appState.defaultNotificationTitle === "")
Expand Down Expand Up @@ -525,7 +525,7 @@ export default class Database {
return await Database.singletonInstance.getServiceWorkerState();
}

static async setAppState(appState: AppState) {
static async setAppState(appState: Partial<AppState>) {
return await Database.singletonInstance.setAppState(appState);
}

Expand Down
4 changes: 2 additions & 2 deletions test/support/mocks/service-workers/models/MockClients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export class MockClients implements Clients {
return Promise.resolve(client || null);
}

async matchAll(_options?: ClientQueryOptions): Promise<Client[]> {
return this.clients;
async matchAll<T extends ClientQueryOptions>(options?: T): Promise<ReadonlyArray<T["type"] extends "window" ? WindowClient : Client>> {
return Object.freeze(this.clients) as ReadonlyArray<T["type"] extends "window" ? WindowClient : Client>;
}

async openWindow(_url: string): Promise<WindowClient | null> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class MockPushManager implements PushManager {
return this.subscription;
}

public async permissionState(_options?: PushSubscriptionOptionsInit): Promise<PushPermissionState> {
public async permissionState(_options?: PushSubscriptionOptionsInit): Promise<PermissionState> {
return "granted";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class MockServiceWorker implements ServiceWorker {
throw new NotImplementedError();
}

postMessage(message: any, transfer: Array<Transferable> | PostMessageOptions): void {
postMessage(message: any, transfer: Array<Transferable> | any): void {
}

removeEventListener<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export class MockServiceWorkerGlobalScope implements ServiceWorkerGlobalScope {
return this.registration;
}

readonly fonts: FontFaceSet;
readonly crossOriginIsolated: boolean;
reportError: (e: any) => void;
structuredClone: (input: any) => any;

readonly caches: CacheStorage;
readonly clients: Clients = new MockClients();
readonly console: Console;
Expand Down Expand Up @@ -51,7 +56,7 @@ export class MockServiceWorkerGlobalScope implements ServiceWorkerGlobalScope {
onrejectionhandled: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null = null;
onpush: ((this: ServiceWorkerGlobalScope, ev: PushEvent) => any) | null = null;
onpushsubscriptionchange: ((this: ServiceWorkerGlobalScope, ev: PushSubscriptionChangeEvent) => any) | null = null;
onsync: ((this: ServiceWorkerGlobalScope, ev: SyncEvent) => any) | null = null;
onsync: ((this: ServiceWorkerGlobalScope, ev: Event) => any) | null = null;
onunhandledrejection: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null = null;
queueMicrotask(callback: VoidFunction): void {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export class MockServiceWorkerRegistration implements ServiceWorkerRegistration
waiting: ServiceWorker | null;
onupdatefound: ((this: ServiceWorkerRegistration, ev: Event) => any) | null;
readonly pushManager: PushManager;
readonly sync: SyncManager;
navigationPreload: NavigationPreloadManager;
updateViaCache: ServiceWorkerUpdateViaCache;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export class MockWorkerNavigator implements WorkerNavigator {
public readonly userAgent: string
) {
}
mediaCapabilities: MediaCapabilities;
clearAppBadge(): Promise<void> {
throw new Error("Method not implemented.");
}
setAppBadge(contents?: unknown): Promise<void> {
throw new Error("Method not implemented.");
}
locks: LockManager;

readonly hardwareConcurrency: number;
readonly onLine: boolean;
Expand Down
41 changes: 41 additions & 0 deletions test/unit/context/sw/ServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import OneSignalUtils from '../../../../src/utils/OneSignalUtils';
import { setupFakePlayerId } from '../../../support/tester/utils';
import * as awaitableTimeout from '../../../../src/utils/AwaitableTimeout';
import { NockOneSignalHelper } from '../../../../test/support/tester/NockOneSignalHelper';
import { ConfigIntegrationKind } from '../../../../src/models/AppConfig';

declare var self: MockServiceWorkerGlobalScope;

Expand Down Expand Up @@ -260,6 +261,46 @@ test('onNotificationClicked - notification PUT Before openWindow', async t => {
t.deepEqual(callOrder, ["notificationPut", "openWindow"]);
});

test('onNotificationClicked - get web config before open if empty in database', async t => {
await Database.remove("Options", "defaultUrl");

// Assert the URL is empty in the database
const urlBefore = await Database.get("Options", "defaultUrl");
t.true(urlBefore === undefined || urlBefore === null);

const serverConfig = TestEnvironment.getFakeServerAppConfig(ConfigIntegrationKind.Custom, false, null, appConfig.appId);
t.is(serverConfig.config.origin, "http://localhost:3000");
nock("https://api.onesignal.com")
.get(/sync\/.*\/web/) // <-- wildcard: match ANY app ID
.reply(200, (uri, body) => {
console.log("GET /sync called with:", uri);
return serverConfig;
});

const notificationId = Random.getRandomUuid();

const callOrder: string[] = [];
sandbox.stub(self.clients, "openWindow", function() {
callOrder.push("openWindow");
});

nock("https://onesignal.com")
.put(`/api/v1/notifications/${notificationId}`)
.reply(200, (_uri: string, _requestBody: string) => {
callOrder.push("notificationPut");
return { success: true };
});

const notificationEvent = mockNotificationNotificationEventInit(notificationId);
await OSServiceWorker.onNotificationClicked(notificationEvent);

t.deepEqual(callOrder, ["notificationPut", "openWindow"]);

// Assert the URL is set after the handler runs
const urlAfter = await Database.get("Options", "defaultUrl");
t.is(urlAfter, "http://localhost:3000");
});

/***************************************************
* sendConfirmedDelivery()
****************************************************/
Expand Down
2 changes: 1 addition & 1 deletion test/unit/managers/SubscriptionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ test(

// Set the initial datetime
timemachine.config({
timestamp: dateString
timestamp: Date.parse(dateString)
});

const initialSubscriptionOptions: PushSubscriptionOptions = {
Expand Down
14 changes: 14 additions & 0 deletions typings/globals/serviceworker.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
declare module 'ServiceWorker' {
// eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any
var OneSignal: any;
export = OneSignal;
}

interface PushSubscriptionChangeEvent extends ExtendableEvent {
readonly oldSubscription: PushSubscription | null;
readonly newSubscription: PushSubscription | null;
}

/* eslint-disable no-var */
declare var PushSubscriptionChangeEvent: {
prototype: PushSubscriptionChangeEvent;
new(): PushSubscriptionChangeEvent;
};
/* eslint-enable no-var */

Loading
Loading