Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ServiceWorker Page Control Requirement #745

Merged
merged 26 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a84b101
Setup sub-root sw scope in dev sandbox
jkasten2 Feb 12, 2021
57e1833
rm page control awaiting on sw install
jkasten2 Feb 12, 2021
f4e32c2
rm waitUntilWorkerControlsPage
jkasten2 Feb 12, 2021
18fe8ae
rm isWorkerControllingPage
jkasten2 Feb 12, 2021
a2838cc
mv getRegistration to ServicWorkerManager
jkasten2 Feb 12, 2021
321acdb
Update directPostMessageToSW to use the correct sw
jkasten2 Feb 12, 2021
6cafaed
Fixed tests from getRegistration change
jkasten2 Feb 12, 2021
19f6214
Replaced serviceWorker.ready with getRegistration
jkasten2 Feb 12, 2021
99e2955
rm Bypassed serviceworker state
jkasten2 Feb 12, 2021
932ef35
Updated includeUncontrolled to true
jkasten2 Feb 12, 2021
d171014
Wrong orgin in HTTP header if non-root scope
jkasten2 Feb 12, 2021
c069c66
Fix SW null somtimes when trying to postMessage
jkasten2 Feb 12, 2021
178e74e
rm skipWaiting and claim
jkasten2 Feb 12, 2021
090fb7f
Changed default open URL to the root domain
jkasten2 Feb 17, 2021
7840d1b
rm `ServiceWorkerActiveState.Installing`
jkasten2 Feb 17, 2021
1fa9118
rm `!active` sw detection for thirdparty
jkasten2 Feb 17, 2021
ef04807
rm listenIfPageUncontrolled
jkasten2 Feb 17, 2021
403d118
Cleaned up comments with "control.*page"
jkasten2 Feb 17, 2021
8d69de5
Cleaned up duplicated ServiceWorkerHelper name
jkasten2 Feb 17, 2021
42bec5f
Replaced all page `sw.active` calls
jkasten2 Feb 17, 2021
57f60b2
rm throw from getAvailableServiceWorker
jkasten2 Feb 18, 2021
0ad03d8
Tests for banning sw page control APIs
jkasten2 Feb 19, 2021
b3de742
Added ServiceWorkerUtilHelper tests
jkasten2 Feb 19, 2021
0b6b420
Moved OneSignal.context access down a level
jkasten2 Feb 19, 2021
46fca9a
rm sw checks before establishServiceWorkerChannel
jkasten2 Feb 19, 2021
4516c35
Small formating and naming cleanup
jkasten2 Feb 19, 2021
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
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