Skip to content

Commit

Permalink
Merge pull request #745 from OneSignal/feat/remove_sw_page_control_re…
Browse files Browse the repository at this point in the history
…quirement

Remove ServiceWorker Page Control Requirement
  • Loading branch information
jkasten2 authored Feb 19, 2021
2 parents db4c4be + 4516c35 commit 76a7d5d
Show file tree
Hide file tree
Showing 30 changed files with 350 additions and 361 deletions.
7 changes: 7 additions & 0 deletions express_webpack/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<html lang="en">
<script src="/sdks/Dev-OneSignalSDK.js" async=""></script>
<script>
const SERVICE_WORKER_PATH = "push/onesignal/";

function getUrlQueryParam(name) {
var url = window.location.href;
// This is just to avoid case sensitiveness
Expand All @@ -14,9 +16,14 @@
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}

const appId = getUrlQueryParam('app_id');
var OneSignal = window.OneSignal || [];
OneSignal.push(function() {
OneSignal.SERVICE_WORKER_PARAM = { scope: "/" + SERVICE_WORKER_PATH };
OneSignal.SERVICE_WORKER_UPDATER_PATH = SERVICE_WORKER_PATH + "OneSignalSDKUpdaterWorker.js";
OneSignal.SERVICE_WORKER_PATH = SERVICE_WORKER_PATH + "OneSignalSDKWorker.js";

OneSignal.init({
appId,
notifyButton: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
importScripts("https://localhost:4001/sdks/Dev-OneSignalSDKWorker.js");
1 change: 1 addition & 0 deletions express_webpack/push/onesignal/OneSignalSDKWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
importScripts("https://localhost:4001/sdks/Dev-OneSignalSDKWorker.js");
4 changes: 2 additions & 2 deletions express_webpack/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ app.get('/', (req, res) => {
})

app.get('/sdks/:file', (req, res) => {
res.sendFile(SDK_FILES+req.params.file);
res.sendFile(SDK_FILES + req.params.file);
});

app.get('/:file', (req, res) => {
res.sendFile(SDK_FILES+req.params.file);
res.sendFile(req.params.file);
});

https.createServer(options, app).listen(4001, () => console.log("express_webpack: listening on port 4001 (https)"));
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/ContextHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export class ContextHelper {
if (config.userConfig) {
if (config.userConfig.path) {
serviceWorkerManagerConfig.workerAPath =
new Path(`${config.userConfig.path}${envPrefix}${config.userConfig.serviceWorkerPath}`);
new Path(`${config.userConfig.path}${config.userConfig.serviceWorkerPath}`);
serviceWorkerManagerConfig.workerBPath =
new Path(`${config.userConfig.path}${envPrefix}${config.userConfig.serviceWorkerUpdaterPath}`);
new Path(`${config.userConfig.path}${config.userConfig.serviceWorkerUpdaterPath}`);
}
if (config.userConfig.serviceWorkerParam) {
serviceWorkerManagerConfig.registrationOptions = config.userConfig.serviceWorkerParam;
Expand Down
6 changes: 1 addition & 5 deletions src/helpers/InitHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import Log from '../libraries/Log';
import Environment from '../Environment';
import Bell from '../bell/Bell';
import { CustomLink } from '../CustomLink';
import { ServiceWorkerManager } from "../managers/ServiceWorkerManager";
import SubscriptionPopupHost from "../modules/frames/SubscriptionPopupHost";
import { OneSignalUtils } from "../utils/OneSignalUtils";
import { DeprecatedApiError, DeprecatedApiReason } from "../errors/DeprecatedApiError";
Expand Down Expand Up @@ -261,10 +260,7 @@ export default class InitHelper {
!await SdkEnvironment.isFrameContextInsecure()
) {
try {
const registration = await ServiceWorkerManager.getRegistration();
if (registration && registration.active) {
await OneSignal.context.serviceWorkerManager.establishServiceWorkerChannel();
}
await OneSignal.context.serviceWorkerManager.establishServiceWorkerChannel();
} catch (e) {
Log.error(e);
}
Expand Down
26 changes: 0 additions & 26 deletions src/helpers/ServiceWorkerHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Database from "../services/Database";
import { SerializedPushDeviceRecord, PushDeviceRecord } from "../models/PushDeviceRecord";
import { NotificationClicked } from "../models/Notification";
import { RawPushSubscription } from '../models/RawPushSubscription';
import PageServiceWorkerHelper from "./page/ServiceWorkerHelper";
import { OutcomesConfig } from "../models/Outcomes";
import OutcomesHelper from './shared/OutcomesHelper';
import { cancelableTimeout, CancelableTimeoutPromise } from './sw/CancelableTimeout';
Expand All @@ -17,10 +16,6 @@ import { OSServiceWorkerFields } from "../service-worker/types";
declare var self: ServiceWorkerGlobalScope & OSServiceWorkerFields;

export default class ServiceWorkerHelper {
public static async getRegistration(): Promise<ServiceWorkerRegistration | null | undefined> {
return await PageServiceWorkerHelper.getRegistration();
}

public static getServiceWorkerHref(
workerState: ServiceWorkerActiveState,
config: ServiceWorkerManagerConfig): string {
Expand All @@ -33,16 +28,6 @@ export default class ServiceWorkerHelper {
workerState === ServiceWorkerActiveState.ThirdParty ||
workerState === ServiceWorkerActiveState.None)
workerFullPath = config.workerAPath.getFullPath();
else if (workerState === ServiceWorkerActiveState.Bypassed) {
/*
if the page is hard refreshed bypassing the cache, no service worker
will control the page.
It doesn't matter if we try to reinstall an existing worker; still no
service worker will control the page after installation.
*/
throw new InvalidStateError(InvalidStateReason.UnsupportedEnvironment);
}

return new URL(workerFullPath, OneSignalUtils.getBaseUrl()).href;
}
Expand Down Expand Up @@ -258,21 +243,10 @@ export enum ServiceWorkerActiveState {
* provided by user config).
*/
ThirdParty = '3rd Party',
/**
* A service worker is currently installing and we can't determine its final state yet. Wait until
* the service worker is finished installing by checking for a controllerchange property..
*/
Installing = 'Installing',
/**
* No service worker is installed.
*/
None = 'None',
/**
* A service worker is active but not controlling the page. This can occur if
* the page is hard-refreshed bypassing the cache, which also bypasses service
* workers.
*/
Bypassed = 'Bypassed',
/**
* Service workers are not supported in this environment. This status is used
* on HTTP pages where it isn't possible to know whether a service worker is
Expand Down
7 changes: 3 additions & 4 deletions src/helpers/SubscriptionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import LocalStorage from '../utils/LocalStorage';
import { SessionOrigin } from "../models/Session";
import MainHelper from "./MainHelper";
import { PushDeviceRecord } from "../models/PushDeviceRecord";
import PageServiceWorkerHelper from "./page/ServiceWorkerHelper";
import { EnvironmentInfo } from "../context/browser/models/EnvironmentInfo";
import { Browser } from "../context/browser/models/Browser";

Expand Down Expand Up @@ -162,8 +161,7 @@ export default class SubscriptionHelper {
return subscription;
}

static async getRawPushSubscriptionFromServiceWorkerRegistration(): Promise<RawPushSubscription | null> {
const registration = await PageServiceWorkerHelper.getRegistration();
static async getRawPushSubscriptionFromServiceWorkerRegistration(registration?: ServiceWorkerRegistration): Promise<RawPushSubscription | null> {
if (!registration) {
return null;
}
Expand Down Expand Up @@ -191,7 +189,8 @@ export default class SubscriptionHelper {
}

if (environmentInfo.isBrowserAndSupportsServiceWorkers) {
return await SubscriptionHelper.getRawPushSubscriptionFromServiceWorkerRegistration();
const registration = await OneSignal.context.serviceWorkerManager.getRegistration();
return await SubscriptionHelper.getRawPushSubscriptionFromServiceWorkerRegistration(registration);
}

return null;
Expand Down
15 changes: 0 additions & 15 deletions src/helpers/page/ServiceWorkerHelper.ts

This file was deleted.

24 changes: 24 additions & 0 deletions src/helpers/page/ServiceWorkerUtilHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Log from "../../libraries/Log";

export default class ServiceWorkerUtilHelper {
// Get the service worker based on a scope as a domain can have none to many service workers.
static async getRegistration(scope: string): Promise<ServiceWorkerRegistration | null | undefined> {
try {
return await navigator.serviceWorker.getRegistration(scope);
} catch (e) {
// This could be null in an HTTP context or error if the user doesn't accept cookies
Log.warn("[Service Worker Status] Error Checking service worker registration", scope, e);
return null;
}
}

// A ServiceWorkerRegistration will have a ServiceWorker in 1 of 3 states, get which ever is available.
static getAvailableServiceWorker(registration: ServiceWorkerRegistration): ServiceWorker | null {
const availableWorker = registration.active || registration.installing || registration.waiting;
// This never be null unless ServiceWorkerRegistration is pointing to a worker that is completely gone.
if (!availableWorker) {
Log.warn("Could not find an available ServiceWorker instance!");
}
return availableWorker;
}
}
110 changes: 24 additions & 86 deletions src/libraries/WorkerMessenger.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { InvalidArgumentError, InvalidArgumentReason } from '../errors/InvalidArgumentError';
import SdkEnvironment from '../managers/SdkEnvironment';
import { ServiceWorkerActiveState } from '../helpers/ServiceWorkerHelper';
import { WindowEnvironmentKind } from '../models/WindowEnvironmentKind';

import { Serializable } from '../models/Serializable';
import Environment from '../Environment';
import Log from './Log';
import { ContextSWInterface } from '../models/ContextSW';
import ServiceWorkerUtilHelper from '../helpers/page/ServiceWorkerUtilHelper';

/**
* NOTE: This file contains a mix of code that runs in ServiceWorker and Page contexts
Expand Down Expand Up @@ -119,12 +119,10 @@ export class WorkerMessenger {
}

/*
For pages:
Sends a postMessage() to the service worker controlling the page.
Waits until the service worker is controlling the page before sending the
message.
If running on a page context:
Sends a postMessage() to OneSignal's Serviceworker
If running in a ServiceWorker context:
Sends a postMessage() to the supplied windowClient
*/
async unicast(command: WorkerMessengerCommand, payload?: WorkerMessengerPayload, windowClient?: Client) {
const env = SdkEnvironment.getWindowEnv();
Expand All @@ -140,18 +138,28 @@ export class WorkerMessenger {
} as any);
}
} else {
if (!(await this.isWorkerControllingPage())) {
Log.debug("[Worker Messenger] The page is not controlled by the service worker yet. Waiting...", (<ServiceWorkerGlobalScope><any>self).registration);
}
await this.waitUntilWorkerControlsPage();
Log.debug(`[Worker Messenger] [Page -> SW] Unicasting '${command.toString()}' to service worker.`)
this.directPostMessageToSW(command, payload);
}
}

public directPostMessageToSW(command: WorkerMessengerCommand, payload?: WorkerMessengerPayload) {
public async directPostMessageToSW(command: WorkerMessengerCommand, payload?: WorkerMessengerPayload): Promise<void> {
Log.debug(`[Worker Messenger] [Page -> SW] Direct command '${command.toString()}' to service worker.`);
navigator.serviceWorker!.controller!.postMessage({

const workerRegistration = await this.context.serviceWorkerManager.getRegistration();
if (!workerRegistration) {
Log.error("`[Worker Messenger] [Page -> SW] Could not get ServiceWorkerRegistration to postMessage!");
return;
}

const availableWorker = ServiceWorkerUtilHelper.getAvailableServiceWorker(workerRegistration);
if (!availableWorker) {
Log.error("`[Worker Messenger] [Page -> SW] Could not get ServiceWorker to postMessage!");
return;
}

// The postMessage payload will still arrive at the SW even if it isn't active yet.
availableWorker.postMessage({
command: command,
payload: payload
});
Expand All @@ -161,13 +169,8 @@ export class WorkerMessenger {
* Due to https://github.com/w3c/ServiceWorker/issues/1156, listen() must
* synchronously add self.addEventListener('message') if we are running in the
* service worker.
*
* @param listenIfPageUncontrolled If true, begins listening for service
* worker messages even if the service worker does not control this page. This
* parameter is set to true on HTTPS iframes expecting service worker messages
* that live under an HTTP page.
*/
public async listen(listenIfPageUncontrolled?: boolean) {
public async listen() {
if (!Environment.supportsServiceWorkers())
return;

Expand All @@ -178,24 +181,13 @@ export class WorkerMessenger {
Log.debug('[Worker Messenger] Service worker is now listening for messages.');
}
else
await this.listenForPage(listenIfPageUncontrolled);
await this.listenForPage();
}

/**
* Listens for messages for the service worker.
*
* Waits until the service worker is controlling the page before listening for
* messages.
*/
private async listenForPage(listenIfPageUncontrolled?: boolean) {
if (!listenIfPageUncontrolled) {
if (!(await this.isWorkerControllingPage())) {
Log.debug(`(${location.origin}) [Worker Messenger] The page is not controlled by the service worker yet. Waiting...`, (<ServiceWorkerGlobalScope><any>self).registration);
}
await this.waitUntilWorkerControlsPage();
Log.debug(`(${location.origin}) [Worker Messenger] The page is now controlled by the service worker.`);
}

private async listenForPage() {
navigator.serviceWorker.addEventListener('message', this.onPageMessageReceivedFromServiceWorker.bind(this));
Log.debug(`(${location.origin}) [Worker Messenger] Page is now listening for messages.`);
}
Expand Down Expand Up @@ -301,58 +293,4 @@ export class WorkerMessenger {
this.replies.deleteAllListenerRecords();
}
}


/*
Service worker postMessage() communication relies on the property
navigator.serviceWorker.controller to be non-null. The controller property
references the active service worker controlling the page. Without this
property, there is no service worker to message.
The controller property is set when a service worker has successfully
registered, installed, and activated a worker, and when a page isn't loaded
in a hard refresh mode bypassing the cache.
It's possible for a service worker to take a second page load to be fully
activated.
*/
async isWorkerControllingPage(): Promise<boolean> {
const env = SdkEnvironment.getWindowEnv();

if (env === WindowEnvironmentKind.ServiceWorker)
return !!(<ServiceWorkerGlobalScope><any>self).registration.active;
else {
const workerState = await this.context.serviceWorkerManager.getActiveState();
return workerState === ServiceWorkerActiveState.WorkerA ||
workerState === ServiceWorkerActiveState.WorkerB;
}
}

/**
* For pages, waits until one of our workers is activated.
*
* For service workers, waits until the registration is active.
*/
async waitUntilWorkerControlsPage() {
return new Promise<void>(async resolve => {
if (await this.isWorkerControllingPage())
resolve();
else {
const env = SdkEnvironment.getWindowEnv();

if (env === WindowEnvironmentKind.ServiceWorker) {
self.addEventListener('activate', async (_e: Event) => {
if (await this.isWorkerControllingPage())
resolve();
});
}
else {
navigator.serviceWorker.addEventListener('controllerchange', async (_e: Event) => {
if (await this.isWorkerControllingPage())
resolve();
});
}
}
});
}
}
4 changes: 2 additions & 2 deletions src/managers/SdkEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default class SdkEnvironment {
}

// Will be null if there was an issue retrieving a status
const registrationResult = await ServiceWorkerHelper.getRegistration();
const registrationResult = await OneSignal.context.serviceWorkerManager.getRegistration();
return !registrationResult;
}

Expand All @@ -182,7 +182,7 @@ export default class SdkEnvironment {
if (Environment.isBrowser()) {
return window.location.origin;
} else if (typeof self !== "undefined" && typeof ServiceWorkerGlobalScope !== "undefined") {
return self.registration.scope;
return self.location.origin;
}
return "Unknown";
}
Expand Down
Loading

0 comments on commit 76a7d5d

Please sign in to comment.