@secure-exec/browser 0.2.0-rc.2 → 0.3.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,377 @@
1
+ import { createInMemoryFileSystem, InMemoryFileSystem, } from "./os-filesystem.js";
2
+ export const allowAllFs = () => true;
3
+ export const allowAllNetwork = () => true;
4
+ export const allowAllChildProcess = () => true;
5
+ export const allowAllEnv = () => true;
6
+ export const allowAll = {
7
+ fs: allowAllFs,
8
+ network: allowAllNetwork,
9
+ childProcess: allowAllChildProcess,
10
+ env: allowAllEnv,
11
+ };
12
+ function normalizePath(inputPath) {
13
+ if (!inputPath)
14
+ return "/";
15
+ let normalized = inputPath.startsWith("/") ? inputPath : `/${inputPath}`;
16
+ normalized = normalized.replace(/\/+/g, "/");
17
+ if (normalized.length > 1 && normalized.endsWith("/")) {
18
+ normalized = normalized.slice(0, -1);
19
+ }
20
+ const parts = normalized.split("/");
21
+ const resolved = [];
22
+ for (const part of parts) {
23
+ if (!part || part === ".")
24
+ continue;
25
+ if (part === "..") {
26
+ resolved.pop();
27
+ continue;
28
+ }
29
+ resolved.push(part);
30
+ }
31
+ return resolved.length === 0 ? "/" : `/${resolved.join("/")}`;
32
+ }
33
+ function dirname(inputPath) {
34
+ const normalized = normalizePath(inputPath);
35
+ if (normalized === "/")
36
+ return "/";
37
+ const parts = normalized.split("/").filter(Boolean);
38
+ return parts.length <= 1 ? "/" : `/${parts.slice(0, -1).join("/")}`;
39
+ }
40
+ function permissionAllowed(decision) {
41
+ if (decision === undefined)
42
+ return true;
43
+ if (typeof decision === "boolean")
44
+ return decision;
45
+ return "allowed" in decision ? decision.allowed : decision.allow;
46
+ }
47
+ export function filterEnv(env, permissions) {
48
+ const source = env ?? {};
49
+ if (!permissions?.env)
50
+ return { ...source };
51
+ const output = {};
52
+ for (const [name, value] of Object.entries(source)) {
53
+ if (permissionAllowed(permissions.env({ name, value }))) {
54
+ output[name] = value;
55
+ }
56
+ }
57
+ return output;
58
+ }
59
+ export function createEnosysError(operation) {
60
+ const error = new Error(`ENOSYS: ${operation} is not supported`);
61
+ error.code = "ENOSYS";
62
+ return error;
63
+ }
64
+ export function createFsStub() {
65
+ return createInMemoryFileSystem();
66
+ }
67
+ export function createNetworkStub() {
68
+ return {
69
+ async fetch() {
70
+ throw createEnosysError("network.fetch");
71
+ },
72
+ async dnsLookup() {
73
+ return { error: "DNS not supported", code: "ENOSYS" };
74
+ },
75
+ async httpRequest() {
76
+ throw createEnosysError("network.httpRequest");
77
+ },
78
+ };
79
+ }
80
+ export function createCommandExecutorStub() {
81
+ return {
82
+ spawn() {
83
+ throw createEnosysError("child_process.spawn");
84
+ },
85
+ };
86
+ }
87
+ export function wrapFileSystem(filesystem, permissions) {
88
+ if (!permissions?.fs)
89
+ return filesystem;
90
+ const check = (path, operation) => {
91
+ if (!permissionAllowed(permissions.fs?.({ path, operation }))) {
92
+ throw new Error(`EACCES: blocked ${operation} on '${path}'`);
93
+ }
94
+ };
95
+ return {
96
+ readFile(path) {
97
+ check(path, "readFile");
98
+ return filesystem.readFile(path);
99
+ },
100
+ readTextFile(path) {
101
+ check(path, "readTextFile");
102
+ return filesystem.readTextFile(path);
103
+ },
104
+ readDir(path) {
105
+ check(path, "readDir");
106
+ return filesystem.readDir(path);
107
+ },
108
+ readDirWithTypes(path) {
109
+ check(path, "readDirWithTypes");
110
+ return filesystem.readDirWithTypes(path);
111
+ },
112
+ writeFile(path, content) {
113
+ check(path, "writeFile");
114
+ return filesystem.writeFile(path, content);
115
+ },
116
+ createDir(path) {
117
+ check(path, "createDir");
118
+ return filesystem.createDir(path);
119
+ },
120
+ mkdir(path, options) {
121
+ check(path, "mkdir");
122
+ return filesystem.mkdir(path, options);
123
+ },
124
+ exists(path) {
125
+ check(path, "exists");
126
+ return filesystem.exists(path);
127
+ },
128
+ stat(path) {
129
+ check(path, "stat");
130
+ return filesystem.stat(path);
131
+ },
132
+ removeFile(path) {
133
+ check(path, "removeFile");
134
+ return filesystem.removeFile(path);
135
+ },
136
+ removeDir(path) {
137
+ check(path, "removeDir");
138
+ return filesystem.removeDir(path);
139
+ },
140
+ rename(oldPath, newPath) {
141
+ check(oldPath, "rename");
142
+ check(newPath, "rename");
143
+ return filesystem.rename(oldPath, newPath);
144
+ },
145
+ realpath(path) {
146
+ check(path, "realpath");
147
+ return filesystem.realpath(path);
148
+ },
149
+ symlink(target, linkPath) {
150
+ check(linkPath, "symlink");
151
+ return filesystem.symlink(target, linkPath);
152
+ },
153
+ readlink(path) {
154
+ check(path, "readlink");
155
+ return filesystem.readlink(path);
156
+ },
157
+ lstat(path) {
158
+ check(path, "lstat");
159
+ return filesystem.lstat(path);
160
+ },
161
+ link(oldPath, newPath) {
162
+ check(oldPath, "link");
163
+ check(newPath, "link");
164
+ return filesystem.link(oldPath, newPath);
165
+ },
166
+ chmod(path, mode) {
167
+ check(path, "chmod");
168
+ return filesystem.chmod(path, mode);
169
+ },
170
+ chown(path, uid, gid) {
171
+ check(path, "chown");
172
+ return filesystem.chown(path, uid, gid);
173
+ },
174
+ utimes(path, atime, mtime) {
175
+ check(path, "utimes");
176
+ return filesystem.utimes(path, atime, mtime);
177
+ },
178
+ truncate(path, length) {
179
+ check(path, "truncate");
180
+ return filesystem.truncate(path, length);
181
+ },
182
+ pread(path, offset, length) {
183
+ check(path, "pread");
184
+ return filesystem.pread(path, offset, length);
185
+ },
186
+ pwrite(path, offset, data) {
187
+ check(path, "pwrite");
188
+ return filesystem.pwrite(path, offset, data);
189
+ },
190
+ };
191
+ }
192
+ export function wrapNetworkAdapter(adapter, permissions) {
193
+ if (!permissions?.network)
194
+ return adapter;
195
+ const check = (request) => {
196
+ if (!permissionAllowed(permissions.network?.(request))) {
197
+ throw new Error(`EACCES: blocked network access to '${request.url ?? request.host ?? ""}'`);
198
+ }
199
+ };
200
+ return {
201
+ async fetch(url, options) {
202
+ check({ url });
203
+ return adapter.fetch(url, options);
204
+ },
205
+ async dnsLookup(hostname) {
206
+ check({ host: hostname });
207
+ return adapter.dnsLookup(hostname);
208
+ },
209
+ async httpRequest(url, options) {
210
+ check({ url });
211
+ return adapter.httpRequest(url, options);
212
+ },
213
+ };
214
+ }
215
+ export async function mkdir(filesystem, path, options) {
216
+ if (typeof options === "boolean") {
217
+ return filesystem.mkdir(path, { recursive: options });
218
+ }
219
+ return filesystem.mkdir(path, options);
220
+ }
221
+ export async function loadFile(path, filesystem) {
222
+ try {
223
+ return await filesystem.readTextFile(path);
224
+ }
225
+ catch {
226
+ return null;
227
+ }
228
+ }
229
+ export async function resolveModule(specifier, fromPath, filesystem, _mode = "require") {
230
+ if (!specifier.startsWith(".") &&
231
+ !specifier.startsWith("/") &&
232
+ !specifier.startsWith("node:")) {
233
+ return specifier;
234
+ }
235
+ if (specifier.startsWith("node:")) {
236
+ return specifier;
237
+ }
238
+ let fromDir = normalizePath(fromPath);
239
+ try {
240
+ const fromStat = await filesystem.stat(fromDir);
241
+ if (!fromStat.isDirectory) {
242
+ fromDir = dirname(fromDir);
243
+ }
244
+ }
245
+ catch {
246
+ const basename = fromDir.split("/").at(-1) ?? "";
247
+ if (basename.includes(".")) {
248
+ fromDir = dirname(fromDir);
249
+ }
250
+ }
251
+ const base = specifier.startsWith("/")
252
+ ? specifier
253
+ : `${fromDir}/${specifier}`;
254
+ const candidates = [
255
+ normalizePath(base),
256
+ `${normalizePath(base)}.js`,
257
+ `${normalizePath(base)}.mjs`,
258
+ `${normalizePath(base)}/index.js`,
259
+ ];
260
+ for (const candidate of candidates) {
261
+ if (await filesystem.exists(candidate)) {
262
+ return candidate;
263
+ }
264
+ }
265
+ return null;
266
+ }
267
+ export function isESM(code, filePath) {
268
+ if (filePath?.endsWith(".mjs"))
269
+ return true;
270
+ return /\b(import|export)\b/.test(code);
271
+ }
272
+ export function transformDynamicImport(code) {
273
+ return code;
274
+ }
275
+ export const POLYFILL_CODE_MAP = {
276
+ fs: "module.exports = globalThis._fsModule;",
277
+ "node:fs": "module.exports = globalThis._fsModule;",
278
+ };
279
+ export function exposeCustomGlobal(name, value) {
280
+ globalThis[name] = value;
281
+ }
282
+ export function exposeMutableRuntimeStateGlobal(name, value) {
283
+ globalThis[name] = value;
284
+ }
285
+ export function getIsolateRuntimeSource(id) {
286
+ if (id === "overrideProcessCwd") {
287
+ return `
288
+ if (globalThis.process && globalThis.__runtimeProcessCwdOverride) {
289
+ globalThis.process.cwd = () => String(globalThis.__runtimeProcessCwdOverride);
290
+ }
291
+ `;
292
+ }
293
+ return "";
294
+ }
295
+ export function getRequireSetupCode() {
296
+ return `
297
+ (function () {
298
+ const callSyncBridge = (ref, ...args) => {
299
+ if (typeof ref === "function") {
300
+ return ref(...args);
301
+ }
302
+ if (ref && typeof ref.applySync === "function") {
303
+ return ref.applySync(undefined, args);
304
+ }
305
+ if (ref && typeof ref.applySyncPromise === "function") {
306
+ return ref.applySyncPromise(undefined, args);
307
+ }
308
+ return undefined;
309
+ };
310
+
311
+ const pathDirname = (value) => {
312
+ const normalized = String(value || "/").replace(/\\\\/g, "/");
313
+ if (normalized === "/") return "/";
314
+ const parts = normalized.split("/").filter(Boolean);
315
+ return parts.length <= 1 ? "/" : "/" + parts.slice(0, -1).join("/");
316
+ };
317
+
318
+ globalThis.require = function require(specifier) {
319
+ const polyfillSource = callSyncBridge(
320
+ globalThis._loadPolyfill,
321
+ specifier.replace(/^node:/, ""),
322
+ );
323
+ if (polyfillSource) {
324
+ const module = { exports: {} };
325
+ const fn = new Function("module", "exports", polyfillSource);
326
+ fn(module, module.exports);
327
+ return module.exports;
328
+ }
329
+
330
+ const currentModule = globalThis._currentModule || { dirname: "/" };
331
+ const resolved = callSyncBridge(
332
+ globalThis._resolveModuleSync,
333
+ specifier,
334
+ currentModule.dirname || "/",
335
+ "require",
336
+ );
337
+ if (!resolved) {
338
+ throw new Error("Cannot resolve module '" + specifier + "'");
339
+ }
340
+
341
+ const cache = globalThis._moduleCache || (globalThis._moduleCache = {});
342
+ if (cache[resolved]) {
343
+ return cache[resolved].exports;
344
+ }
345
+
346
+ const source = callSyncBridge(
347
+ globalThis._loadFileSync,
348
+ resolved,
349
+ "require",
350
+ );
351
+ if (source == null) {
352
+ throw new Error("Cannot load module '" + resolved + "'");
353
+ }
354
+
355
+ const module = { exports: {} };
356
+ cache[resolved] = module;
357
+ const previous = globalThis._currentModule;
358
+ globalThis._currentModule = { filename: resolved, dirname: pathDirname(resolved) };
359
+ try {
360
+ const fn = new Function(
361
+ "require",
362
+ "module",
363
+ "exports",
364
+ "__filename",
365
+ "__dirname",
366
+ source,
367
+ );
368
+ fn(globalThis.require, module, module.exports, resolved, pathDirname(resolved));
369
+ } finally {
370
+ globalThis._currentModule = previous;
371
+ }
372
+ return module.exports;
373
+ };
374
+ })();
375
+ `;
376
+ }
377
+ export { createInMemoryFileSystem, InMemoryFileSystem };
@@ -0,0 +1,46 @@
1
+ export declare const SYNC_BRIDGE_SIGNAL_STATE_INDEX = 0;
2
+ export declare const SYNC_BRIDGE_SIGNAL_STATUS_INDEX = 1;
3
+ export declare const SYNC_BRIDGE_SIGNAL_KIND_INDEX = 2;
4
+ export declare const SYNC_BRIDGE_SIGNAL_LENGTH_INDEX = 3;
5
+ export declare const SYNC_BRIDGE_SIGNAL_STATE_IDLE = 0;
6
+ export declare const SYNC_BRIDGE_SIGNAL_STATE_READY = 1;
7
+ export declare const SYNC_BRIDGE_STATUS_OK = 0;
8
+ export declare const SYNC_BRIDGE_STATUS_ERROR = 1;
9
+ export declare const SYNC_BRIDGE_KIND_NONE = 0;
10
+ export declare const SYNC_BRIDGE_KIND_TEXT = 1;
11
+ export declare const SYNC_BRIDGE_KIND_BINARY = 2;
12
+ export declare const SYNC_BRIDGE_KIND_JSON = 3;
13
+ export declare const SYNC_BRIDGE_SIGNAL_BYTES: number;
14
+ export declare const SYNC_BRIDGE_DEFAULT_WAIT_TIMEOUT_MS = 30000;
15
+ export declare const SYNC_BRIDGE_DEFAULT_DATA_BYTES: number;
16
+ export declare const SYNC_BRIDGE_MIN_DATA_BYTES: number;
17
+ export declare const SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
18
+ export type BrowserWorkerSyncOperation = "fs.readFile" | "fs.writeFile" | "fs.readFileBinary" | "fs.writeFileBinary" | "fs.readDir" | "fs.createDir" | "fs.mkdir" | "fs.rmdir" | "fs.exists" | "fs.stat" | "fs.lstat" | "fs.unlink" | "fs.rename" | "fs.realpath" | "fs.readlink" | "fs.symlink" | "fs.link" | "fs.chmod" | "fs.truncate" | "module.resolve" | "module.loadFile";
19
+ export interface BrowserSyncBridgeBuffers {
20
+ signalBuffer: SharedArrayBuffer;
21
+ dataBuffer: SharedArrayBuffer;
22
+ }
23
+ export interface BrowserSyncBridgePayload extends BrowserSyncBridgeBuffers {
24
+ timeoutMs?: number;
25
+ }
26
+ export interface BrowserWorkerSyncRequestMessage {
27
+ type: "sync-request";
28
+ controlToken: string;
29
+ requestId: number;
30
+ operation: BrowserWorkerSyncOperation;
31
+ args: unknown[];
32
+ }
33
+ export interface BrowserSyncBridgeErrorPayload {
34
+ message: string;
35
+ code?: string;
36
+ }
37
+ export declare function assertBrowserSyncBridgeSupport(): void;
38
+ export declare function getBrowserSyncBridgeDataBytes(payloadLimits?: {
39
+ base64TransferBytes?: number;
40
+ jsonPayloadBytes?: number;
41
+ }): number;
42
+ export declare function createBrowserSyncBridgePayload(payloadLimits?: {
43
+ base64TransferBytes?: number;
44
+ jsonPayloadBytes?: number;
45
+ }): BrowserSyncBridgePayload;
46
+ export declare function toBrowserSyncBridgeError(error: unknown): BrowserSyncBridgeErrorPayload;
@@ -0,0 +1,49 @@
1
+ export const SYNC_BRIDGE_SIGNAL_STATE_INDEX = 0;
2
+ export const SYNC_BRIDGE_SIGNAL_STATUS_INDEX = 1;
3
+ export const SYNC_BRIDGE_SIGNAL_KIND_INDEX = 2;
4
+ export const SYNC_BRIDGE_SIGNAL_LENGTH_INDEX = 3;
5
+ export const SYNC_BRIDGE_SIGNAL_STATE_IDLE = 0;
6
+ export const SYNC_BRIDGE_SIGNAL_STATE_READY = 1;
7
+ export const SYNC_BRIDGE_STATUS_OK = 0;
8
+ export const SYNC_BRIDGE_STATUS_ERROR = 1;
9
+ export const SYNC_BRIDGE_KIND_NONE = 0;
10
+ export const SYNC_BRIDGE_KIND_TEXT = 1;
11
+ export const SYNC_BRIDGE_KIND_BINARY = 2;
12
+ export const SYNC_BRIDGE_KIND_JSON = 3;
13
+ export const SYNC_BRIDGE_SIGNAL_BYTES = 4 * Int32Array.BYTES_PER_ELEMENT;
14
+ export const SYNC_BRIDGE_DEFAULT_WAIT_TIMEOUT_MS = 30_000;
15
+ export const SYNC_BRIDGE_DEFAULT_DATA_BYTES = 16 * 1024 * 1024;
16
+ export const SYNC_BRIDGE_MIN_DATA_BYTES = 64 * 1024;
17
+ export const SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
18
+ export function assertBrowserSyncBridgeSupport() {
19
+ if (typeof SharedArrayBuffer === "undefined") {
20
+ throw new Error("Browser runtime requires SharedArrayBuffer for sync filesystem and module loading parity");
21
+ }
22
+ if (typeof Atomics === "undefined" || typeof Atomics.wait !== "function") {
23
+ throw new Error("Browser runtime requires Atomics.wait for sync filesystem and module loading parity");
24
+ }
25
+ }
26
+ export function getBrowserSyncBridgeDataBytes(payloadLimits) {
27
+ return Math.max(payloadLimits?.base64TransferBytes ?? SYNC_BRIDGE_DEFAULT_DATA_BYTES, payloadLimits?.jsonPayloadBytes ?? 4 * 1024 * 1024, SYNC_BRIDGE_MIN_DATA_BYTES);
28
+ }
29
+ export function createBrowserSyncBridgePayload(payloadLimits) {
30
+ assertBrowserSyncBridgeSupport();
31
+ return {
32
+ signalBuffer: new SharedArrayBuffer(SYNC_BRIDGE_SIGNAL_BYTES),
33
+ dataBuffer: new SharedArrayBuffer(getBrowserSyncBridgeDataBytes(payloadLimits)),
34
+ timeoutMs: SYNC_BRIDGE_DEFAULT_WAIT_TIMEOUT_MS,
35
+ };
36
+ }
37
+ export function toBrowserSyncBridgeError(error) {
38
+ if (error instanceof Error) {
39
+ return {
40
+ message: error.message,
41
+ code: typeof error.code === "string"
42
+ ? error.code
43
+ : undefined,
44
+ };
45
+ }
46
+ return {
47
+ message: String(error),
48
+ };
49
+ }
@@ -4,6 +4,7 @@
4
4
  * Wraps the Web Worker API for spawning Workers.
5
5
  * Requires COOP/COEP headers for SharedArrayBuffer support.
6
6
  */
7
+ // biome-ignore lint/complexity/noStaticOnlyClass: This class is part of the public browser package API.
7
8
  export class BrowserWorkerAdapter {
8
9
  /**
9
10
  * Spawn a Web Worker for the given script URL.
@@ -1,4 +1,5 @@
1
- import type { OSConfig, ProcessConfig, ExecResult, RunResult, StdioChannel } from "@secure-exec/core";
1
+ import type { ExecResult, OSConfig, ProcessConfig, RunResult, StdioChannel, TimingMitigation } from "./runtime.js";
2
+ import type { BrowserSyncBridgePayload, BrowserWorkerSyncRequestMessage } from "./sync-bridge.js";
2
3
  export type SerializedPermissions = {
3
4
  fs?: string;
4
5
  network?: string;
@@ -10,6 +11,15 @@ export type BrowserWorkerExecOptions = {
10
11
  env?: Record<string, string>;
11
12
  cwd?: string;
12
13
  stdin?: string;
14
+ timingMitigation?: TimingMitigation;
15
+ };
16
+ export type BrowserWorkerExtensionRequestPayload = {
17
+ namespace: string;
18
+ payload: Uint8Array;
19
+ };
20
+ export type BrowserWorkerExtensionResponse = {
21
+ namespace: string;
22
+ payload: Uint8Array;
13
23
  };
14
24
  export type BrowserWorkerInitPayload = {
15
25
  processConfig?: ProcessConfig;
@@ -17,16 +27,22 @@ export type BrowserWorkerInitPayload = {
17
27
  permissions?: SerializedPermissions;
18
28
  filesystem?: "opfs" | "memory";
19
29
  networkEnabled?: boolean;
30
+ timingMitigation?: TimingMitigation;
20
31
  payloadLimits?: {
21
32
  base64TransferBytes?: number;
22
33
  jsonPayloadBytes?: number;
23
34
  };
35
+ syncBridge?: BrowserSyncBridgePayload;
24
36
  };
25
- export type BrowserWorkerRequestMessage = {
37
+ type BrowserWorkerControlMessage = {
38
+ controlToken: string;
39
+ };
40
+ export type BrowserWorkerRequestMessage = (BrowserWorkerControlMessage & {
26
41
  id: number;
27
42
  type: "init";
28
43
  payload: BrowserWorkerInitPayload;
29
- } | {
44
+ }) | {
45
+ controlToken: string;
30
46
  id: number;
31
47
  type: "exec";
32
48
  payload: {
@@ -35,6 +51,7 @@ export type BrowserWorkerRequestMessage = {
35
51
  captureStdio?: boolean;
36
52
  };
37
53
  } | {
54
+ controlToken: string;
38
55
  id: number;
39
56
  type: "run";
40
57
  payload: {
@@ -42,16 +59,21 @@ export type BrowserWorkerRequestMessage = {
42
59
  filePath?: string;
43
60
  captureStdio?: boolean;
44
61
  };
45
- } | {
62
+ } | (BrowserWorkerControlMessage & {
63
+ id: number;
64
+ type: "extension";
65
+ payload: BrowserWorkerExtensionRequestPayload;
66
+ }) | (BrowserWorkerControlMessage & {
46
67
  id: number;
47
68
  type: "dispose";
48
- };
49
- export type BrowserWorkerResponseMessage = {
69
+ });
70
+ export type BrowserWorkerResponseMessage = (BrowserWorkerControlMessage & {
50
71
  type: "response";
51
72
  id: number;
52
73
  ok: true;
53
- result: ExecResult | RunResult | true;
54
- } | {
74
+ result: ExecResult | RunResult | BrowserWorkerExtensionResponse | true;
75
+ }) | {
76
+ controlToken: string;
55
77
  type: "response";
56
78
  id: number;
57
79
  ok: false;
@@ -61,10 +83,11 @@ export type BrowserWorkerResponseMessage = {
61
83
  code?: string;
62
84
  };
63
85
  };
64
- export type BrowserWorkerStdioMessage = {
86
+ export type BrowserWorkerStdioMessage = BrowserWorkerControlMessage & {
65
87
  type: "stdio";
66
88
  requestId: number;
67
89
  channel: StdioChannel;
68
90
  message: string;
69
91
  };
70
- export type BrowserWorkerOutboundMessage = BrowserWorkerResponseMessage | BrowserWorkerStdioMessage;
92
+ export type BrowserWorkerOutboundMessage = BrowserWorkerResponseMessage | BrowserWorkerStdioMessage | BrowserWorkerSyncRequestMessage;
93
+ export {};