From dce1e9ffb43ee0fba4bd97ef14091d29c570dd19 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Tue, 29 Nov 2022 18:48:02 +0200 Subject: [PATCH] fix(shared): Make createWorkerTimers fallback to native timers if CSP blocks blob worker-src --- .../utils/workerTimers/createWorkerTimers.ts | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/shared/src/utils/workerTimers/createWorkerTimers.ts b/packages/shared/src/utils/workerTimers/createWorkerTimers.ts index 761c339d72e..2c8f820eb0c 100644 --- a/packages/shared/src/utils/workerTimers/createWorkerTimers.ts +++ b/packages/shared/src/utils/workerTimers/createWorkerTimers.ts @@ -10,43 +10,57 @@ import { // @ts-ignore import pollerWorkerSource from './workerTimers.worker'; -const createWebWorker = (source: string, opts: ConstructorParameters[1] = {}) => { - const workerFile = new Blob([source]); - const workerScript = globalThis.URL.createObjectURL(workerFile); - return new Worker(workerScript, opts); -}; - -export const createWorkerTimers = () => { +const createWebWorker = (source: string, opts: ConstructorParameters[1] = {}): Worker | null => { if (typeof Worker === 'undefined') { - const setTimeout = globalThis.setTimeout as WorkerSetTimeout; - const setInterval = globalThis.setInterval as WorkerSetTimeout; - const clearTimeout = globalThis.clearTimeout as WorkerClearTimeout; - const clearInterval = globalThis.clearInterval as WorkerClearTimeout; - return { setTimeout, setInterval, clearTimeout, clearInterval, cleanup: noop }; + return null; + } + + try { + const blob = new Blob([source], { type: 'application/javascript; charset=utf-8' }); + const workerScript = globalThis.URL.createObjectURL(blob); + return new Worker(workerScript, opts); + } catch (e) { + console.warn('Clerk: Cannot create worker from blob. Consider adding worker-src blob:; to your CSP'); + return null; } +}; + +const fallbackTimers = () => { + const setTimeout = globalThis.setTimeout.bind(globalThis) as WorkerSetTimeout; + const setInterval = globalThis.setInterval.bind(globalThis) as WorkerSetTimeout; + const clearTimeout = globalThis.clearTimeout.bind(globalThis) as WorkerClearTimeout; + const clearInterval = globalThis.clearInterval.bind(globalThis) as WorkerClearTimeout; + return { setTimeout, setInterval, clearTimeout, clearInterval, cleanup: noop }; +}; +export const createWorkerTimers = () => { let id = 0; - const callbacks = new Map(); const generateId = () => id++; - let worker: Worker | undefined; - let post: ((p: WorkerTimerEvent) => void) | undefined; + const callbacks = new Map(); + const post = (w: Worker | null, p: WorkerTimerEvent) => w?.postMessage(p); + const handleMessage = (e: MessageEvent) => { + callbacks.get(e.data.id)?.(); + }; + + let worker = createWebWorker(pollerWorkerSource, { name: 'clerk-timers' }); + worker?.addEventListener('message', handleMessage); + + if (!worker) { + return fallbackTimers(); + } const init = () => { if (!worker) { worker = createWebWorker(pollerWorkerSource, { name: 'clerk-timers' }); - post = (p: WorkerTimerEvent) => worker?.postMessage(p); - worker.addEventListener('message', e => { - const data = e.data as WorkerTimerResponseEvent; - callbacks.get(data.id)?.(); - }); + worker?.addEventListener('message', handleMessage); } }; const cleanup = () => { if (worker) { worker.terminate(); - worker = undefined; - post = undefined; + worker = null; + callbacks.clear(); } }; @@ -54,7 +68,7 @@ export const createWorkerTimers = () => { init(); const id = generateId(); callbacks.set(id, cb); - post?.({ type: 'setTimeout', id, ms }); + post(worker, { type: 'setTimeout', id, ms }); return id; }; @@ -62,20 +76,20 @@ export const createWorkerTimers = () => { init(); const id = generateId(); callbacks.set(id, cb); - post?.({ type: 'setInterval', id, ms }); + post(worker, { type: 'setInterval', id, ms }); return id; }; const clearTimeout: WorkerClearTimeout = id => { init(); callbacks.delete(id); - post?.({ type: 'clearTimeout', id }); + post(worker, { type: 'clearTimeout', id }); }; const clearInterval: WorkerClearTimeout = id => { init(); callbacks.delete(id); - post?.({ type: 'clearInterval', id }); + post(worker, { type: 'clearInterval', id }); }; return { setTimeout, setInterval, clearTimeout, clearInterval, cleanup };