From b849e999c7877e5c89bdff111d885512573d3752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Pe=CC=81rez=20Zamora?= Date: Fri, 26 Jan 2024 08:35:06 +0100 Subject: [PATCH] fix: loadScriptsOnMainThread breaks when using a regexp --- src/integration/index.ts | 2 ++ src/integration/snippet.ts | 11 ++----- src/lib/sandbox/read-main-platform.ts | 11 ++----- src/lib/types.ts | 7 ++++- src/lib/utils.ts | 41 +++++++++++++++++++++++++++ src/lib/web-worker/init-web-worker.ts | 17 ++++++----- src/lib/web-worker/worker-node.ts | 9 ++++-- src/lib/web-worker/worker-script.ts | 8 ++---- tests/unit/utils.ts | 3 +- 9 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/integration/index.ts b/src/integration/index.ts index 5c7d5749..85632806 100644 --- a/src/integration/index.ts +++ b/src/integration/index.ts @@ -16,6 +16,8 @@ export { SCRIPT_TYPE } from '../lib/utils'; export type { PartytownConfig, PartytownForwardProperty, + PartytownForwardPropertyWithSettings, + PartytownForwardPropertySettings, ApplyHook, GetHook, SetHook, diff --git a/src/integration/snippet.ts b/src/integration/snippet.ts index 842f9226..b059a112 100644 --- a/src/integration/snippet.ts +++ b/src/integration/snippet.ts @@ -1,17 +1,10 @@ +import { serializeConfig } from '../lib/utils'; import type { PartytownConfig } from '../lib/types'; export const createSnippet = (config: PartytownConfig | undefined | null, snippetCode: string) => { const { forward = [], ...filteredConfig } = config || {}; - const configStr = JSON.stringify(filteredConfig, (k, v) => { - if (typeof v === 'function') { - v = String(v); - if (v.startsWith(k + '(')) { - v = 'function ' + v; - } - } - return v; - }); + const configStr = serializeConfig(filteredConfig); return [ `!(function(w,p,f,c){`, diff --git a/src/lib/sandbox/read-main-platform.ts b/src/lib/sandbox/read-main-platform.ts index 7f59d95e..1293907d 100644 --- a/src/lib/sandbox/read-main-platform.ts +++ b/src/lib/sandbox/read-main-platform.ts @@ -5,6 +5,7 @@ import { isValidMemberName, len, noop, + serializeConfig, } from '../utils'; import { config, docImpl, libPath, mainWindow } from './main-globals'; import { @@ -61,15 +62,7 @@ export const readMainPlatform = () => { readImplementation('Node', textNode), ]; - const $config$ = JSON.stringify(config, (k, v) => { - if (typeof v === 'function') { - v = String(v); - if (v.startsWith(k + '(')) { - v = 'function ' + v; - } - } - return v; - }); + const $config$ = serializeConfig(config); const initWebWorkerData: InitWebWorkerData = { $config$, diff --git a/src/lib/types.ts b/src/lib/types.ts index 9613764c..517d5620 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -157,7 +157,7 @@ export type InterfaceMember = export interface WebWorkerContext { $asyncMsgTimer$?: any; - $config$: PartytownConfig; + $config$: PartytownInternalConfig; $importScripts$: (...urls: string[]) => void; $initWindowMedia$?: InitWindowMedia; $interfaces$: InterfaceInfo[]; @@ -522,6 +522,10 @@ export interface PartytownConfig { nonce?: string; } +export type PartytownInternalConfig = Omit & { + loadScriptsOnMainThread?: ['regexp' | 'string', string][]; +}; + export type PartytownForwardPropertySettings = { preserveBehavior?: boolean; }; @@ -715,6 +719,7 @@ export interface WorkerInstance { } export interface WorkerNode extends WorkerInstance, Node { + id?: string | undefined | null; type: string | undefined; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2ed2d59a..da23e419 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,9 +1,11 @@ import type { ApplyPath, MainWindow, + PartytownConfig, PartytownForwardProperty, PartytownForwardPropertySettings, PartytownForwardPropertyWithSettings, + PartytownInternalConfig, RandomId, StringIndexable, } from './types'; @@ -205,3 +207,42 @@ export const emptyObjectValue = (propertyName: string): [] | {} => { return {}; }; + +function escapeRegExp(input: string) { + return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +export function testIfMustLoadScriptOnMainThread( + config: PartytownInternalConfig, + value: string +): boolean { + return ( + config.loadScriptsOnMainThread + ?.map(([type, value]) => new RegExp(type === 'string' ? escapeRegExp(value) : value)) + .some((regexp) => regexp.test(value)) ?? false + ); +} + +export function serializeConfig(config: PartytownConfig) { + return JSON.stringify(config, (key, value) => { + if (typeof value === 'function') { + value = String(value); + if (value.startsWith(key + '(')) { + value = 'function ' + value; + } + } + if (key === 'loadScriptsOnMainThread') { + value = ( + value as Required['loadScriptsOnMainThread'] + ).map((scriptUrl) => + Array.isArray(scriptUrl) + ? scriptUrl + : [ + typeof scriptUrl === 'string' ? 'string' : 'regexp', + typeof scriptUrl === 'string' ? scriptUrl : scriptUrl.source, + ] + ) satisfies Required['loadScriptsOnMainThread']; + } + return value; + }); +} diff --git a/src/lib/web-worker/init-web-worker.ts b/src/lib/web-worker/init-web-worker.ts index 324ec060..801d160b 100644 --- a/src/lib/web-worker/init-web-worker.ts +++ b/src/lib/web-worker/init-web-worker.ts @@ -4,11 +4,12 @@ import { webWorkerlocalStorage, webWorkerSessionStorage, } from './worker-constants'; -import type { InitWebWorkerData } from '../types'; -import type { PartytownConfig } from '@builder.io/partytown/integration'; +import type { InitWebWorkerData, PartytownInternalConfig } from '../types'; export const initWebWorker = (initWebWorkerData: InitWebWorkerData) => { - const config: PartytownConfig = (webWorkerCtx.$config$ = JSON.parse(initWebWorkerData.$config$)); + const config: PartytownInternalConfig = (webWorkerCtx.$config$ = JSON.parse( + initWebWorkerData.$config$ + )); const locOrigin = initWebWorkerData.$origin$; webWorkerCtx.$importScripts$ = importScripts.bind(self); webWorkerCtx.$interfaces$ = initWebWorkerData.$interfaces$; @@ -24,9 +25,11 @@ export const initWebWorker = (initWebWorkerData: InitWebWorkerData) => { delete (self as any).postMessage; delete (self as any).WorkerGlobalScope; - (commaSplit('resolveUrl,get,set,apply') as any).map((configName: keyof PartytownConfig) => { - if (config[configName]) { - config[configName] = new Function('return ' + config[configName])(); + (commaSplit('resolveUrl,get,set,apply') as any).map( + (configName: keyof PartytownInternalConfig) => { + if (config[configName]) { + config[configName] = new Function('return ' + config[configName])(); + } } - }); + ); }; diff --git a/src/lib/web-worker/worker-node.ts b/src/lib/web-worker/worker-node.ts index baeb2ac2..9719d030 100644 --- a/src/lib/web-worker/worker-node.ts +++ b/src/lib/web-worker/worker-node.ts @@ -16,7 +16,12 @@ import { webWorkerCtx, WinIdKey, } from './worker-constants'; -import { defineConstructorName, SCRIPT_TYPE, SCRIPT_TYPE_EXEC } from '../utils'; +import { + defineConstructorName, + SCRIPT_TYPE, + SCRIPT_TYPE_EXEC, + testIfMustLoadScriptOnMainThread, +} from '../utils'; import { getInstanceStateValue } from './worker-state'; import { insertIframe, runScriptContent } from './worker-exec'; import { isScriptJsType } from './worker-script'; @@ -59,7 +64,7 @@ export const createNodeCstr = ( // @ts-ignore const scriptId = newNode.id; const loadOnMainThread = - scriptId && config.loadScriptsOnMainThread?.includes?.(scriptId); + scriptId && testIfMustLoadScriptOnMainThread(config, scriptId); if (loadOnMainThread) { setter(newNode, ['type'], 'text/javascript'); diff --git a/src/lib/web-worker/worker-script.ts b/src/lib/web-worker/worker-script.ts index 730f72dd..5492cfa3 100644 --- a/src/lib/web-worker/worker-script.ts +++ b/src/lib/web-worker/worker-script.ts @@ -1,4 +1,4 @@ -import { definePrototypePropertyDescriptor } from '../utils'; +import { definePrototypePropertyDescriptor, testIfMustLoadScriptOnMainThread } from '../utils'; import { getInstanceStateValue, setInstanceStateValue } from './worker-state'; import { getter, setter } from './worker-proxy'; import { HTMLSrcElementDescriptorMap } from './worker-src-element'; @@ -26,10 +26,8 @@ export const patchHTMLScriptElement = (WorkerHTMLScriptElement: any, env: WebWor setter(this, ['dataset', 'ptsrc'], orgUrl); } - if (this.type && config.loadScriptsOnMainThread) { - const shouldExecuteScriptViaMainThread = config.loadScriptsOnMainThread.some( - (scriptUrl) => new RegExp(scriptUrl).test(url) - ); + if (this.type) { + const shouldExecuteScriptViaMainThread = testIfMustLoadScriptOnMainThread(config, url); if (shouldExecuteScriptViaMainThread) { setter(this, ['type'], 'text/javascript'); diff --git a/tests/unit/utils.ts b/tests/unit/utils.ts index a30d2656..8acb3294 100644 --- a/tests/unit/utils.ts +++ b/tests/unit/utils.ts @@ -2,6 +2,7 @@ import { suite as uvuSuite } from 'uvu'; import type { MainWindow, PartytownConfig, + PartytownInternalConfig, PartytownWebWorker, WebWorkerEnvironment, WinId, @@ -73,7 +74,7 @@ export interface TestContext { location: Location; loc: any; worker: TestWorker; - config: PartytownConfig; + config: PartytownInternalConfig; env: WebWorkerEnvironment; snippetCode: string; run: (code: string) => any;