From 19564b31ee7c5d2462021ba25006b92e905dec44 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Sun, 23 Jun 2024 23:08:54 -0700 Subject: [PATCH] feat: configuration through url + context abstraction through rpc (#8) This PR removes direct dependency on browser's `MessageChannel` in favor of an RPC implementation: https://github.com/microsoft/postmessage-rpc/ PR also allows configuration of the service worker through initial query string parameters. --- .vscode/launch.json | 13 + README.md | 4 +- ext/bib.ts | 5 +- ext/samples/express-post.ts | 30 + ext/samples/remote-context.ts | 40 + index.d.ts | 54 + package-lock.json | 1616 ++++++++++++++++++-- package.json | 28 +- patches/@mixer+postmessage-rpc+1.1.4.patch | 47 + src/channel.ts | 136 ++ src/common.ts | 194 ++- src/context.ts | 197 +++ src/frame.html | 13 + src/frame.ts | 89 ++ src/index.ts | 46 +- src/mt.ts | 149 +- src/sw.ts | 159 +- test/channel.test.ts | 95 ++ test/server.test.js | 4 +- test/subprocess.ts | 26 + tsconfig.json | 1 + webpack.config.ts | 41 +- 22 files changed, 2651 insertions(+), 336 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 ext/samples/express-post.ts create mode 100644 ext/samples/remote-context.ts create mode 100644 index.d.ts create mode 100644 patches/@mixer+postmessage-rpc+1.1.4.patch create mode 100644 src/channel.ts create mode 100644 src/context.ts create mode 100644 src/frame.html create mode 100644 src/frame.ts create mode 100644 test/channel.test.ts create mode 100644 test/subprocess.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1237f1c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "native tests", + "request": "launch", + "runtimeArgs": ["run-script", "test:native"], + "runtimeExecutable": "npm", + "skipFiles": ["/**"], + "type": "node" + } + ] +} diff --git a/README.md b/README.md index d9e9614..639c264 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fakettp -sandbox node core http module in a service worker. +sandbox node core http module in a service worker. - [disclaimer](#disclaimer) - [motivation](#motivation) @@ -146,8 +146,10 @@ and if you attempt to send a request after that, you will get an error: working samples: - `ext/samples/express.ts`: express app with a dynamic route. +- `ext/samples/express-post.ts`: express app with a post route. - `ext/samples/express-static.ts`: express app with static files. - `ext/samples/socket-io.ts`: express app with socket io. +- `ext/samples/remote-context`: shows the usage of `fakettp.html`. this is what is known to work good enough for most use cases: diff --git a/ext/bib.ts b/ext/bib.ts index 3da927b..0e733cf 100644 --- a/ext/bib.ts +++ b/ext/bib.ts @@ -1,8 +1,8 @@ // BIB: Browser In Browser. -export async function createBib(initialUrl: string) { +export async function createBib(initialUrl: string): Promise { const html = `\
- +
`; const parser = new DOMParser(); @@ -54,4 +54,5 @@ export async function createBib(initialUrl: string) { frame.addEventListener("error", (err) => { frame.srcdoc = err.message; }); + return frame; } diff --git a/ext/samples/express-post.ts b/ext/samples/express-post.ts new file mode 100644 index 0000000..b7208e2 --- /dev/null +++ b/ext/samples/express-post.ts @@ -0,0 +1,30 @@ +import cors from "cors"; +import express from "express"; +import { createServer } from "http"; +import type { AddressInfo } from "net"; + +const app = express(); +const server = createServer(app); + +app.use(cors()); + +app.get("/", (req, res) => { + console.log("Request received.", req.url); + res.send("Hello From Express."); +}); + +app.post("/", (req, res) => { + console.log("POST request received.", req.url); + req.pipe(res); +}); + +server.listen(async () => { + console.log(`Server listening on ${server.address()}.`); + const address = server.address() as AddressInfo; + const scheme = address.port === 443 ? "https" : "http"; + const url = `${scheme}://${address.address}:${address.port}/`; + const response = await fetch(url, { method: "POST", body: "Hello From Fetch." }); + console.log("Response received.", response.status, response.statusText); + console.log("Response body:", await response.text()); + alert("Check console for logs!"); +}); diff --git a/ext/samples/remote-context.ts b/ext/samples/remote-context.ts new file mode 100644 index 0000000..b571a5b --- /dev/null +++ b/ext/samples/remote-context.ts @@ -0,0 +1,40 @@ +import express from "express"; +import { createServer } from "http"; +import type { AddressInfo } from "net"; + +import { createBib } from "../bib"; +import { IFrameContext, setContext } from "../../src/context"; + +const params = new URLSearchParams(location.search); + +params.set("d", ""); // enables debugging +params.set("i", new RegExp("localhost").source); +params.append("e", new RegExp("fakettp.html").source); +params.append("e", new RegExp("fakettp.js").source); +params.append("e", new RegExp("nosw.js").source); +params.append("e", new RegExp("sample-.*").source); + +const serviceUrl = new URL(`fakettp.html?${params.toString()}`, location.href); + +createBib(serviceUrl.href).then((frame) => { + frame.onload = () => { + const context = new IFrameContext(frame); + setContext(context); + + const app = express(); + const server = createServer(app); + + app.get("/", (req, res) => { + console.log("Request received.", req.url); + res.send("Hello From Remote Context!"); + }); + + server.listen(() => { + console.log(`Server listening on ${server.address()}.`); + const address = server.address() as AddressInfo; + const scheme = address.port === 443 ? "https" : "http"; + const url = `${scheme}://${address.address}:${address.port}/`; + context.browse(url); + }); + }; +}); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..ea68552 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,54 @@ +/// +declare module "fakettp" { + type CleanupReceiver = () => void | Promise; + type MessageReceiver = (message: any) => void | Promise; + export interface Context { + readonly postMessage: MessageReceiver; + readonly readMessages: (callback: MessageReceiver) => CleanupReceiver; + readonly reloadWorker?: () => void | Promise; + readonly unloadWorker?: () => void | Promise; + } + export function setContext(context: Context): void; + export function getContext(): Context; + export class WindowContext implements Context { + constructor(config?: { include?: RegExp[]; exclude?: RegExp[] }); + postMessage(message: any): void; + readMessages(callback: MessageReceiver): CleanupReceiver; + reloadWorker(): Promise; + unloadWorker(): Promise; + } + export interface MessageEvent { + data: any; + } + export interface Postable { + postMessage(data: any): void; + } + export interface Receivable { + readMessages(callback: (ev: MessageEvent) => void): () => void; + } + export interface RPCOptions { + target: Postable; + serviceId: string; + receiver?: Receivable; + } + export class RPC { + readonly isReady: Promise; + constructor(options: RPCOptions); + expose(method: string, handler: (params: T) => Promise | any): this; + call(method: string, params?: object, waitForReply?: true): Promise; + call(method: string, params?: object, waitForReply?: false): void; + destroy(): void; + } + export class RemoteContext implements Context { + protected readonly rpc: RPC; + constructor(rpc: RPC); + postMessage(message: any): void; + readMessages(callback: MessageReceiver): CleanupReceiver; + reloadWorker(): Promise; + unloadWorker(): Promise; + browse(url: string): Promise; + } + export class IFrameContext extends RemoteContext { + constructor(frame: HTMLIFrameElement); + } +} diff --git a/package-lock.json b/package-lock.json index a522706..0f97327 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fakettp", - "version": "1.9.2", + "version": "1.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fakettp", - "version": "1.9.2", + "version": "1.9.3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -21,30 +21,34 @@ "util": "*" }, "devDependencies": { + "@mixer/postmessage-rpc": "^1.1.4", "@types/debug": "^4.1.12", "@types/node": "^20.12.7", + "@types/traverse": "^0.6.37", + "@types/uuid": "^9.0.8", "@types/webpack": "^5.28.5", "@wdio/cli": "^8.35.1", "@wdio/local-runner": "^8.35.1", "@wdio/mocha-framework": "^8.35.0", "@wdio/spec-reporter": "^8.32.4", - "assert": "*", - "buffer": "*", + "beforeunload-request": "^1.0.1", "copy-webpack-plugin": "^12.0.2", "cors": "^2.8.5", - "debug": "*", - "events": "*", + "exponential-backoff": "^3.1.1", + "glob": "^10.4.1", "html-webpack-plugin": "^5.6.0", "null-loader": "^4.0.1", - "process": "*", + "patch-package": "^8.0.0", "stream-browserify": "*", "stream-http": "*", "terser-webpack-plugin": "^5.3.10", + "traverse": "^0.6.9", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", + "tsx": "^4.11.0", "typescript": "^5.4.5", - "url": "*", - "util": "*", + "uuid": "^9.0.1", + "uvu": "^0.5.6", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", @@ -202,6 +206,374 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -415,6 +787,21 @@ "node": ">= 0.4" } }, + "node_modules/@mixer/postmessage-rpc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@mixer/postmessage-rpc/-/postmessage-rpc-1.1.4.tgz", + "integrity": "sha512-Cb/3CukztYD/mXILTOjtlOsfKEeHNBJE5Cipn5i+IAVm2pvbI0LaVJ1TJZS67BOTcAlBcvTjhwAlzyrpQO0TeA==", + "dev": true, + "dependencies": { + "eventemitter3": "^3.1.0" + } + }, + "node_modules/@mixer/postmessage-rpc/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -832,6 +1219,18 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/traverse": { + "version": "0.6.37", + "resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.37.tgz", + "integrity": "sha512-c90MVeDiUI1FhOZ6rLQ3kDWr50YE8+paDpM+5zbHjbmsqEp2DlMYkqnZnwbK9oI+NvDe8yRajup4jFwnVX6xsA==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@types/webpack": { "version": "5.28.5", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", @@ -1524,6 +1923,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1882,6 +2287,44 @@ "dequal": "^2.0.3" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -1904,7 +2347,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -1940,11 +2382,22 @@ "node": ">=0.12.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -2004,7 +2457,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2044,6 +2496,12 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/beforeunload-request": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/beforeunload-request/-/beforeunload-request-1.0.1.tgz", + "integrity": "sha512-g+fHMM9aGGnOR8zwtHs3K1diiaa6qYvPxUiJOWuotKZuzTZcPibXEhv/Oa60Lf966Q2NdYBG5TwT/fEiX53aGQ==", + "dev": true + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -2313,7 +2771,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -2501,6 +2958,15 @@ "node": "*" } }, + "node_modules/chainsaw/node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3244,6 +3710,57 @@ "node": ">= 12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3401,7 +3918,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -3931,6 +4447,66 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -3956,6 +4532,87 @@ "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4104,7 +4761,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -4178,6 +4834,12 @@ "webdriverio": "^8.29.3" } }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -4521,6 +5183,15 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -4554,7 +5225,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -4681,6 +5351,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -4805,6 +5502,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -4830,22 +5556,22 @@ } }, "node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4893,6 +5619,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", @@ -5013,6 +5755,15 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5034,9 +5785,9 @@ } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -5056,12 +5807,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5095,9 +5845,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -5422,7 +6172,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -5529,6 +6278,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -5564,7 +6327,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -5576,12 +6338,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5594,11 +6384,26 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5618,6 +6423,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -5655,7 +6490,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -5709,7 +6543,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -5721,6 +6554,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-network-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", @@ -5742,6 +6587,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -5766,6 +6626,37 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5778,13 +6669,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -5805,6 +6725,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -5842,9 +6774,9 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -6018,6 +6950,30 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6042,6 +6998,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6060,6 +7025,24 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ky": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", @@ -6506,9 +7489,9 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -6786,6 +7769,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7033,7 +8025,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -7049,19 +8040,17 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -7373,14 +8362,111 @@ "node": ">= 0.8" } }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" } }, "node_modules/path-browserify": { @@ -7423,25 +8509,25 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -7522,6 +8608,14 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -7577,7 +8671,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, "engines": { "node": ">= 0.6.0" } @@ -8146,6 +9239,24 @@ "node": ">=6.0.0" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -8236,6 +9347,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", @@ -8399,12 +9519,48 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safaridriver": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.2.tgz", "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", "dev": true }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8424,6 +9580,23 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -8666,6 +9839,21 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -8859,6 +10047,15 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -9098,6 +10295,55 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -9326,12 +10572,20 @@ "dev": true }, "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", + "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", "dev": true, + "dependencies": { + "gopd": "^1.0.1", + "typedarray.prototype.slice": "^1.0.3", + "which-typed-array": "^1.1.15" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/ts-loader": { @@ -9420,6 +10674,25 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsx": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.0.tgz", + "integrity": "sha512-vzGGELOgAupsNVssAmZjbUDfdm/pWP4R+Kg8TVdsonxbXk0bEpE1qh0yV6/QxUVXaVlNemgcPajGdJJ82n3stg==", + "dev": true, + "dependencies": { + "esbuild": "~0.20.2", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -9444,6 +10717,99 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz", + "integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-offset": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -9457,6 +10823,21 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -9583,7 +10964,6 @@ "version": "0.11.3", "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", - "dev": true, "dependencies": { "punycode": "^1.4.1", "qs": "^6.11.2" @@ -9598,14 +10978,12 @@ "node_modules/url/node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/url/node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -9629,7 +11007,6 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -9658,14 +11035,36 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10268,17 +11667,32 @@ "node": ">= 8" } }, - "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -10391,6 +11805,18 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.3.tgz", + "integrity": "sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 5f41483..399d0e7 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,16 @@ { "name": "fakettp", - "version": "1.9.2", - "description": "sandbox node core http module in a service worker.", + "version": "1.9.3", + "description": "sandbox node core http module in a service worker.", "main": "dist/fakettp.js", + "types": "./index.d.ts", "scripts": { - "install": "npm run build", + "install": "patch-package && npm run build", "build": "webpack", "watch": "webpack --watch", "serve": "webpack serve", - "test": "wdio run ./wdio.conf.js" + "test": "wdio run ./wdio.conf.js && npm run test:native", + "test:native": "npx tsx test/channel.test.ts" }, "keywords": [ "http", @@ -26,30 +28,34 @@ "author": "Sepehr Laal", "license": "MIT", "devDependencies": { + "@mixer/postmessage-rpc": "^1.1.4", "@types/debug": "^4.1.12", "@types/node": "^20.12.7", + "@types/traverse": "^0.6.37", + "@types/uuid": "^9.0.8", "@types/webpack": "^5.28.5", "@wdio/cli": "^8.35.1", "@wdio/local-runner": "^8.35.1", "@wdio/mocha-framework": "^8.35.0", "@wdio/spec-reporter": "^8.32.4", - "assert": "*", - "buffer": "*", + "beforeunload-request": "^1.0.1", "copy-webpack-plugin": "^12.0.2", "cors": "^2.8.5", - "debug": "*", - "events": "*", + "exponential-backoff": "^3.1.1", + "glob": "^10.4.1", "html-webpack-plugin": "^5.6.0", "null-loader": "^4.0.1", - "process": "*", + "patch-package": "^8.0.0", "stream-browserify": "*", "stream-http": "*", "terser-webpack-plugin": "^5.3.10", + "traverse": "^0.6.9", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", + "tsx": "^4.11.0", "typescript": "^5.4.5", - "url": "*", - "util": "*", + "uuid": "^9.0.1", + "uvu": "^0.5.6", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", diff --git a/patches/@mixer+postmessage-rpc+1.1.4.patch b/patches/@mixer+postmessage-rpc+1.1.4.patch new file mode 100644 index 0000000..472ef92 --- /dev/null +++ b/patches/@mixer+postmessage-rpc+1.1.4.patch @@ -0,0 +1,47 @@ +diff --git a/node_modules/@mixer/postmessage-rpc/dist/rpc.js b/node_modules/@mixer/postmessage-rpc/dist/rpc.js +index 4b452f8..4432bce 100644 +--- a/node_modules/@mixer/postmessage-rpc/dist/rpc.js ++++ b/node_modules/@mixer/postmessage-rpc/dist/rpc.js +@@ -46,19 +46,7 @@ var RPC = /** @class */ (function (_super) { + * Reorder utility for incoming messages. + */ + _this.reorder = new reorder_1.Reorder(); +- _this.listener = function (ev) { +- // If we got data that wasn't a string or could not be parsed, or was +- // from a different remote, it's not for us. +- if (_this.options.origin && _this.options.origin !== '*' && ev.origin !== _this.options.origin) { +- return; +- } +- var packet; +- try { +- packet = JSON.parse(ev.data); +- } +- catch (e) { +- return; +- } ++ _this.listener = function (packet) { + if (!types_1.isRPCMessage(packet) || packet.serviceID !== _this.options.serviceId) { + return; + } +@@ -216,7 +204,7 @@ var RPC = /** @class */ (function (_super) { + }; + RPC.prototype.post = function (message) { + message.counter = this.callCounter++; +- this.options.target.postMessage(JSON.stringify(message), this.options.origin || '*'); ++ this.options.target.postMessage(message, this.options.origin || '*'); + }; + RPC.prototype.isReadySignal = function (packet) { + if (packet.type === 'method' && packet.method === 'ready') { +diff --git a/node_modules/@mixer/postmessage-rpc/dist/types.js b/node_modules/@mixer/postmessage-rpc/dist/types.js +index abbdeb4..ff48989 100644 +--- a/node_modules/@mixer/postmessage-rpc/dist/types.js ++++ b/node_modules/@mixer/postmessage-rpc/dist/types.js +@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); + * and postmessages from other sources. + */ + function isRPCMessage(data) { +- return (data.type === 'method' || data.type === 'reply') && typeof data.counter === 'number'; ++ return typeof data === "object" && (data.type === 'method' || data.type === 'reply') && typeof data.counter === 'number'; + } + exports.isRPCMessage = isRPCMessage; + /** diff --git a/src/channel.ts b/src/channel.ts new file mode 100644 index 0000000..2e7dde2 --- /dev/null +++ b/src/channel.ts @@ -0,0 +1,136 @@ +import debug from "debug"; +import assert from "assert"; +import traverse from "traverse"; +import { EventEmitter } from "events"; +import { RPC } from "@mixer/postmessage-rpc"; +import { uniqueId, threadId, timedPromise } from "./common"; +import { getContext } from "./context"; + +const POST_PREFIX = "port"; +const POST_TIMEOUT = 10000; + +const _log = debug("fakettp:channel"); +const log = (patt: string, ...args: any[]) => _log(`[${threadId()}] ${patt}`, ...args); + +interface TransferredBuffer { + data: number[]; + type: "Buffer"; +} + +function isTransferredBuffer(input: any): input is TransferredBuffer { + return typeof input === "object" && Array.isArray(input.data) && input.type === "Buffer"; +} + +function correctTransferredBuffer(input: any) { + return traverse(input).map(function (value) { + if (isTransferredBuffer(value)) { + this.update(new Uint8Array(value.data)); + } + }); +} + +export class MessagePort extends EventEmitter { + private _started = false; + private _stopped = false; + private _posting = false; + private readonly _q: T[] = []; + private readonly _rpc: RPC; + constructor(readonly id = uniqueId(), readonly context = getContext()) { + super(); + this.setMaxListeners(0); // fixme + this.id = (id || uniqueId()).replace(new RegExp(`^${POST_PREFIX}:`), ""); + this._rpc = new RPC({ + serviceId: this.id, + target: this.context, + receiver: this.context, + }); + this._rpc.expose("emit", (message: T) => { + log("message received: %o", message); + if (this._started) this.emit("message", correctTransferredBuffer(message)); + else this._q.push(correctTransferredBuffer(message)); + }); + } + set onmessage(value: (this: MessagePort, message: T) => any) { + this.on("message", value); + this.start(); + } + close(): void { + if (this._stopped) return; + if (this._posting) { + this.once("unblocked", this.close.bind(this)); + return; + } + this._stopped = true; + this._rpc.destroy(); + this.emit("close"); + this.removeAllListeners(); + log("port closed: %s", this.id); + } + async postMessage(message: T) { + this._posting = true; + await timedPromise( + POST_TIMEOUT, + this._rpc.isReady, + `port ${this.id} failed to connect (msg: '${JSON.stringify(message)}').` + ); + log("message sent: %o", message); + const ret = await this._rpc.call("emit", message, true); + this._posting = false; + this.emit("unblocked"); + return ret; + } + start(): void { + if (this._started) return; + this._started = true; + for (const message of this._q) { + this.emit("message", message); + } + } + addEventListener(type: string, listener: (this: MessagePort, ev: T) => any): void { + this.on(type, listener); + } + removeEventListener(type: string, listener: (this: MessagePort, ev: T) => any): void { + this.off(type, listener); + } + dispatchEvent = (): boolean => { + assert(false, "dispatchEvent not supported. use postMessage instead."); + }; + toString(): string { + return `${POST_PREFIX}:${this.id}`; + } + toJSON(): string { + return this.toString(); + } +} + +export class MessageChannel { + readonly port1: MessagePort; + readonly port2: MessagePort; + + constructor(context = getContext()) { + this.port1 = new MessagePort(uniqueId(), context); + this.port2 = new MessagePort(uniqueId(), context); + const _port1PostMessage = this.port1.postMessage.bind(this.port1); + const _port2PostMessage = this.port2.postMessage.bind(this.port2); + this.port1.postMessage = _port2PostMessage; + this.port2.postMessage = _port1PostMessage; + const port2OnMessage = (message: any) => { + this.port1 + .listeners("message") + .filter((listener) => listener !== port1OnMessage) + .forEach((listener) => { + listener.call(this.port1, correctTransferredBuffer(message)); + }); + }; + const port1OnMessage = (message: any) => { + this.port2 + .listeners("message") + .filter((listener) => listener !== port2OnMessage) + .forEach((listener) => { + listener.call(this.port2, correctTransferredBuffer(message)); + }); + }; + this.port1.onmessage = port1OnMessage; + this.port2.onmessage = port2OnMessage; + } +} diff --git a/src/common.ts b/src/common.ts index a2c3a88..f5ea1a8 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,77 +1,68 @@ -declare const globalThis: ServiceWorkerGlobalScope | Window; - import debug from "debug"; +import { v4 as uuid } from "uuid"; -const log = debug("fakettp:sw"); +import { MessageChannel, MessagePort } from "./channel"; -export const FIN = "\x00" as const; +const LOG = debug("fakettp:common"); +const log = (patt: string, ...args: any[]) => LOG(`[${threadId()}] ${patt}`, ...args); -export function getBundledWorkerFileName() { - return process.env.FAKETTP_MAIN || "fakettp.js"; -} +export const FIN = "__FAKETTP__FIN__PACKET__" as const; +export const FIN_BYTES = new TextEncoder().encode(FIN); -export function getExcludedPaths() { - const paths = [getBundledWorkerFileName()]; - return [ - ...paths, - "nosw.js", - "app.html", - "favicon.ico", - "auxillary.js", - "inspector.js", - "bundle.webgme.js", - "bundle.memory.zip", - "sample-express.js", - "sample-express.html", - "sample-express-static.js", - "sample-express-static.html", - "sample-socket-io.js", - "sample-socket-io.html", - ]; +export function isRunningInServiceWorker() { + return typeof globalThis !== "undefined" && "ServiceWorkerGlobalScope" in globalThis; } -export function isRunningInBrowserWindow() { - return typeof window !== "undefined" && window === globalThis; +export function isRunningInServiceWindow() { + return ( + typeof window !== "undefined" && + window.parent !== window && + typeof document !== "undefined" && + document.getElementById("fakettp-frame") !== null + ); } -export function isRunningInServiceWorker() { - return typeof globalThis !== "undefined" && "ServiceWorkerGlobalScope" in globalThis; +export function getServiceId() { + const url = new URL(isRunningInServiceWindow() ? globalThis.location.href : "http://localhost/fakettp.html"); + return `fakettp:${url.href}`; } export type StringOrBuffer = string | ArrayBufferView; -export function StringOrBufferToBuffer(input: StringOrBuffer) { - if (typeof input === "string") return new TextEncoder().encode(input); +function StringOrBufferToBuffer(input: StringOrBuffer) { if (ArrayBuffer.isView(input)) return input; + return new TextEncoder().encode(input.toString()); } -export function StringOrBufferToString(input: StringOrBuffer) { - if (typeof input === "string") return input; +function StringOrBufferToString(input: StringOrBuffer) { if (ArrayBuffer.isView(input)) return new TextDecoder().decode(input); + return input.toString(); +} + +function isFIN(input: any): input is typeof FIN { + log("checking if input is FIN: %o", input); + return input == FIN || StringOrBufferToString(input) === FIN; } -let counter = 0; -export const monotonicId = () => ++counter; -export const uniqueId = () => Date.now() * 1000 + Math.floor(Math.random() * 1000); +export const uniqueId = () => uuid(); -export function MessagePortToReadableStream(port: MessagePort, onClose?: () => void): ReadableStream { +export function MessagePortToReadableStream(port: MessagePort): ReadableStream { const portId = uniqueId(); - log("creating readable stream from message port: %d", portId); + log("creating readable stream from message port: %s", portId); let controller: ReadableStreamController | null = null; - port.onmessage = function (event: MessageEvent) { - const { data } = event; - log("message received from message port: %d", portId); - if (StringOrBufferToString(data) === FIN) { - log("fin received from message port for readable stream: %d", portId); + port.onmessage = function (data: StringOrBuffer | typeof FIN) { + log("message received from message port: %s", portId, data); + if (isFIN(data)) { + log("fin received from message port for readable stream: %s", portId); try { controller?.close(); } catch (e) { log(e); } controller = null; - onClose?.(); + port.close(); } else { - log("pushing data to readable stream from message port: %d", portId); + log("pushing data to readable stream from message port: %s", portId); const content = StringOrBufferToBuffer(data); controller?.enqueue(content); } @@ -79,29 +70,28 @@ export function MessagePortToReadableStream(port: MessagePort, onClose?: () => v return new ReadableStream({ type: "bytes", start(ctrl) { - log("controller started for readable stream from message port: %d", portId); + log("controller started for readable stream from message port: %s", portId); controller = ctrl; }, }); } -export function ReadableStreamToMessagePort(stream: ReadableStream, onClose?: () => void): MessagePort { +export function ReadableStreamToMessagePort(stream: ReadableStream): MessagePort { const channel = new MessageChannel(); const portId = uniqueId(); const port = channel.port1; - log("creating message port from readable stream: %d", portId); + log("creating message port from readable stream: %s", portId); stream.pipeTo( new WritableStream({ write(data) { - log("pushing data from readable stream to message port: %d", portId); + log("pushing data from readable stream to message port: %s", portId); const content = StringOrBufferToBuffer(data); - port.postMessage(content, [content.buffer]); + port.postMessage(content); }, close() { - log("fin received from stream for message port: %d", portId); + log("fin received from stream for message port: %s", portId); port.postMessage(FIN); - channel.port1.close(); - onClose?.(); + port.close(); }, }) ); @@ -121,12 +111,12 @@ export async function RequestBodyToReadableStream(request: Request): Promise>; export async function serializeRequest(request: Request) { const id = uniqueId(); - log("serializing request: %d", id); + log("serializing request: %s", id); const url = request.url; const method = request.method; const headers: Record = {}; @@ -162,7 +152,7 @@ export async function serializeRequest(request: Request) { }; } -export function deserializeRequest(request: SerializedRequest): Request & { id?: number } { +export function deserializeRequest(request: SerializedRequest): Request & { id?: string } { const { id, url, @@ -178,11 +168,12 @@ export function deserializeRequest(request: SerializedRequest): Request & { id?: integrity, keepalive, } = request; - log("deserializing request: %d", id); + log("deserializing request: %s", id); const requestInit: RequestInit = {}; + Object.assign(requestInit, { duplex: "half" }); if (method) requestInit.method = method; if (headers) requestInit.headers = new Headers(headers); - if (body) requestInit.body = MessagePortToReadableStream(body); + if (body) requestInit.body = MessagePortToReadableStream(new MessagePort(body as unknown as string)); if (mode) requestInit.mode = mode === "navigate" ? undefined : mode; if (credentials) requestInit.credentials = credentials; if (cache) requestInit.cache = cache; @@ -200,15 +191,90 @@ export function normalizedPort(url: URL) { return url.port !== "" ? url.port : url.protocol === "https:" ? "443" : "80"; } -function _defaultHost() { - const _url = new URL(globalThis.location.href); +export function defaultUrl() { + try { + return new URL(globalThis.location.href); + } catch { + log("defaultURL: globalThis.location not found."); + return new URL("http://localhost"); + } +} + +export function defaultHost() { + const _url = defaultUrl(); return _url.hostname; } -function _defaultPort() { - const _url = new URL(globalThis.location.href); +export function defaultPort() { + const _url = defaultUrl(); return normalizedPort(_url); } -export const defaultPort = _defaultPort(); -export const defaultHost = _defaultHost(); +export class Singleton { + private _instance: T | null = null; + private _factory: () => T; + constructor(factory: () => T) { + this._factory = factory; + } + Get() { + if (this._instance === null) { + this._instance = this._factory(); + } + return this._instance; + } +} + +const THREAD_ID = new Singleton(() => { + const _id = uniqueId(); + return `${_id.slice(0, 3)}${_id.slice(-3)}`; +}); + +export function threadId() { + return THREAD_ID.Get(); +} + +export function timedPromise(ms: number, promise: Promise, message = "Timed out") { + return Promise.race([ + promise, + new Promise((_, reject) => { + setTimeout(() => reject(new Error(message)), ms); + }), + ]); +} + +export interface PartConfig { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface FullConfig { + readonly include: RegExp[]; + readonly exclude: RegExp[]; +} + +const DEFAULT_CONFIG: Required = { + exclude: ["fakettp\\.html", "fakettp\\.js", "nosw\\.js$", "favicon\\.ico$"], + include: [".*"], +}; + +export function getConfigFromLocation(): Required { + if (typeof globalThis !== "object" || !("location" in globalThis)) { + log("location object not found"); + return DEFAULT_CONFIG; + } + log("checking for location config: %o", globalThis.location); + const search = new URLSearchParams(globalThis.location.search); + const include = search.getAll("i"); + const exclude = search.getAll("e"); + if (include.length === 0 && exclude.length === 0) { + log("no location config found, using defaults"); + return DEFAULT_CONFIG; + } + const out = { include, exclude }; + log("location config: %o", out); + return out; +} + +export function isDebugEnabled() { + return typeof globalThis !== "undefined" && globalThis.location?.search.includes("d"); +} diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..3b3d2e3 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,197 @@ +/// +/// + +import debug from "debug"; +import assert from "assert"; +import { RPC } from "@mixer/postmessage-rpc"; +import { backOff } from "exponential-backoff"; +import { EventEmitter } from "events"; + +import { type FullConfig, type PartConfig, getConfigFromLocation, isDebugEnabled } from "./common"; + +const log = debug("fakettp:context"); + +type CleanupReceiver = () => void | Promise; +type MessageReceiver = (message: any) => void | Promise; + +export interface Context { + readonly postMessage: MessageReceiver; + readonly readMessages: (callback: MessageReceiver) => CleanupReceiver; + readonly reloadWorker?: () => void | Promise; + readonly unloadWorker?: () => void | Promise; +} + +export class RemoteContext implements Context { + private readonly _emitter = new EventEmitter(); + + constructor(protected readonly rpc: RPC) { + this._emitter.setMaxListeners(0); // fixme + this.rpc.expose("message", ({ message }: { message: any }) => { + this._emitter.emit("message", message); + }); + } + + async postMessage(message: any) { + log("posting message %o via remote context", message); + await this.rpc.isReady; + await this.rpc.call("message", { message }, true); + } + + readMessages(callback: MessageReceiver) { + this._emitter.on("message", callback); + return () => { + this._emitter.off("message", callback); + }; + } + + async reloadWorker() { + log("reloading worker via remote context"); + await this.rpc.isReady; + await this.rpc.call("reload", {}, true); + } + + async unloadWorker() { + log("unloading worker via remote context"); + await this.rpc.isReady; + await this.rpc.call("unload", {}, true); + } + + async browse(url: string) { + log("browsing to %s via remote context", url); + await this.rpc.isReady; + await this.rpc.call("browse", { url }, true); + } +} + +function getReferencedWindow(el: HTMLElement) { + const doc = el.ownerDocument; + if (!doc) return null; + return doc.defaultView; +} + +export class IFrameContext extends RemoteContext { + constructor(readonly frame: HTMLIFrameElement) { + const serviceId = `fakettp:${new URL(frame.src).href}`; + log("remote service ID: %s", serviceId); + super( + new RPC({ + serviceId, + target: { + postMessage: (message) => { + frame.contentWindow?.postMessage(message, "*"); + }, + }, + receiver: { + readMessages: (cb) => { + const _cb = ({ data }: MessageEvent) => cb(data); + getReferencedWindow(frame)?.addEventListener("message", _cb); + return () => { + getReferencedWindow(frame)?.removeEventListener("message", _cb); + }; + }, + }, + }) + ); + } +} + +export class WindowContext implements Context { + private _worker: ServiceWorker | null = null; + private readonly _config: Required; + + constructor(config?: Partial) { + const locationConfig = getConfigFromLocation(); + this._config = { + include: [...(config?.include?.map((i) => i.source) || []), ...locationConfig.include], + exclude: [...(config?.exclude?.map((i) => i.source) || []), ...locationConfig.exclude], + }; + } + + postMessage(message: any) { + this._worker?.postMessage(message); + } + + readMessages(callback: MessageReceiver) { + const cb = (event: MessageEvent) => { + callback(event.data); + }; + navigator.serviceWorker.addEventListener("message", cb); + return () => { + navigator.serviceWorker.removeEventListener("message", cb); + }; + } + + async reloadWorker() { + if (this._worker) { + await this.unloadWorker(); + } + const query = new URLSearchParams(); + if (isDebugEnabled()) query.append("d", ""); + this._config.include.forEach((i) => query.append("i", i)); + this._config.exclude.forEach((e) => query.append("e", e)); + const queryString = query.toString(); + log("reloading worker with query: %s", queryString); + navigator.serviceWorker.register(`fakettp.js?${queryString}`, { updateViaCache: "none", scope: "/" }); + await this._waitForControllerChange(); + await this._waitForWorkerLoad(); + const reg = await navigator.serviceWorker.ready; + this._worker = reg.active; + globalThis.addEventListener("beforeunload", this._selfDestruct); + } + + async unloadWorker() { + if (!this._worker) return; + log("unloading worker"); + navigator.serviceWorker.ready.then((r) => r.unregister()); + navigator.serviceWorker.register("nosw.js", { updateViaCache: "none", scope: "/" }); + await this._waitForControllerChange(); + await this._waitForWorkerStop(); + this._worker = null; + globalThis.removeEventListener("beforeunload", this._selfDestruct); + } + + private _selfDestruct = (ev: BeforeUnloadEvent) => { + log("attempting to unload worker via proxy window"); + const beforeunloadRequest = require("beforeunload-request"); + const success = beforeunloadRequest("/__self_destruct__"); + if (!success) { + ev.preventDefault(); + ev.returnValue = ""; + } + }; + + private async _waitForControllerChange() { + if (navigator.serviceWorker.controller) return; + await new Promise((resolve) => { + navigator.serviceWorker.addEventListener("controllerchange", resolve, { once: true }); + }); + } + + private async _waitForWorkerStop() { + const _work = async () => { + const response = await fetch(`/__status__`); + assert(response.status !== 200); + }; + await backOff(_work); + } + + private async _waitForWorkerLoad() { + const _work = async () => { + const response = await fetch(`/__status__`); + assert(response.status === 200); + }; + await backOff(_work); + } +} + +let _context: Context | null = null; +export const getContext = () => { + if (!_context) { + log("creating a default context. use setContext to override this."); + _context = new WindowContext(); + } + return _context; +}; +export const setContext = (context: Context) => { + _context = context; +}; diff --git a/src/frame.html b/src/frame.html new file mode 100644 index 0000000..e9187f5 --- /dev/null +++ b/src/frame.html @@ -0,0 +1,13 @@ + + + + Fakettp Frame + + + + + + diff --git a/src/frame.ts b/src/frame.ts new file mode 100644 index 0000000..b9e5c5e --- /dev/null +++ b/src/frame.ts @@ -0,0 +1,89 @@ +declare const globalThis: Window; + +import debug from "debug"; +import assert from "assert"; +import { RPC } from "@mixer/postmessage-rpc"; + +import { WindowContext } from "../src/context"; +import { getServiceId, isDebugEnabled } from "./common"; + +const log = debug("fakettp:frame"); + +if (isDebugEnabled()) debug.enable("*"); + +async function _installProxyWindow() { + const serviceId = getServiceId(); + log("installing proxy window with service id: %s", serviceId); + const rpc = new RPC({ + serviceId, + target: { + postMessage: (message) => { + globalThis.parent?.postMessage(message, "*"); + }, + }, + receiver: { + readMessages: (cb) => { + const _cb = ({ data }: MessageEvent) => cb(data); + globalThis.addEventListener("message", _cb); + return () => { + globalThis.removeEventListener("message", _cb); + }; + }, + }, + }); + log("exposing browse"); + rpc.expose("browse", async ({ url }: { url: string }) => { + log("browsing to %s", url); + const style = [ + "position: fixed", + "top: 0", + "left: 0", + "right: 0", + "bottom: 0", + "width: 100%", + "height: 100%", + "z-index: 999", + "background: #fff", + "border: none", + "margin: 0", + "padding: 0", + ].join(";"); + const sandbox = ["allow-scripts", "allow-same-origin", "allow-modals"].join(" "); + const html = ``; + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const frame = doc.querySelector("iframe"); + assert(frame, "fakettp: failed to create iframe"); + document.body.appendChild(frame); + }); + const ctx = new WindowContext(); + log("exposing reload"); + rpc.expose("reload", async () => { + log("reloading worker via proxy window"); + await ctx.reloadWorker(); + }); + log("exposing unload"); + rpc.expose("unload", async () => { + log("unloading worker via proxy window"); + await ctx.unloadWorker(); + }); + log("exposing message"); + rpc.expose("message", ({ message }: { message: any }) => { + ctx.postMessage(message); + }); + log("forwarding messages to the remote side"); + ctx.readMessages(async (message) => { + await rpc.isReady; + await rpc.call("message", { message }, true); + }); + log("window ready, awaiting rpc connection..."); + await rpc.isReady; + log("rpc connection established."); + // ensure rpc is not garbage collected. + Object.assign(globalThis, { __rpc__: rpc }); +} + +export function installProxyWindow() { + if (/complete|interactive|loaded/.test(document.readyState)) _installProxyWindow(); + else window.addEventListener("DOMContentLoaded", _installProxyWindow); +} diff --git a/src/index.ts b/src/index.ts index ca0f531..d3e9002 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,36 +1,36 @@ import debug from "debug"; -import { createProxyClient } from "./sw"; -import { isRunningInBrowserWindow, isRunningInServiceWorker } from "./common"; -import { createProxyServer, IncomingMessage, ServerResponse, unload } from "./mt"; +import { installProxyWorker } from "./sw"; +import { installProxyWindow } from "./frame"; +import { createProxyServer, IncomingMessage, ServerResponse } from "./mt"; +import { isRunningInServiceWorker, isRunningInServiceWindow, isDebugEnabled } from "./common"; +import { getContext, setContext, WindowContext, RemoteContext, IFrameContext } from "./context"; +import { RPC } from "@mixer/postmessage-rpc"; import type { RequestListener } from "http"; -const log = debug("fakettp"); -const _http = (() => { - try { - return require("stream-http"); - } catch (_) { - return {}; - } -})(); +if (isDebugEnabled()) debug.enable("*"); -log("built with webpack mode: %s", process.env.FAKETTP_MODE); -log("webpack bundle filename: %s", process.env.FAKETTP_MAIN); - -if (isRunningInServiceWorker()) createProxyClient(); +if (isRunningInServiceWorker()) installProxyWorker(); +if (isRunningInServiceWindow()) installProxyWindow(); const http = { - ..._http, - unload, + ...require("stream-http"), + RPC, + getContext, + setContext, + IFrameContext, + WindowContext, + RemoteContext, ServerResponse, IncomingMessage, - createServer: isRunningInBrowserWindow() - ? (...args: any[]) => { - const requestListener = args.find((arg) => typeof arg === "function") as RequestListener; - return createProxyServer(requestListener); - } - : undefined, + createServer: + !isRunningInServiceWorker() && !isRunningInServiceWindow() + ? (...args: any[]) => { + const requestListener = args.find((arg) => typeof arg === "function") as RequestListener; + return createProxyServer(requestListener); + } + : undefined, __esModule: true, }; diff --git a/src/mt.ts b/src/mt.ts index c1fbe49..da72cfa 100644 --- a/src/mt.ts +++ b/src/mt.ts @@ -1,21 +1,21 @@ -declare const globalThis: Window; - import debug from "debug"; import assert from "assert"; import { EventEmitter } from "events"; import type { RequestListener } from "http"; import { Writable, Duplex, Readable } from "stream"; + +import { getContext } from "./context"; +import { MessageChannel, MessagePort } from "./channel"; import { FIN, uniqueId, + defaultUrl, defaultHost, defaultPort, normalizedPort, SerializedRequest, SerializedResponse, deserializeRequest, - isRunningInBrowserWindow, - getBundledWorkerFileName, } from "./common"; const nosup = (..._unused: any[]) => assert(false, "fakettp: not supported."); @@ -40,17 +40,17 @@ class Socket extends Duplex { constructor(readonly incomingRequest: Request, public readonly ports: SocketPorts) { super(); - log("creating socket: %d", this.id); + log("creating socket: %s", this.id); ports.responsePort.start(); ports.requestPort?.start(); if (this.ports.requestPort && canRequestHaveBody(incomingRequest.method)) { - this.ports.requestPort.onmessage = (event: MessageEvent) => { - if (event.data === FIN) { - log("request port received FIN: %d", this.id); + this.ports.requestPort.onmessage = (data: ArrayBufferView | typeof FIN) => { + if (data === FIN) { + log("request port received FIN: %s", this.id); this.push(null); } else { - log("request port received data: %d", this.id); - this.push(event.data); + log("request port received data: %s", this.id); + this.push(data); } }; } @@ -59,34 +59,34 @@ class Socket extends Duplex { this.remotePort = this._getRemotePort(); } private _getRemoteAddress() { - const _url = new URL(this.incomingRequest.url || globalThis.location.href); + const _url = new URL(this.incomingRequest.url || defaultUrl().href); return _url.hostname; } private _getRemoteFamily() { return "IPv4"; } private _getRemotePort() { - const _url = new URL(this.incomingRequest.url || globalThis.location.href); + const _url = new URL(this.incomingRequest.url || defaultUrl().href); return normalizedPort(_url); } _destroy(error?: Error, callback?: (error?: Error) => void): void { - log("destroying socket: %d", this.id); + log("destroying socket: %s", this.id); this.ports.responsePort.close(); this.ports.requestPort?.close(); callback?.(error); } _write(data: any, encoding?: BufferEncoding, callback?: (error?: Error) => void): void { - log("writing to socket: %d with encoding", this.id, encoding); - assert("buffer" in data); - this.ports.responsePort.postMessage(data, [data.buffer]); + log("writing to socket: %s with encoding", this.id, encoding); + assert("buffer" in data, "fakettp: data must be a buffer"); + this.ports.responsePort.postMessage(data); callback?.(); } _read(size: number): void { if (canRequestHaveBody(this.incomingRequest.method)) { - log("reading from socket: %d", this.id); + log("reading from socket: %s", this.id); this.ports.requestPort?.postMessage(size); } else { - log("socket cannot have more data: %d", this.id); + log("socket cannot have more data: %s", this.id); this.push(null); } } @@ -127,12 +127,12 @@ export class ServerResponse extends Writable { } // https://github.com/expressjs/session/pull/908/files get _header() { - log("deprecated _header for server response"); + // log("deprecated _header for server response"); // too much log spam return !!this.headersSent; } // https://github.com/expressjs/session/pull/908/files _implicitHeader() { - log("deprecated _implicitHeader for server response"); + // log("deprecated _implicitHeader for server response"); // too much log spam this.writeHead(this.statusCode, this.statusMessage, this.getHeaders()); } writeHead(statusCode: number, statusMessage?: string, headers?: Record) { @@ -175,14 +175,14 @@ export class ServerResponse extends Writable { } } private _getRemoteAddress() { - const _url = new URL(this.incomingRequest.url || globalThis.location.href); + const _url = new URL(this.incomingRequest.url || defaultUrl().href); return _url.hostname; } private _getRemoteFamily() { return "IPv4"; } private _getRemotePort() { - const _url = new URL(this.incomingRequest.url || globalThis.location.href); + const _url = new URL(this.incomingRequest.url || defaultUrl().href); return normalizedPort(_url); } } @@ -204,7 +204,7 @@ export class IncomingMessage extends Readable { this.push(null); }); this.socket.on("data", (chunk: any) => { - log("data received in incoming message from socket: %d", this.socket.id); + log("data received in incoming message from socket: %s", this.socket.id); this.push(chunk); }); this.headers = this._getHeaders(); @@ -215,10 +215,10 @@ export class IncomingMessage extends Readable { } _read(size: number): void { if (this.complete) { - log("incoming message does not have data: %d", this.socket.id); + log("incoming message does not have data: %s", this.socket.id); return; } else { - log("incoming message needs data: %d", this.socket.id); + log("incoming message needs data: %s", this.socket.id); this.socket.read(size); } } @@ -255,15 +255,9 @@ export class IncomingMessage extends Readable { } class Server extends EventEmitter { - private _host = defaultHost; - private _port = +defaultPort; - private _listening = false; - constructor() { - log("creating fakettp server"); - assert(isRunningInBrowserWindow(), "fakettp: Server must be created in main thread."); - assert("serviceWorker" in navigator, "fakettp: ServiceWorkers are not supported."); - super(); - } + private _host = defaultHost(); + private _port = +defaultPort(); + private _dispose: Function | null = null; address() { const { _host, _port } = this; return { @@ -279,10 +273,13 @@ class Server extends EventEmitter { toString() { return `${this.address}:${this.port}`; }, + toJSON() { + return this.toString(); + }, }; } get listening() { - return this._listening; + return this._dispose !== null; } listen(port?: number, hostname?: string, listeningListener?: () => void): this; listen(port?: number, listeningListener?: () => void): this; @@ -304,34 +301,35 @@ class Server extends EventEmitter { this._port = args[0].port || this._port; this._host = args[0].host || this._host; } - log("listening on address: %o", this.address()); + log("listening on address: %s", JSON.stringify(this.address())); const _last = args.pop(); const _done = typeof _last === "function" ? (_last as (error?: Error) => void) : () => {}; this.once("error", _done); this.once("listening", _done); - if (this._listening) { + if (this.listening) { log("already listening"); const error = new Error("Already listening."); this.emit("error", error); } else { log("starting to believe..."); reload() - .then(() => getWorker()) - .then(() => { - this._listening = true; + .then(getContext) + .then((ctx) => { log("service worker ready"); this.once("close", () => { log("closing service worker"); - this._listening = false; + this._dispose?.(); + this._dispose = null; unload(); }); this.emit("listening"); - navigator.serviceWorker.addEventListener("message", (event: MessageEvent) => { - log("message received from service worker"); + this._dispose = ctx.readMessages((event: SerializedRequest) => { + if (!("id" in event && "body" in event && "url" in event)) return; + log("message received from service worker: %o", event); const responseChannel = new MessageChannel(); const responsePort = responseChannel.port1; - const requestPort = event.ports[0]; - const request = deserializeRequest(event.data); + const requestPort = new MessagePort(event.body as unknown as string); + const request = deserializeRequest(event); const socket = new Socket(request, { responsePort, requestPort }); this.emit("connection", socket); const message = new IncomingMessage(request, socket); @@ -341,18 +339,14 @@ class Server extends EventEmitter { log("closing request stream"); response.headersSent = true; log("responding to service worker"); - event.source.postMessage( - { - id: request.id, - status: response.statusCode, - statusText: response.statusMessage, - headers: response.getHeaders(), - } as SerializedResponse, - { - transfer: [responseChannel.port2], - targetOrigin: event.origin, - } - ); + const payload: SerializedResponse = { + id: request.id, + body: responseChannel.port2.toString(), + status: response.statusCode, + statusText: response.statusMessage, + headers: response.getHeaders(), + }; + ctx.postMessage(payload); }; response.once("finish", () => { if (message.complete) _wrapUp(); @@ -378,45 +372,12 @@ class Server extends EventEmitter { } } -async function getWorker() { - return await navigator.serviceWorker.ready.then((registration) => { - return registration.active || registration.installing || registration.waiting; - }); -} - -export async function reload() { - navigator.serviceWorker.register(getBundledWorkerFileName()); - await waitForWorkerLoad(); +async function reload() { + await getContext().reloadWorker?.(); } -export async function unload() { - navigator.serviceWorker.ready.then((r) => r.unregister()); - navigator.serviceWorker.register("nosw.js"); - await waitForWorkerStop(); -} - -async function waitForWorkerStop() { - while (true) { - try { - const response = await fetch(`/__status__`); - if (response.status !== 200) break; - } catch (error) { - log("error fetching /__status__: %o", error); - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } -} - -async function waitForWorkerLoad() { - while (true) { - try { - const response = await fetch(`/__status__`); - if (response.status === 200) break; - } catch (error) { - log("error fetching /__status__: %o", error); - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } +async function unload() { + await getContext().unloadWorker?.(); } export function createProxyServer(requestListener?: RequestListener): Server { diff --git a/src/sw.ts b/src/sw.ts index 8348ad9..8cae36f 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -2,24 +2,53 @@ declare const globalThis: ServiceWorkerGlobalScope; import debug from "debug"; import assert from "assert"; +import { EventEmitter } from "events"; + +import { MessagePort } from "./channel"; +import { type Context, setContext } from "./context"; import { - monotonicId, - getExcludedPaths, + uniqueId, + Singleton, + isDebugEnabled, + type FullConfig, serializeRequest, SerializedResponse, + getConfigFromLocation, isRunningInServiceWorker, MessagePortToReadableStream, } from "./common"; +if (isDebugEnabled()) debug.enable("*"); + const log = debug("fakettp:sw"); +const emitter = new EventEmitter(); const SEARCH_VALUE = "polling"; const SEARCH_PARAM = "transport"; const SOCKET_TIMEOUT = 120000; // 2 minutes +emitter.setMaxListeners(0); // fixme + function isLongPolling(url: URL) { return url.searchParams.has(SEARCH_PARAM) && url.searchParams.get(SEARCH_PARAM) === SEARCH_VALUE; } +async function getClients() { + return globalThis.clients.matchAll({ type: "window" }); +} + +class WindowClientContext implements Context { + constructor(private readonly client: WindowClient) {} + postMessage(message: any) { + this.client.postMessage(message); + } + readMessages(callback: any) { + emitter.on("message", callback); + return () => { + emitter.off("message", callback); + }; + } +} + async function onFetch(this: Listeners, ev: FetchEvent) { const _bypass = () => ev.respondWith(fetch(ev.request)); log("fetch event: %s", ev.request.url); @@ -28,47 +57,67 @@ async function onFetch(this: Listeners, ev: FetchEvent) { return _bypass(); } if ("isReload" in ev && ev.isReload) { - log("request is a reload. disarming: %s", ev.request.url); + log("request is a reload. bypassing: %s", ev.request.url); return _bypass(); } if (ev.request.url.endsWith("/__status__")) { log("request is a status check: %s", ev.request.url); return ev.respondWith(new Response("OK")); } - if (getExcludedPaths().some((path) => ev.request.url.endsWith(path))) { - log("request is excluded from interception: %s", ev.request.url); - return _bypass(); + if (ev.request.url.endsWith("/__self_destruct__")) { + log("request is a self destruct: %s", ev.request.url); + emitter.emit("self-destruct"); + await globalThis.registration.unregister(); + emitter.removeAllListeners(); + this.clear(); + globalThis.removeEventListener("fetch", onFetch); + globalThis.removeEventListener("message", onMessage); + globalThis.removeEventListener("install", onInstall); + globalThis.removeEventListener("activate", onActivate); + return ev.respondWith(new Response("OK")); } - let timeoutResponse: NodeJS.Timeout | null = null; log("processing fetch event: %s", ev.request.url); - const eventId = monotonicId(); const requestUrl = new URL(ev.request.url); - const work = globalThis.clients.matchAll({ type: "window" }).then((clients) => + const config = getConfig(); + const include = config.include.some((re) => re.test(requestUrl.href)); + const exclude = config.exclude.some((re) => re.test(requestUrl.href)); + if (exclude || !include) { + log("bypassing '%s' include: %o, exclude: %o config: %o", ev.request.url, include, exclude, config); + return _bypass(); + } + let abortedResponse: () => void | null = null; + let timeoutResponse: NodeJS.Timeout | null = null; + const eventId = uniqueId(); + const work = getClients().then((clients) => Promise.any([ ...clients.map((mt) => { return new Promise(async (resolve) => { try { const requestSerialized = await serializeRequest(ev.request); const requestId = requestSerialized.id; - log("fetch event: %s, id: %d", ev.request.url, requestId); - const cb = (event: MessageEvent) => { + log("fetch event: %s, id: %s", ev.request.url, requestId); + const cb = (responseInit: SerializedResponse) => { if (timeoutResponse) { clearTimeout(timeoutResponse); timeoutResponse = null; } - log("fetch event: %s, id: %d, response event: %s", ev.request.url, requestId, event.data.id); + log("fetch event: %s, id: %s, response event: %s", ev.request.url, requestId, responseInit.id); this.delete(requestId); - const { data: responseInit } = event; const responseId = responseInit.id; - assert(responseId === requestId, "request-response pair id mismatch"); - const responseBody = MessagePortToReadableStream(event.ports[0]); - log("responding to fetch event: %s, id: %d", ev.request.url, requestId); + assert(responseId === requestId, "fakettp: request-response pair id mismatch"); + log("streaming response: %s", responseInit.body); + const responseBody = MessagePortToReadableStream( + new MessagePort(responseInit.body, new WindowClientContext(mt)) + ); + log("responding to fetch event: %s, id: %s", ev.request.url, requestId); resolve(new Response(responseBody, responseInit)); }; Object.assign(cb, { eventId }); this.set(requestId, cb); - const { body: requestBody, ...requestRest } = requestSerialized; - mt.postMessage(requestRest, requestBody ? [requestBody] : []); + mt.postMessage({ + ...requestSerialized, + body: requestSerialized.body ? requestSerialized.body.toString() : null, + }); } catch (err) { log("sw fetch error: %o", err); } @@ -78,9 +127,9 @@ async function onFetch(this: Listeners, ev: FetchEvent) { if (!isLongPolling(requestUrl)) { timeoutResponse = setTimeout(() => { timeoutResponse = null; - const eventCbs: number[] = []; + const eventCbs: string[] = []; this.forEach((cb, id) => { - assert("eventId" in cb); + assert("eventId" in cb, "fakettp: missing eventId in cb"); if (cb.eventId === eventId) { eventCbs.push(id); } @@ -97,20 +146,39 @@ async function onFetch(this: Listeners, ev: FetchEvent) { }, SOCKET_TIMEOUT); } }), - ]) + new Promise((resolve) => { + abortedResponse = () => { + resolve( + new Response(null, { + statusText: "Service Unavailable", + status: 503, + }) + ); + }; + emitter.once("self-destruct", abortedResponse); + }), + ]).finally(() => { + if (timeoutResponse) { + clearTimeout(timeoutResponse); + timeoutResponse = null; + } + if (abortedResponse) { + emitter.off("self-destruct", abortedResponse); + } + }) ); ev.respondWith(work); } -function onMessage(this: Listeners, event: MessageEvent) { - const { data: responseInit } = event; - const responseId = responseInit.id; - if (this.has(responseId)) { - const messageListener = this.get(responseId); - messageListener(event as MessageEvent); +function onMessage(this: Listeners, event: MessageEvent) { + if ("id" in event.data && this.has(event.data.id)) { + log("got fetch response event: %o", event.data); + const messageListener = this.get(event.data.id); + messageListener(event.data); } else { - log("message event: %d, no listener found", responseId); + log("got RPC event: %o", event.data); + emitter.emit("message", event.data); } } @@ -131,14 +199,41 @@ function onActivate(event: ExtendableEvent) { return globalThis.clients.claim(); } -export type Listeners = Map) => void>; +export type Listeners = Map void>; -export function createProxyClient() { - log("creating sw client"); - assert(isRunningInServiceWorker()); +function buildConfigFromLocation(): FullConfig { + const locationConfig = getConfigFromLocation(); + return { + include: locationConfig.include.map((i) => new RegExp(i)), + exclude: locationConfig.exclude.map((e) => new RegExp(e)), + }; +} + +const CONFIG = new Singleton(buildConfigFromLocation); + +const getConfig = () => CONFIG.Get(); + +export function installProxyWorker() { + log("install proxy worker"); + assert(isRunningInServiceWorker(), "fakettp: createProxyClient != sw."); const listeners: Listeners = new Map(); globalThis.addEventListener("fetch", onFetch.bind(listeners)); globalThis.addEventListener("message", onMessage.bind(listeners)); globalThis.addEventListener("install", onInstall); globalThis.addEventListener("activate", onActivate); + setContext({ + postMessage: (data) => { + getClients().then((clients) => { + clients.forEach((client) => { + client.postMessage(data); + }); + }); + }, + readMessages: (cb) => { + emitter.on("message", cb); + return () => { + emitter.off("message", cb); + }; + }, + }); } diff --git a/test/channel.test.ts b/test/channel.test.ts new file mode 100644 index 0000000..c36a3f3 --- /dev/null +++ b/test/channel.test.ts @@ -0,0 +1,95 @@ +import { resolve } from "path"; +import { fork } from "child_process"; +import { MessageChannel, MessagePort } from "../src/channel"; +import { type Context } from "../src/context"; +import { EventEmitter } from "events"; +import * as assert from "uvu/assert"; +import { suite } from "uvu"; + +const it = suite("MessageChannel tests"); + +class SameThreadContext implements Context { + readonly emitter = new EventEmitter(); + postMessage(message: any) { + this.emitter.emit("message", message); + } + readMessages(cb: (message: any) => void | Promise) { + this.emitter.on("message", cb); + return () => { + this.emitter.off("message", cb); + }; + } + unloadWorker = () => assert.ok(false); + reloadWorker = () => assert.ok(false); +} + +class SubProcessContext implements Context { + readonly child = fork(resolve(__dirname, "subprocess.ts")); + postMessage(message: any) { + this.child.send(message); + } + readMessages(cb: (message: any) => void | Promise) { + this.child.on("message", cb); + return () => { + this.child.off("message", cb); + }; + } + unloadWorker = () => assert.ok(false); + reloadWorker = () => assert.ok(false); +} + +it("should correctly serialize ports", async () => { + const testContext = new SameThreadContext(); + const testChannel = new MessageChannel(testContext); + const serialized = { ...testChannel }; + assert.equal(serialized.port1.toJSON(), testChannel.port1.toString()); + assert.equal(serialized.port2.toJSON(), testChannel.port2.toString()); +}); + +it("should be able to send messages through MessagePort (same thread)", async () => { + const testContext = new SameThreadContext(); + const testPort = new MessagePort("sample", testContext); + const message = { hello: "world" }; + const promise = new Promise((resolve) => { + testPort.onmessage = resolve; + }); + testPort.postMessage(message); + assert.equal(JSON.stringify(await promise), JSON.stringify(message)); +}); + +it("should be able to send messages through MessagePort (multithread)", async () => { + const testContext = new SubProcessContext(); + const testPort = new MessagePort("shared", testContext); + const message = { hello: "world" }; + const promise = new Promise((resolve) => { + testPort.onmessage = resolve; + }); + testContext.child.send({ port: testPort.toString(), message }); + assert.equal(JSON.stringify(await promise), JSON.stringify(message)); + testContext.child.kill(); +}); + +it("should be able to send messages through MessageChannel (same thread)", async () => { + const testContext = new SameThreadContext(); + const testChannel = new MessageChannel(testContext); + const message = { hello: "world" }; + const promise = new Promise((resolve) => { + testChannel.port2.onmessage = resolve; + }); + testChannel.port1.postMessage(message); + assert.equal(JSON.stringify(await promise), JSON.stringify(message)); +}); + +it("should be able to send messages through MessageChannel (multithread)", async () => { + const testContext = new SubProcessContext(); + const testChannel = new MessageChannel(testContext); + const message = { hello: "world" }; + const promise = new Promise((resolve) => { + testChannel.port2.onmessage = resolve; + }); + testContext.child.send({ port: testChannel.port1.toString(), message }); + assert.equal(JSON.stringify(await promise), JSON.stringify(message)); + testContext.child.kill(); +}); + +it.run(); diff --git a/test/server.test.js b/test/server.test.js index 7a82472..83b7a2a 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -22,6 +22,9 @@ describe("http.Server", () => { window.server = server; }); await expect(browser.execute(() => fetch("https://example.com").then(({ ok }) => ok))).resolves.toBe(true); + await expect(browser.execute(() => fetch("https://example.com").then((res) => res.text()))).resolves.toBe( + "hello world" + ); await expect(browser.executeAsync((done) => window.server.close(done))).resolves.toBeNull(); await expect( browser.execute(() => @@ -44,7 +47,6 @@ describe("http.Server", () => { req.on("data", (chunk) => { reqBody += chunk.toString(); }); - req.on("end", () => {}); res.writeHead(200, { "Content-Type": "text/plain" }); res.end('{"yo":"nice"}'); }) diff --git a/test/subprocess.ts b/test/subprocess.ts new file mode 100644 index 0000000..0b32f1a --- /dev/null +++ b/test/subprocess.ts @@ -0,0 +1,26 @@ +import assert from "assert"; +import { MessagePort } from "../src/channel"; +import { type Context } from "../src/context"; +class SubProcessContext implements Context { + postMessage = (message: any) => { + assert.ok(process.send); + process.send(message); + }; + readMessages = (cb: any) => { + process.on("message", cb); + return () => { + process.off("message", cb); + }; + }; + reloadWorker = () => assert.ok(false); + unloadWorker = () => assert.ok(false); +} +process.on("message", ({ port, message }) => { + if (typeof port === "string") { + const _port = new MessagePort(port, new SubProcessContext()); + _port.start(); + setTimeout(() => { + _port.postMessage(message); + }, 1000); + } +}); diff --git a/tsconfig.json b/tsconfig.json index 7535afd..c21d699 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "allowJs": true, "lib": ["ES2021", "DOM", "WebWorker"], "skipDefaultLibCheck": true, + "skipLibCheck": true, "outDir": "dist/build" }, "files": ["src/index.ts"], diff --git a/webpack.config.ts b/webpack.config.ts index 383d9d4..e84a869 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -1,10 +1,11 @@ import fs from "fs"; import path from "path"; -import webpack, { ProvidePlugin } from "webpack"; +import webpack from "webpack"; import HtmlWebpackPlugin from "html-webpack-plugin"; import CopyWebpackPlugin from "copy-webpack-plugin"; import TerserWebpackPlugin from "terser-webpack-plugin"; import WebpackShellPlugin from "webpack-shell-plugin-next"; +import { globSync } from "glob"; import "webpack-dev-server"; @@ -33,9 +34,19 @@ const mainConfig: webpack.Configuration = { static: DIST, }, plugins: [ - new ProvidePlugin({ + new webpack.ProvidePlugin({ process: "process/browser", }), + new HtmlWebpackPlugin({ + filename: "fakettp.html", + template: "./src/frame.html", + minify: { + html5: true, + minifyCSS: true, + minifyJS: false, + collapseWhitespace: true, + }, + }), new CopyWebpackPlugin({ patterns: [ { @@ -46,8 +57,8 @@ const mainConfig: webpack.Configuration = { delete pkg.private; delete pkg.scripts; delete pkg.devDependencies; - pkg.main = "build/index.js"; - pkg.files = ["LICENSE", "README.md", mainConfig.output?.filename, noswConfig.output?.filename, "build"]; + pkg.main = "./fakettp.js"; + pkg.files = ["LICENSE", "README.md", "index.d.ts", "fakettp.js", "fakettp.html", "nosw.js"]; return JSON.stringify(pkg, null, 2); }, }, @@ -59,6 +70,10 @@ const mainConfig: webpack.Configuration = { from: "LICENSE", to: DIST, }, + { + from: "index.d.ts", + to: DIST, + }, ], }), ], @@ -97,13 +112,6 @@ const mainConfig: webpack.Configuration = { }, }; -mainConfig.plugins?.push( - new webpack.DefinePlugin({ - "process.env.FAKETTP_MODE": JSON.stringify(mainConfig.mode), - "process.env.FAKETTP_MAIN": JSON.stringify(mainConfig.output?.filename), - }) -); - if (mainConfig.mode === "development") { mainConfig.devtool = "inline-source-map"; } else { @@ -228,9 +236,12 @@ function makeTemplateIndexContent(...names: string[]) {

for best results, view this page in Chrome and in incognito mode.

Examples:

    -${names.map((name) => `
  • ${name}
  • `).join("\n")} + ${names.map((name) => `
  • ${name}
  • `).join("\n")}
+ \n`; } @@ -258,4 +269,8 @@ function createConfigForExamples(...names: string[]) { return configs; } -export default [mainConfig, noswConfig, ...createConfigForExamples("express", "express-static", "socket-io")]; +export default [ + mainConfig, + noswConfig, + ...createConfigForExamples(...globSync("ext/samples/*.ts").map((f) => path.basename(f, ".ts"))), +];