Skip to content

Commit

Permalink
feat(oidc-client): enhance service worker flexibility
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-chervet committed Nov 11, 2023
1 parent 9549b9a commit 9ff16f8
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 152 deletions.
2 changes: 2 additions & 0 deletions packages/oidc-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ const configuration = {
},
refresh_time_before_tokens_expiration_in_second: Number, // default is 120 seconds
service_worker_relative_url: String,
service_worker_keep_alive_path: String, // default is "/"
service_worker_only: Boolean, // default false
service_worker_activate: () => boolean, // you can take the control of the service worker default activation which use user agent string
service_worker_update_require_callback: (registration:any, stopKeepAlive:Function) => Promise<void>, // callback called when service worker need to be updated, you can take the control of the update process
extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server (more info: https://github.com/openid/AppAuth-JS)
token_request_extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server during token request (more info: https://github.com/openid/AppAuth-JS)
Expand Down
2 changes: 1 addition & 1 deletion packages/oidc-client/src/iniWorker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect,it } from 'vitest';

import { excludeOs, getOperatingSystem } from './initWorker';
import { excludeOs, getOperatingSystem } from './initWorkerOption';

describe('initWorker test Suite', () => {

Expand Down
156 changes: 16 additions & 140 deletions packages/oidc-client/src/initWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,118 +4,17 @@ import { OidcConfiguration } from './types.js';
import codeVersion from './version.js';
import {ILOidcLocation} from "./location";

export const getOperatingSystem = (navigator) => {
const nVer = navigator.appVersion;
const nAgt = navigator.userAgent;
const unknown = '-';
// system
let os = unknown;
const clientStrings = [
{ s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
{ s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
{ s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
{ s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
{ s: 'Windows Vista', r: /Windows NT 6.0/ },
{ s: 'Windows Server 2003', r: /Windows NT 5.2/ },
{ s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
{ s: 'Windows 2000', r: /(Windows NT 5.0|Windows 2000)/ },
{ s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/ },
{ s: 'Windows 98', r: /(Windows 98|Win98)/ },
{ s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/ },
{ s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ },
{ s: 'Windows CE', r: /Windows CE/ },
{ s: 'Windows 3.11', r: /Win16/ },
{ s: 'Android', r: /Android/ },
{ s: 'Open BSD', r: /OpenBSD/ },
{ s: 'Sun OS', r: /SunOS/ },
{ s: 'Chrome OS', r: /CrOS/ },
{ s: 'Linux', r: /(Linux|X11(?!.*CrOS))/ },
{ s: 'iOS', r: /(iPhone|iPad|iPod)/ },
{ s: 'Mac OS X', r: /Mac OS X/ },
{ s: 'Mac OS', r: /(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
{ s: 'QNX', r: /QNX/ },
{ s: 'UNIX', r: /UNIX/ },
{ s: 'BeOS', r: /BeOS/ },
{ s: 'OS/2', r: /OS\/2/ },
{ s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ },
];
for (const id in clientStrings) {
const cs = clientStrings[id];
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}

let osVersion = unknown;

if (/Windows/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
os = 'Windows';
}

switch (os) {
case 'Mac OS':
case 'Mac OS X':
case 'Android':
osVersion = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._\d]+)/.exec(nAgt)[1];
break;

case 'iOS': {
const osVersionArray = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
osVersion = osVersionArray[1] + '.' + osVersionArray[2] + '.' + (parseInt(osVersionArray[3]) | 0);
break;
}
}
return {
os,
osVersion,
};
};

function getBrowser() {
const ua = navigator.userAgent; let tem;
let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'ie', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR|Edge\/(\d+)/);

if (tem != null) {
let version = tem[1];
if (!version) {
const splits = ua.split(tem[0] + '/');
if (splits.length > 1) {
version = splits[1];
}
}

return { name: 'opera', version };
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0].toLowerCase(),
version: M[1],
};
}

let keepAliveServiceWorkerTimeoutId = null;

let keepAliveController;
export const sleepAsync = (milliseconds) => {
return new Promise(resolve => timer.setTimeout(resolve, milliseconds));
};

let keepAliveController ;
const keepAlive = () => {
const keepAlive = (service_worker_keep_alive_path='/') => {
try {
const operatingSystem = getOperatingSystem(navigator);
const minSleepSeconds = operatingSystem.os === 'Android' ? 240 : 150;
const minSleepSeconds = 150;
keepAliveController = new AbortController();
const promise = fetch(`/OidcKeepAliveServiceWorker.json?minSleepSeconds=${minSleepSeconds}`, { signal: keepAliveController.signal });
const promise = fetch(`${service_worker_keep_alive_path}OidcKeepAliveServiceWorker.json?minSleepSeconds=${minSleepSeconds}`, { signal: keepAliveController.signal });
promise.catch(error => { console.log(error); });
sleepAsync(minSleepSeconds * 1000).then(keepAlive);
} catch (error) { console.log(error); }
Expand All @@ -127,8 +26,8 @@ const stopKeepAlive = () => {
}
};

const isServiceWorkerProxyActiveAsync = () => {
return fetch('/OidcKeepAliveServiceWorker.json', {
const isServiceWorkerProxyActiveAsync = (service_worker_keep_alive_path='/') => {
return fetch(`${service_worker_keep_alive_path}OidcKeepAliveServiceWorker.json`, {
headers: {
'oidc-vanilla': 'true',
},
Expand All @@ -145,17 +44,8 @@ export const defaultServiceWorkerUpdateRequireCallback = (location:ILOidcLocatio
await sleepAsync(2000);
location.reload();
}


export const excludeOs = (operatingSystem) => {
if (operatingSystem.os === 'iOS' && operatingSystem.osVersion.startsWith('12')) {
return true;
}
if (operatingSystem.os === 'Mac OS X' && operatingSystem.osVersion.startsWith('10_15_6')) {
return true;
}
return false;
};


const sendMessageAsync = (registration) => (data) : Promise<any> => {
return new Promise(function(resolve, reject) {
Expand All @@ -171,28 +61,14 @@ const sendMessageAsync = (registration) => (data) : Promise<any> => {
});
};

export const initWorkerAsync = async(serviceWorkerRelativeUrl, configurationName) => {
export const initWorkerAsync = async(configuration, configurationName) => {

const serviceWorkerRelativeUrl = configuration.service_worker_relative_url;
if (typeof window === 'undefined' || typeof navigator === 'undefined' || !navigator.serviceWorker || !serviceWorkerRelativeUrl) {
return null;
}
const { name, version } = getBrowser();
if (name === 'chrome' && parseInt(version) <= 70) {
return null;
}
if (name === 'opera') {
if (!version) {
return null;
}
if (parseInt(version.split('.')[0]) < 80) {
return null;
}
}
if (name === 'ie') {
return null;
}

const operatingSystem = getOperatingSystem(navigator);
if (excludeOs(operatingSystem)) {

if(configuration.service_worker_activate() === false) {
return null;
}

Expand Down Expand Up @@ -232,10 +108,10 @@ export const initWorkerAsync = async(serviceWorkerRelativeUrl, configurationName
return { tokens: parseOriginalTokens(result.tokens, null, oidcConfiguration.token_renew_mode), status: result.status };
};

const startKeepAliveServiceWorker = () => {
const startKeepAliveServiceWorker = (service_worker_keep_alive_path='/') => {
if (keepAliveServiceWorkerTimeoutId == null) {
keepAliveServiceWorkerTimeoutId = 'not_null';
keepAlive();
keepAlive(service_worker_keep_alive_path);
}
};

Expand Down Expand Up @@ -336,8 +212,8 @@ export const initWorkerAsync = async(serviceWorkerRelativeUrl, configurationName
return {
clearAsync,
initAsync,
startKeepAliveServiceWorker,
isServiceWorkerProxyActiveAsync,
startKeepAliveServiceWorker : () => startKeepAliveServiceWorker(configuration.service_worker_keep_alive_path),
isServiceWorkerProxyActiveAsync : () => isServiceWorkerProxyActiveAsync(configuration.service_worker_keep_alive_path),
setSessionStateAsync,
getSessionStateAsync,
setNonceAsync,
Expand Down
133 changes: 133 additions & 0 deletions packages/oidc-client/src/initWorkerOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {ServiceWorkerActivate} from "./types";

export const excludeOs = (operatingSystem) => {
if (operatingSystem.os === 'iOS' && operatingSystem.osVersion.startsWith('12')) {
return true;
}
if (operatingSystem.os === 'Mac OS X' && operatingSystem.osVersion.startsWith('10_15_6')) {
return true;
}
return false;
};
export const getOperatingSystem = (navigator) => {
const nVer = navigator.appVersion;
const nAgt = navigator.userAgent;
const unknown = '-';
// system
let os = unknown;
const clientStrings = [
{ s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
{ s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
{ s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
{ s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
{ s: 'Windows Vista', r: /Windows NT 6.0/ },
{ s: 'Windows Server 2003', r: /Windows NT 5.2/ },
{ s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
{ s: 'Windows 2000', r: /(Windows NT 5.0|Windows 2000)/ },
{ s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/ },
{ s: 'Windows 98', r: /(Windows 98|Win98)/ },
{ s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/ },
{ s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ },
{ s: 'Windows CE', r: /Windows CE/ },
{ s: 'Windows 3.11', r: /Win16/ },
{ s: 'Android', r: /Android/ },
{ s: 'Open BSD', r: /OpenBSD/ },
{ s: 'Sun OS', r: /SunOS/ },
{ s: 'Chrome OS', r: /CrOS/ },
{ s: 'Linux', r: /(Linux|X11(?!.*CrOS))/ },
{ s: 'iOS', r: /(iPhone|iPad|iPod)/ },
{ s: 'Mac OS X', r: /Mac OS X/ },
{ s: 'Mac OS', r: /(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
{ s: 'QNX', r: /QNX/ },
{ s: 'UNIX', r: /UNIX/ },
{ s: 'BeOS', r: /BeOS/ },
{ s: 'OS/2', r: /OS\/2/ },
{ s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ },
];
for (const id in clientStrings) {
const cs = clientStrings[id];
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}

let osVersion = unknown;

if (/Windows/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
os = 'Windows';
}

switch (os) {
case 'Mac OS':
case 'Mac OS X':
case 'Android':
osVersion = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._\d]+)/.exec(nAgt)[1];
break;

case 'iOS': {
const osVersionArray = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
osVersion = osVersionArray[1] + '.' + osVersionArray[2] + '.' + (parseInt(osVersionArray[3]) | 0);
break;
}
}
return {
os,
osVersion,
};
};

function getBrowser() {
const ua = navigator.userAgent; let tem;
let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'ie', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR|Edge\/(\d+)/);

if (tem != null) {
let version = tem[1];
if (!version) {
const splits = ua.split(tem[0] + '/');
if (splits.length > 1) {
version = splits[1];
}
}

return { name: 'opera', version };
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0].toLowerCase(),
version: M[1],
};
}

export const activateServiceWorker : ServiceWorkerActivate = () : boolean =>{
const { name, version } = getBrowser();
if (name === 'chrome' && parseInt(version) <= 70) {
return false;
}
if (name === 'opera') {
if (!version) {
return false;
}
if (parseInt(version.split('.')[0]) < 80) {
return false;
}
}
if (name === 'ie') {
return false;
}

const operatingSystem = getOperatingSystem(navigator);
if (excludeOs(operatingSystem)) {
return false;
}
return true;
}
4 changes: 2 additions & 2 deletions packages/oidc-client/src/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const defaultLoginAsync = (configurationName:string, configuration:OidcCo
extraFinal.nonce = generateRandom(12);
}
const nonce = { nonce: extraFinal.nonce };
const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, configurationName);
const serviceWorker = await initWorkerAsync(configuration, configurationName);
const oidcServerConfiguration = await initAsync(configuration.authority, configuration.authority_configuration);
let storage;
if (serviceWorker) {
Expand Down Expand Up @@ -87,7 +87,7 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
const href = oidc.location.getCurrentHref();
const queryParams = getParseQueryStringFromLocation(href);
const sessionState = queryParams.session_state;
const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, oidc.configurationName);
const serviceWorker = await initWorkerAsync(configuration, oidc.configurationName);
let storage;
let nonceData;
let getLoginParams;
Expand Down
2 changes: 1 addition & 1 deletion packages/oidc-client/src/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const destroyAsync = (oidc) => async (status) => {
if (oidc.checkSessionIFrame) {
oidc.checkSessionIFrame.stop();
}
const serviceWorker = await initWorkerAsync(oidc.configuration.service_worker_relative_url, oidc.configurationName);
const serviceWorker = await initWorkerAsync(oidc.configuration, oidc.configurationName);
if (!serviceWorker) {
const session = initSession(oidc.configurationName, oidc.configuration.storage);
await session.clearAsync(status);
Expand Down
Loading

0 comments on commit 9ff16f8

Please sign in to comment.