@blastlauncher/web 3.0.0-alpha.2 → 3.0.0-alpha.3
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.
- package/README.md +6 -6
- package/assets/blast-sources.json +1 -1
- package/assets/blast-web.js +14 -9
- package/dist/bundles.d.ts +4 -0
- package/dist/bundles.js +9 -1
- package/dist/capabilities.d.ts +2 -1
- package/dist/capabilities.js +6 -1
- package/dist/extension-files.d.ts +17 -0
- package/dist/extension-files.js +43 -0
- package/dist/sandbox.d.ts +9 -0
- package/dist/sandbox.js +24 -0
- package/dist/session.d.ts +2 -0
- package/dist/session.js +22 -36
- package/package.json +3 -3
package/dist/bundles.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { CommandMode } from "@blastlauncher/protocol";
|
|
|
3
3
|
export type CatalogArgument = {
|
|
4
4
|
name: string;
|
|
5
5
|
required: boolean;
|
|
6
|
+
/** The manifest argument type (`text`, `password`, `dropdown`). */
|
|
7
|
+
type?: string;
|
|
8
|
+
/** A dropdown argument's option values, in order. */
|
|
9
|
+
values?: string[];
|
|
6
10
|
};
|
|
7
11
|
export type CatalogManifest = {
|
|
8
12
|
name: string;
|
package/dist/bundles.js
CHANGED
|
@@ -92,7 +92,15 @@ export function parseBundleIndex(value) {
|
|
|
92
92
|
const entry = argument;
|
|
93
93
|
if (!entry || typeof entry.name !== "string" || typeof entry.required !== "boolean")
|
|
94
94
|
throw new Error(`Invalid bundle manifest argument: ${key}/${name}`);
|
|
95
|
-
|
|
95
|
+
const values = Array.isArray(entry.values)
|
|
96
|
+
? entry.values.filter((option) => typeof option === "string")
|
|
97
|
+
: [];
|
|
98
|
+
return {
|
|
99
|
+
name: entry.name,
|
|
100
|
+
required: entry.required,
|
|
101
|
+
...(typeof entry.type === "string" ? { type: entry.type } : {}),
|
|
102
|
+
...(values.length ? { values } : {}),
|
|
103
|
+
};
|
|
96
104
|
});
|
|
97
105
|
}
|
|
98
106
|
manifest = {
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -42,7 +42,8 @@ export type BrowserCapabilityOptions = {
|
|
|
42
42
|
storage?: BrowserStorage;
|
|
43
43
|
clipboard?: ClipboardBridge;
|
|
44
44
|
openUrl?: (url: string) => void;
|
|
45
|
-
|
|
45
|
+
/** `args` are the launched command's manifest argument values. */
|
|
46
|
+
launchCommand?: (source: string, command: string, args: Record<string, string>) => Promise<void>;
|
|
46
47
|
status?: (message: string, error?: boolean) => void;
|
|
47
48
|
openPreferences?: (scope: "extension" | "command", context: CapabilityContext) => void;
|
|
48
49
|
};
|
package/dist/capabilities.js
CHANGED
|
@@ -259,7 +259,12 @@ export function createBrowserCapabilities(options) {
|
|
|
259
259
|
}
|
|
260
260
|
if (!options.launchCommand)
|
|
261
261
|
return denial("Command launching is unavailable in this browser session");
|
|
262
|
-
|
|
262
|
+
const args = {};
|
|
263
|
+
if (params.arguments && typeof params.arguments === "object")
|
|
264
|
+
for (const [name, value] of Object.entries(params.arguments))
|
|
265
|
+
if (typeof value === "string")
|
|
266
|
+
args[name] = value;
|
|
267
|
+
await options.launchCommand(context.source, command, args);
|
|
263
268
|
return { ok: true, value: null };
|
|
264
269
|
}
|
|
265
270
|
if (request.name === "oauth.authorize")
|
|
@@ -15,6 +15,23 @@ export type FetchExtensionOptions = {
|
|
|
15
15
|
export declare function resolveExtensionSource(value: string): string;
|
|
16
16
|
/** Fetches an extension's files from a local fixture server or GitHub. */
|
|
17
17
|
export declare function fetchExtensionFiles(source: string, options?: FetchExtensionOptions): Promise<FetchedExtension>;
|
|
18
|
+
/**
|
|
19
|
+
* Where an extension file lives under the command's assets directory, or
|
|
20
|
+
* undefined when the bundle already holds it: `assets/*` sit at its top,
|
|
21
|
+
* `src/` data files (not code) keep their place below it, and other files
|
|
22
|
+
* (`package.json`) keep their path.
|
|
23
|
+
*/
|
|
24
|
+
export declare function assetRelativePath(path: string): string | undefined;
|
|
25
|
+
/** An extension's files keyed by `assetRelativePath`, as prebuilt bundles list them. */
|
|
26
|
+
export declare function extensionAssets(files: ReadonlyMap<string, BrowserFile>): Array<[string, BrowserFile]>;
|
|
27
|
+
/**
|
|
28
|
+
* The files a command's sandbox needs before it runs: the extension's assets
|
|
29
|
+
* (keyed by `assetRelativePath`) under `assetsPath` (decision 0013), its
|
|
30
|
+
* `package.json` also one level up (bundled code reads
|
|
31
|
+
* `__dirname/../package.json`), and markers that create the assets and support
|
|
32
|
+
* directories.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sandboxFiles(assets: Iterable<readonly [string, BrowserFile]>, assetsPath: string, supportPath: string): Array<[string, BrowserFile]>;
|
|
18
35
|
/** The source file a command's bundle starts from. */
|
|
19
36
|
export declare function commandEntry(files: Map<string, BrowserFile>, name: string): string;
|
|
20
37
|
/** Command names a manifest declares, in order. */
|
package/dist/extension-files.js
CHANGED
|
@@ -66,6 +66,49 @@ export async function fetchExtensionFiles(source, options = {}) {
|
|
|
66
66
|
const lockfile = files.get("/package-lock.json");
|
|
67
67
|
return { files, manifest, root, ...(typeof lockfile === "string" ? { lockfile } : {}) };
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Where an extension file lives under the command's assets directory, or
|
|
71
|
+
* undefined when the bundle already holds it: `assets/*` sit at its top,
|
|
72
|
+
* `src/` data files (not code) keep their place below it, and other files
|
|
73
|
+
* (`package.json`) keep their path.
|
|
74
|
+
*/
|
|
75
|
+
export function assetRelativePath(path) {
|
|
76
|
+
if (path.includes("/node_modules/"))
|
|
77
|
+
return undefined;
|
|
78
|
+
if (path.startsWith("/assets/"))
|
|
79
|
+
return path.slice("/assets/".length) || undefined;
|
|
80
|
+
if (path.startsWith("/src/"))
|
|
81
|
+
return /\.(?:cjs|css|html?|js|jsx|mjs|ts|tsx)$/iu.test(path)
|
|
82
|
+
? undefined
|
|
83
|
+
: path.slice("/src/".length);
|
|
84
|
+
return path.replace(/^\//u, "") || undefined;
|
|
85
|
+
}
|
|
86
|
+
/** An extension's files keyed by `assetRelativePath`, as prebuilt bundles list them. */
|
|
87
|
+
export function extensionAssets(files) {
|
|
88
|
+
return [...files].flatMap(([path, value]) => {
|
|
89
|
+
const relative = assetRelativePath(path);
|
|
90
|
+
return relative ? [[relative, value]] : [];
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The files a command's sandbox needs before it runs: the extension's assets
|
|
95
|
+
* (keyed by `assetRelativePath`) under `assetsPath` (decision 0013), its
|
|
96
|
+
* `package.json` also one level up (bundled code reads
|
|
97
|
+
* `__dirname/../package.json`), and markers that create the assets and support
|
|
98
|
+
* directories.
|
|
99
|
+
*/
|
|
100
|
+
export function sandboxFiles(assets, assetsPath, supportPath) {
|
|
101
|
+
const result = [
|
|
102
|
+
[`${supportPath}/.keep`, ""],
|
|
103
|
+
[`${assetsPath}/.keep`, ""],
|
|
104
|
+
];
|
|
105
|
+
for (const [relative, value] of assets) {
|
|
106
|
+
result.push([`${assetsPath}/${relative}`, value]);
|
|
107
|
+
if (relative === "package.json")
|
|
108
|
+
result.push([`${assetsPath.slice(0, assetsPath.lastIndexOf("/"))}/package.json`, value]);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
69
112
|
/** The source file a command's bundle starts from. */
|
|
70
113
|
export function commandEntry(files, name) {
|
|
71
114
|
for (const extension of ["tsx", "ts", "jsx", "js"]) {
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ export type SandboxOptions = {
|
|
|
18
18
|
};
|
|
19
19
|
/** `path` under `assetsUrl`, which defaults to the package's own `assets/` directory. */
|
|
20
20
|
export declare const assetUrl: (options: Pick<SandboxOptions, "assetsUrl">, path: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Writes files into the sandbox's own file system before a command runs.
|
|
23
|
+
* Writes to `getVFS()` never reach it (the sandbox page keeps its own
|
|
24
|
+
* VirtualFS, and almostnode's sync would decode binary files as text), and a
|
|
25
|
+
* message posted to the frame right after `createRuntime` can still land on
|
|
26
|
+
* its initial `about:blank`. `execute` is the channel every command already
|
|
27
|
+
* runs through, so the files go as one script, base64 for exact bytes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function writeSandboxFiles(runtime: Pick<SandboxRuntime, "execute">, files: ReadonlyArray<readonly [string, string | Uint8Array]>): Promise<void>;
|
|
21
30
|
export declare function disposeSandbox(runtime: SandboxRuntime | undefined): void;
|
|
22
31
|
/** Starts a sandbox now so the next `mount()` with the same options skips that wait. */
|
|
23
32
|
export declare function prewarm(options: SandboxOptions): void;
|
package/dist/sandbox.js
CHANGED
|
@@ -14,6 +14,30 @@ export const assetUrl = (options, path) => new URL(path, options.assetsUrl
|
|
|
14
14
|
? new URL(options.assetsUrl, globalThis.location?.href)
|
|
15
15
|
: packagedAssets()).toString();
|
|
16
16
|
const keyOf = (options) => [options.sandboxUrl, assetUrl(options, "./"), options.bundlesUrl, options.buildUrl].join("\n");
|
|
17
|
+
const base64 = (value) => {
|
|
18
|
+
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
19
|
+
let binary = "";
|
|
20
|
+
for (let start = 0; start < bytes.length; start += 0x8000)
|
|
21
|
+
binary += String.fromCharCode(...bytes.subarray(start, start + 0x8000));
|
|
22
|
+
return btoa(binary);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Writes files into the sandbox's own file system before a command runs.
|
|
26
|
+
* Writes to `getVFS()` never reach it (the sandbox page keeps its own
|
|
27
|
+
* VirtualFS, and almostnode's sync would decode binary files as text), and a
|
|
28
|
+
* message posted to the frame right after `createRuntime` can still land on
|
|
29
|
+
* its initial `about:blank`. `execute` is the channel every command already
|
|
30
|
+
* runs through, so the files go as one script, base64 for exact bytes.
|
|
31
|
+
*/
|
|
32
|
+
export async function writeSandboxFiles(runtime, files) {
|
|
33
|
+
const encoded = JSON.stringify(files.map(([path, value]) => [path, base64(value)]));
|
|
34
|
+
await runtime.execute(`const fs = require("fs");
|
|
35
|
+
for (const [path, data] of ${encoded}) {
|
|
36
|
+
fs.mkdirSync(path.slice(0, path.lastIndexOf("/")) || "/", { recursive: true });
|
|
37
|
+
fs.writeFileSync(path, Buffer.from(data, "base64"));
|
|
38
|
+
}
|
|
39
|
+
module.exports = null;`, "/blast/write-files.cjs");
|
|
40
|
+
}
|
|
17
41
|
export function disposeSandbox(runtime) {
|
|
18
42
|
try {
|
|
19
43
|
runtime?.terminate?.();
|
package/dist/session.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type WebMountOptions = {
|
|
|
26
26
|
fixtures?: boolean;
|
|
27
27
|
/** Show the extension's commands as the engine root view instead of running one. */
|
|
28
28
|
root?: boolean;
|
|
29
|
+
/** Manifest argument values for `command` (`LaunchProps.arguments`). */
|
|
30
|
+
arguments?: Record<string, string>;
|
|
29
31
|
/** Preference values, applied over the manifest defaults and saved values. */
|
|
30
32
|
preferences?: PreferenceValues;
|
|
31
33
|
/** Prebuilt bundle index (usually `/bundles.json`) used before fetching GitHub sources. */
|
package/dist/session.js
CHANGED
|
@@ -12,13 +12,13 @@ import { installForBrowser } from "./browser-install.js";
|
|
|
12
12
|
import { aliases } from "./bundle-config.js";
|
|
13
13
|
import { createBrowserCapabilities, createBrowserPreferenceStore, } from "./capabilities.js";
|
|
14
14
|
import { createDefaultRenderer, } from "./default-renderer.js";
|
|
15
|
-
import { commandEntry, fetchExtensionFiles, manifestCommands } from "./extension-files.js";
|
|
15
|
+
import { commandEntry, extensionAssets, fetchExtensionFiles, manifestCommands, sandboxFiles, } from "./extension-files.js";
|
|
16
16
|
import { createDefaultHandlers } from "./handlers.js";
|
|
17
17
|
import { createLaunchGeneration, withLaunchTimeout } from "./launch.js";
|
|
18
18
|
import { missingPreferences, parsePreferences, preferenceDefaults, preferenceFields, } from "@blastlauncher/protocol/preference-fields";
|
|
19
19
|
import { loadPrebuiltCommand } from "./prebuilt.js";
|
|
20
20
|
import { acceptsRuntimePortRequest } from "./runtime-port.js";
|
|
21
|
-
import { acquireSandbox, assetUrl, disposeSandbox } from "./sandbox.js";
|
|
21
|
+
import { acquireSandbox, assetUrl, disposeSandbox, writeSandboxFiles, } from "./sandbox.js";
|
|
22
22
|
const initialized = new WeakSet();
|
|
23
23
|
const LAUNCH_TIMEOUT_MS = 30_000;
|
|
24
24
|
const RUNTIME_ENTRY = "/packages/web/sandbox/runtime-entry.ts";
|
|
@@ -86,7 +86,7 @@ export function startSession(element, options) {
|
|
|
86
86
|
const result = root.input(value);
|
|
87
87
|
for (const effect of result.effects)
|
|
88
88
|
if (effect.type === "launch")
|
|
89
|
-
void run(effect.command);
|
|
89
|
+
void run(effect.command, {});
|
|
90
90
|
return { events: [], effects: [] };
|
|
91
91
|
};
|
|
92
92
|
const renderer = options.render
|
|
@@ -217,7 +217,7 @@ export function startSession(element, options) {
|
|
|
217
217
|
store.update(extensionValues, command, commandValues);
|
|
218
218
|
return supplied;
|
|
219
219
|
}
|
|
220
|
-
async function run(commandName) {
|
|
220
|
+
async function run(commandName, args) {
|
|
221
221
|
// A command launched from the running one (`launchCommand`) replaces it.
|
|
222
222
|
stopCommand();
|
|
223
223
|
const generation = launches.current();
|
|
@@ -230,12 +230,13 @@ export function startSession(element, options) {
|
|
|
230
230
|
: undefined;
|
|
231
231
|
if (!current())
|
|
232
232
|
return;
|
|
233
|
-
|
|
233
|
+
/** The extension's files by `assetRelativePath`, for the sandbox. */
|
|
234
|
+
let assets;
|
|
234
235
|
let declared;
|
|
235
236
|
let entry;
|
|
236
237
|
let code;
|
|
237
238
|
if (built) {
|
|
238
|
-
|
|
239
|
+
assets = [...built.files].map(([path, value]) => [path.replace(/^\//u, ""), value]);
|
|
239
240
|
declared = fromCatalog(built.manifest, commandName);
|
|
240
241
|
entry = `/src/${commandName}.tsx`;
|
|
241
242
|
code = built.code;
|
|
@@ -244,7 +245,7 @@ export function startSession(element, options) {
|
|
|
244
245
|
const extension = await fetchExtension();
|
|
245
246
|
if (!current())
|
|
246
247
|
return;
|
|
247
|
-
|
|
248
|
+
assets = extensionAssets(extension.files);
|
|
248
249
|
declared = fromManifest(extension.manifest, commandName, source);
|
|
249
250
|
entry = commandEntry(extension.files, commandName);
|
|
250
251
|
setStatus("Installing…", false, true);
|
|
@@ -306,30 +307,9 @@ export function startSession(element, options) {
|
|
|
306
307
|
const pathSegment = extensionName.replace(/[^A-Za-z0-9._-]/gu, "_") || "extension";
|
|
307
308
|
const supportPath = `/blast/support/${pathSegment}`;
|
|
308
309
|
const assetsPath = `/blast/assets/${pathSegment}`;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// A marker syncs directories created after the sandbox was initialised.
|
|
313
|
-
vfs.writeFileSync(`${supportPath}/.keep`, "");
|
|
314
|
-
vfs.writeFileSync(`${assetsPath}/.keep`, "");
|
|
315
|
-
for (const [path, value] of files) {
|
|
316
|
-
if (path.includes("/node_modules/"))
|
|
317
|
-
continue;
|
|
318
|
-
const relative = path.startsWith("/assets/")
|
|
319
|
-
? path.slice("/assets/".length)
|
|
320
|
-
: path.startsWith("/src/")
|
|
321
|
-
? /\.(?:cjs|css|html?|js|jsx|mjs|ts|tsx)$/iu.test(path)
|
|
322
|
-
? undefined
|
|
323
|
-
: path.slice("/src/".length)
|
|
324
|
-
: path.replace(/^\//u, "");
|
|
325
|
-
if (!relative)
|
|
326
|
-
continue;
|
|
327
|
-
const target = `${assetsPath}/${relative}`;
|
|
328
|
-
vfs.mkdirSync(target.slice(0, target.lastIndexOf("/")), { recursive: true });
|
|
329
|
-
vfs.writeFileSync(target, value);
|
|
330
|
-
if (path === "/package.json")
|
|
331
|
-
vfs.writeFileSync("/blast/assets/package.json", value);
|
|
332
|
-
}
|
|
310
|
+
await writeSandboxFiles(active, sandboxFiles(assets, assetsPath, supportPath));
|
|
311
|
+
if (!current())
|
|
312
|
+
return;
|
|
333
313
|
const context = { extensionName, commandName, source };
|
|
334
314
|
const capabilities = createBrowserCapabilities({
|
|
335
315
|
catalog: {
|
|
@@ -341,8 +321,8 @@ export function startSession(element, options) {
|
|
|
341
321
|
...(handlers.clipboard ? { clipboard: handlers.clipboard } : {}),
|
|
342
322
|
...(handlers.openUrl ? { openUrl: handlers.openUrl } : {}),
|
|
343
323
|
status: (text, error) => setStatus(text, error === true),
|
|
344
|
-
launchCommand: async (_source, command) => {
|
|
345
|
-
void run(command);
|
|
324
|
+
launchCommand: async (_source, command, launchArgs) => {
|
|
325
|
+
void run(command, launchArgs);
|
|
346
326
|
},
|
|
347
327
|
openPreferences: (scope) => {
|
|
348
328
|
if (!options.preferenceForm) {
|
|
@@ -352,7 +332,7 @@ export function startSession(element, options) {
|
|
|
352
332
|
// Saved preferences reach a command when it launches, so it runs again.
|
|
353
333
|
void askPreferences(declared, commandName, scope, { ...values }).then((supplied) => {
|
|
354
334
|
if (supplied && current())
|
|
355
|
-
void run(commandName);
|
|
335
|
+
void run(commandName, args);
|
|
356
336
|
});
|
|
357
337
|
},
|
|
358
338
|
});
|
|
@@ -442,7 +422,13 @@ export function startSession(element, options) {
|
|
|
442
422
|
port.postMessage({
|
|
443
423
|
type: "init",
|
|
444
424
|
protocol: 1,
|
|
445
|
-
command: {
|
|
425
|
+
command: {
|
|
426
|
+
extension: extensionName,
|
|
427
|
+
name: commandName,
|
|
428
|
+
entry,
|
|
429
|
+
mode,
|
|
430
|
+
...(Object.keys(args).length ? { arguments: { ...args } } : {}),
|
|
431
|
+
},
|
|
446
432
|
preferences: { ...values },
|
|
447
433
|
environment: {
|
|
448
434
|
extensionName,
|
|
@@ -473,7 +459,7 @@ export function startSession(element, options) {
|
|
|
473
459
|
if (!options.root) {
|
|
474
460
|
const command = options.command ?? manifestCommands((await fetchExtension()).manifest)[0];
|
|
475
461
|
if (!closed)
|
|
476
|
-
await run(command ?? "index");
|
|
462
|
+
await run(command ?? "index", { ...options.arguments });
|
|
477
463
|
return;
|
|
478
464
|
}
|
|
479
465
|
const manifest = (await fetchExtension()).manifest;
|
package/package.json
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@blastlauncher/client-core": "3.0.0-alpha.
|
|
13
|
-
"@blastlauncher/protocol": "3.0.0-alpha.
|
|
12
|
+
"@blastlauncher/client-core": "3.0.0-alpha.3",
|
|
13
|
+
"@blastlauncher/protocol": "3.0.0-alpha.3",
|
|
14
14
|
"semver": "7.8.5"
|
|
15
15
|
},
|
|
16
|
-
"version": "3.0.0-alpha.
|
|
16
|
+
"version": "3.0.0-alpha.3",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|