@tlockney/cdp-kernel 0.7.0 → 0.7.1
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 +4 -2
- package/dist/browser/kernel.js +136 -10
- package/dist/browser/kernel.js.map +2 -2
- package/dist/browser/mod.d.ts +6 -1
- package/dist/browser/mod.js +4 -1
- package/dist/browser/target.d.ts +23 -0
- package/dist/browser/target.js +62 -4
- package/dist/browser/worker.js +83 -12
- package/dist/browser/worker.js.map +3 -3
- package/dist/bun-target.d.ts +2 -0
- package/dist/bun-target.js +15 -2
- package/dist/cdp.d.ts +2 -0
- package/dist/cdp.js +34 -7
- package/dist/celld-target.js +3 -2
- package/dist/debugger.js +51 -5
- package/dist/kernel-core.d.ts +9 -0
- package/dist/kernel-core.js +73 -3
- package/dist/kernel.js +12 -0
- package/dist/main.js +11 -4
- package/dist/package.d.ts +1 -1
- package/dist/package.js +1 -1
- package/dist/target-api.d.ts +12 -0
- package/dist/target-api.js +26 -0
- package/dist/target.d.ts +3 -2
- package/dist/target.js +25 -13
- package/dist/workerd-target.d.ts +6 -1
- package/dist/workerd-target.js +56 -12
- package/dist/workers-project.d.ts +1 -1
- package/dist/workers-project.js +4 -3
- package/package.json +3 -1
- package/runtime/bootstrap.mjs +2 -0
- package/runtime/browser-worker.mjs +83 -0
- package/runtime/evaluator.mjs +2 -0
- package/runtime/workerd-worker.mjs +2 -0
package/dist/browser/mod.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { JsonObject } from "../message.ts";
|
|
1
2
|
import type { TypeStripper, TypeStripperName } from "../transform.ts";
|
|
2
3
|
import { type Channel, type JupyterMessage, type MessageListener } from "./channels.ts";
|
|
3
4
|
import { type BrowserTargetOptions } from "./target.ts";
|
|
4
5
|
export type { Channel, JupyterMessage, MessageListener } from "./channels.ts";
|
|
5
|
-
export { BrowserTarget, type BrowserTargetOptions, type WorkerLike } from "./target.ts";
|
|
6
|
+
export { BrowserTarget, type BrowserTargetOptions, type HostHandler, type HostRequest, type WorkerLike } from "./target.ts";
|
|
6
7
|
export interface BrowserKernelOptions extends BrowserTargetOptions {
|
|
7
8
|
/** What strips cells' types; sucrase by default, since esbuild's native binary can't run in a page. */
|
|
8
9
|
typeStrip?: TypeStripperName | TypeStripper;
|
|
9
10
|
/** Where the kernel's own log lines go; console.debug by default. */
|
|
10
11
|
log?(text: string): void;
|
|
12
|
+
/** Returned as `capabilities` in kernel_info_reply: what this page offers cells (#384). */
|
|
13
|
+
capabilities?: JsonObject;
|
|
11
14
|
}
|
|
12
15
|
export interface BrowserKernel {
|
|
13
16
|
/** A message from the frontend, on shell, control or stdin. */
|
|
@@ -16,5 +19,7 @@ export interface BrowserKernel {
|
|
|
16
19
|
onMessage(listener: MessageListener): () => void;
|
|
17
20
|
/** Settles once a shutdown_request has been answered. */
|
|
18
21
|
readonly closed: Promise<void>;
|
|
22
|
+
/** An event for the Worker's `host.onEvent` listeners; dropped while no Worker runs (#384). */
|
|
23
|
+
notifyHost(event: string, value?: unknown): void;
|
|
19
24
|
}
|
|
20
25
|
export declare function startBrowserKernel(options: BrowserKernelOptions): BrowserKernel;
|
package/dist/browser/mod.js
CHANGED
|
@@ -20,12 +20,14 @@ export function startBrowserKernel(options) {
|
|
|
20
20
|
const log = options.log ?? ((text) => console.debug(`[cdp-kernel] ${text}`));
|
|
21
21
|
let closed;
|
|
22
22
|
const closedPromise = new Promise((resolve) => (closed = resolve));
|
|
23
|
+
let target = null;
|
|
23
24
|
const core = new KernelCore({
|
|
24
25
|
runtime: "browser",
|
|
25
26
|
version: VERSION,
|
|
26
27
|
channels,
|
|
27
|
-
launchTarget: (events) => BrowserTarget.launch(events, options),
|
|
28
|
+
launchTarget: async (events) => (target = await BrowserTarget.launch(events, options)),
|
|
28
29
|
typeStrip: options.typeStrip ?? "sucrase",
|
|
30
|
+
capabilities: options.capabilities,
|
|
29
31
|
log,
|
|
30
32
|
exit: () => closed(),
|
|
31
33
|
});
|
|
@@ -34,5 +36,6 @@ export function startBrowserKernel(options) {
|
|
|
34
36
|
send: (channel, msg) => channels.deliver(channel, msg),
|
|
35
37
|
onMessage: (listener) => channels.onMessage(listener),
|
|
36
38
|
closed: closedPromise,
|
|
39
|
+
notifyHost: (event, value) => target?.notifyHost(event, value),
|
|
37
40
|
};
|
|
38
41
|
}
|
package/dist/browser/target.d.ts
CHANGED
|
@@ -22,7 +22,27 @@ export interface BrowserTargetOptions {
|
|
|
22
22
|
base?: string;
|
|
23
23
|
/** Where npm:, jsr: and bare imports load from; esm.sh by default. */
|
|
24
24
|
cdn?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Modules the Worker imports before the first cell, each time it starts,
|
|
27
|
+
* resolved against `base`. A module's default export, if a function, is
|
|
28
|
+
* called with `{ host, jupyter }` (#384).
|
|
29
|
+
*/
|
|
30
|
+
preload?: string[];
|
|
31
|
+
/** Answers the Worker's `host.request` calls; without it they reject (#384). */
|
|
32
|
+
host?: HostHandler;
|
|
33
|
+
}
|
|
34
|
+
/** A request a preload module made with `host.request(method, params)`. */
|
|
35
|
+
export interface HostRequest {
|
|
36
|
+
method: string;
|
|
37
|
+
params: unknown;
|
|
25
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves with the value `host.request` resolves with in the Worker, or
|
|
41
|
+
* rejects, and the Worker's promise rejects with an Error carrying the
|
|
42
|
+
* thrown error's `name`, `message` and `code`. `signal` aborts when the
|
|
43
|
+
* Worker cancels the call or stops.
|
|
44
|
+
*/
|
|
45
|
+
export type HostHandler = (request: HostRequest, signal: AbortSignal) => unknown;
|
|
26
46
|
export declare class BrowserTarget implements KernelTarget {
|
|
27
47
|
#private;
|
|
28
48
|
readonly runtime: "browser";
|
|
@@ -33,6 +53,8 @@ export declare class BrowserTarget implements KernelTarget {
|
|
|
33
53
|
readonly transform: typeof transformCellForEval;
|
|
34
54
|
private constructor();
|
|
35
55
|
static launch(events: TargetEvents, options: BrowserTargetOptions): Promise<BrowserTarget>;
|
|
56
|
+
/** Sends `host.onEvent` listeners in the Worker an event (#384). */
|
|
57
|
+
notifyHost(event: string, value?: unknown): void;
|
|
36
58
|
evaluate(script: string): Promise<EvaluateResult>;
|
|
37
59
|
/**
|
|
38
60
|
* A cell that's waiting on something leaves the Worker free: it answers a
|
|
@@ -53,6 +75,7 @@ export declare class BrowserTarget implements KernelTarget {
|
|
|
53
75
|
stack: string;
|
|
54
76
|
};
|
|
55
77
|
}>;
|
|
78
|
+
format(value: RemoteObject): Promise<CdpParams>;
|
|
56
79
|
describeError(exception: RemoteObject): Promise<{
|
|
57
80
|
name: string;
|
|
58
81
|
message: string;
|
package/dist/browser/target.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// follows. Cells are transformed as for Bun and workerd
|
|
6
6
|
// (transformCellForEval).
|
|
7
7
|
import { asEvaluateResult, isRecord } from "../cdp.js";
|
|
8
|
-
import { Interrupted, isTargetMessage } from "../target-api.js";
|
|
8
|
+
import { Interrupted, isTargetMessage, parseBundle } from "../target-api.js";
|
|
9
9
|
import { transformCellForEval } from "../transform.js";
|
|
10
10
|
/** See BunTarget: how long an interrupted cell has to show it isn't busy. */
|
|
11
11
|
export const INTERRUPT_GRACE_MS = 500;
|
|
@@ -23,13 +23,17 @@ export class BrowserTarget {
|
|
|
23
23
|
#pending = new Map();
|
|
24
24
|
/** Requests that interrupt() abandons when the driver can still answer. */
|
|
25
25
|
#interruptible = new Set();
|
|
26
|
+
/** Host requests in flight, by the Worker's id (#384). */
|
|
27
|
+
#hostCalls = new Map();
|
|
28
|
+
#host;
|
|
26
29
|
#nextId = 0;
|
|
27
30
|
#interrupted = false;
|
|
28
31
|
#stopNote;
|
|
29
32
|
#exited = false;
|
|
30
|
-
constructor(worker, events) {
|
|
33
|
+
constructor(worker, events, host) {
|
|
31
34
|
this.#worker = worker;
|
|
32
35
|
this.#events = events;
|
|
36
|
+
this.#host = host;
|
|
33
37
|
worker.addEventListener("message", (event) => this.#receive(event.data));
|
|
34
38
|
// A driver that failed to load: what's pending can't be answered.
|
|
35
39
|
worker.addEventListener("error", (event) => {
|
|
@@ -38,10 +42,10 @@ export class BrowserTarget {
|
|
|
38
42
|
});
|
|
39
43
|
}
|
|
40
44
|
static async launch(events, options) {
|
|
41
|
-
const target = new BrowserTarget(options.createWorker(), events);
|
|
45
|
+
const target = new BrowserTarget(options.createWorker(), events, options.host);
|
|
42
46
|
const base = options.base ?? globalThis.location?.href ?? "file:///";
|
|
43
47
|
try {
|
|
44
|
-
const { userAgent } = await target.#request("info", { base, cdn: options.cdn });
|
|
48
|
+
const { userAgent } = await target.#request("info", { base, cdn: options.cdn, preload: options.preload ?? [] });
|
|
45
49
|
if (typeof userAgent === "string")
|
|
46
50
|
target.versions.runtime = browserName(userAgent);
|
|
47
51
|
target.versions.engine = engineName(typeof userAgent === "string" ? userAgent : "");
|
|
@@ -56,6 +60,9 @@ export class BrowserTarget {
|
|
|
56
60
|
if (isRecord(msg) && msg.kind === "stream") {
|
|
57
61
|
this.#events.stream(msg.name === "stderr" ? "stderr" : "stdout", String(msg.text));
|
|
58
62
|
}
|
|
63
|
+
else if (isRecord(msg) && msg.kind === "host") {
|
|
64
|
+
this.#hostRequest(msg);
|
|
65
|
+
}
|
|
59
66
|
else if (isTargetMessage(msg)) {
|
|
60
67
|
this.#events.message(msg);
|
|
61
68
|
}
|
|
@@ -72,11 +79,47 @@ export class BrowserTarget {
|
|
|
72
79
|
pending.resolve(msg);
|
|
73
80
|
}
|
|
74
81
|
}
|
|
82
|
+
#hostRequest(msg) {
|
|
83
|
+
const id = Number(msg.id);
|
|
84
|
+
if (msg.cancel === true) {
|
|
85
|
+
this.#hostCalls.get(id)?.abort();
|
|
86
|
+
this.#hostCalls.delete(id);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
this.#hostCalls.set(id, controller);
|
|
91
|
+
const answer = (reply) => {
|
|
92
|
+
if (this.#hostCalls.get(id) !== controller)
|
|
93
|
+
return; // cancelled, or the Worker stopped
|
|
94
|
+
this.#hostCalls.delete(id);
|
|
95
|
+
try {
|
|
96
|
+
this.#worker.postMessage({ op: "host", id, ...reply });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// A result structured clone can't copy, such as a function.
|
|
100
|
+
this.#worker.postMessage({ op: "host", id, error: describeHostError(err) });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const host = this.#host;
|
|
104
|
+
new Promise((resolve) => {
|
|
105
|
+
if (!host)
|
|
106
|
+
throw new Error("this page offers no host requests");
|
|
107
|
+
resolve(host({ method: String(msg.method), params: msg.params }, controller.signal));
|
|
108
|
+
}).then((result) => answer({ result }), (err) => answer({ error: describeHostError(err) }));
|
|
109
|
+
}
|
|
110
|
+
/** Sends `host.onEvent` listeners in the Worker an event (#384). */
|
|
111
|
+
notifyHost(event, value) {
|
|
112
|
+
if (!this.#exited)
|
|
113
|
+
this.#worker.postMessage({ op: "host", event, value });
|
|
114
|
+
}
|
|
75
115
|
#exit(code) {
|
|
76
116
|
if (this.#exited)
|
|
77
117
|
return;
|
|
78
118
|
this.#exited = true;
|
|
79
119
|
this.#worker.terminate();
|
|
120
|
+
for (const controller of this.#hostCalls.values())
|
|
121
|
+
controller.abort();
|
|
122
|
+
this.#hostCalls.clear();
|
|
80
123
|
// Reported before the requests it cut off fail, so the note lands in
|
|
81
124
|
// the interrupted cell's output.
|
|
82
125
|
this.#events.exit(code, this.#stopNote);
|
|
@@ -150,6 +193,10 @@ export class BrowserTarget {
|
|
|
150
193
|
}
|
|
151
194
|
return { handled: reply.handled === true };
|
|
152
195
|
}
|
|
196
|
+
async format(value) {
|
|
197
|
+
const reply = await this.#request("format", { handle: value.objectId });
|
|
198
|
+
return parseBundle(reply.value);
|
|
199
|
+
}
|
|
153
200
|
async describeError(exception) {
|
|
154
201
|
if (exception.objectId === undefined)
|
|
155
202
|
return { name: "Error", message: exception.description ?? "", stack: "" };
|
|
@@ -175,6 +222,17 @@ export class BrowserTarget {
|
|
|
175
222
|
this.#exit(null);
|
|
176
223
|
}
|
|
177
224
|
}
|
|
225
|
+
function describeHostError(err) {
|
|
226
|
+
if (isRecord(err)) {
|
|
227
|
+
const { name, message, code } = err;
|
|
228
|
+
return {
|
|
229
|
+
name: typeof name === "string" ? name : "Error",
|
|
230
|
+
message: typeof message === "string" ? message : String(err),
|
|
231
|
+
...(code === undefined ? {} : { code: code }),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { name: "Error", message: String(err) };
|
|
235
|
+
}
|
|
178
236
|
function toBase64(bytes) {
|
|
179
237
|
let binary = "";
|
|
180
238
|
for (let i = 0; i < bytes.length; i += 0x8000)
|
package/dist/browser/worker.js
CHANGED
|
@@ -524,13 +524,13 @@ function hasDisplaySymbol(obj) {
|
|
|
524
524
|
function isMediaBundle(obj) {
|
|
525
525
|
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
526
526
|
}
|
|
527
|
-
function createJupyter(
|
|
527
|
+
function createJupyter(host2) {
|
|
528
528
|
async function format2(obj) {
|
|
529
529
|
return await formatNow(obj);
|
|
530
530
|
}
|
|
531
531
|
function formatNow(obj) {
|
|
532
532
|
if (hasDisplaySymbol(obj)) return obj[$display]();
|
|
533
|
-
if (typeof obj !== "object" || obj === null) return { "text/plain":
|
|
533
|
+
if (typeof obj !== "object" || obj === null) return { "text/plain": host2.inspect(obj) };
|
|
534
534
|
if (isCanvasLike(obj)) {
|
|
535
535
|
const [prefix, data] = obj.toDataURL().split(",");
|
|
536
536
|
return { [prefix.split(":")[1].split(";")[0]]: data };
|
|
@@ -540,11 +540,11 @@ function createJupyter(host) {
|
|
|
540
540
|
if (vega) return vega;
|
|
541
541
|
}
|
|
542
542
|
if (isDataFrameLike(obj)) return extractDataFrame(obj);
|
|
543
|
-
if (isJpg(obj)) return { "image/jpeg":
|
|
544
|
-
if (isPng(obj)) return { "image/png":
|
|
543
|
+
if (isJpg(obj)) return { "image/jpeg": host2.base64(obj) };
|
|
544
|
+
if (isPng(obj)) return { "image/png": host2.base64(obj) };
|
|
545
545
|
if (isSVGElementLike(obj)) return { "image/svg+xml": obj.outerHTML };
|
|
546
546
|
if (isHTMLElementLike(obj)) return { "text/html": obj.outerHTML };
|
|
547
|
-
return { "text/plain":
|
|
547
|
+
return { "text/plain": host2.inspect(obj) };
|
|
548
548
|
}
|
|
549
549
|
function taggedTemplate(mediatype) {
|
|
550
550
|
return (strings, ...values) => {
|
|
@@ -555,18 +555,18 @@ function createJupyter(host) {
|
|
|
555
555
|
function image(obj) {
|
|
556
556
|
if (typeof obj === "string") {
|
|
557
557
|
try {
|
|
558
|
-
obj =
|
|
558
|
+
obj = host2.readFile(obj);
|
|
559
559
|
} catch {
|
|
560
560
|
}
|
|
561
561
|
}
|
|
562
|
-
if (isJpg(obj)) return { [$display]: () => ({ "image/jpeg":
|
|
563
|
-
if (isPng(obj)) return { [$display]: () => ({ "image/png":
|
|
562
|
+
if (isJpg(obj)) return { [$display]: () => ({ "image/jpeg": host2.base64(obj) }) };
|
|
563
|
+
if (isPng(obj)) return { [$display]: () => ({ "image/png": host2.base64(obj) }) };
|
|
564
564
|
throw new TypeError(
|
|
565
565
|
"Object is not a valid image or a path to an image. `jupyter.image` supports displaying JPG or PNG images."
|
|
566
566
|
);
|
|
567
567
|
}
|
|
568
568
|
function broadcast(msgType, content, { metadata = {}, buffers = [] } = {}) {
|
|
569
|
-
|
|
569
|
+
host2.send(msgType, content, metadata, buffers);
|
|
570
570
|
}
|
|
571
571
|
async function display(obj, options = { raw: false, update: false }) {
|
|
572
572
|
const bundle = options.raw && isMediaBundle(obj) ? obj : await format2(obj);
|
|
@@ -595,15 +595,15 @@ function createJupyter(host) {
|
|
|
595
595
|
}
|
|
596
596
|
}
|
|
597
597
|
function confirm(message = "Confirm") {
|
|
598
|
-
const answer2 =
|
|
598
|
+
const answer2 = host2.input(`${message} [y/N] `, false);
|
|
599
599
|
return answer2 === "Y" || answer2 === "y";
|
|
600
600
|
}
|
|
601
601
|
function prompt(message = "Prompt", defaultValue = "", { password = false } = {}) {
|
|
602
602
|
if (defaultValue != "") message += ` [${defaultValue}]`;
|
|
603
|
-
const answer2 =
|
|
603
|
+
const answer2 = host2.input(message, password);
|
|
604
604
|
return answer2 === "" ? defaultValue : answer2;
|
|
605
605
|
}
|
|
606
|
-
const comms = createComms((msgType, content, metadata, buffers) =>
|
|
606
|
+
const comms = createComms((msgType, content, metadata, buffers) => host2.send(msgType, content, metadata, buffers));
|
|
607
607
|
return {
|
|
608
608
|
namespace: {
|
|
609
609
|
broadcast,
|
|
@@ -665,6 +665,8 @@ function createHandler(shim, evaluate = (code) => (0, eval)(code)) {
|
|
|
665
665
|
case "broadcast":
|
|
666
666
|
await shim.broadcastResult(request.count, take(request.handle));
|
|
667
667
|
return {};
|
|
668
|
+
case "format":
|
|
669
|
+
return { value: await shim.formatJson(request.handle === void 0 ? void 0 : take(request.handle)) };
|
|
668
670
|
case "describe":
|
|
669
671
|
return { value: shim.describeError(take(request.handle)) };
|
|
670
672
|
case "comm":
|
|
@@ -952,6 +954,71 @@ var jupyter = createJupyter({
|
|
|
952
954
|
input: () => null
|
|
953
955
|
});
|
|
954
956
|
var cdn = DEFAULT_CDN;
|
|
957
|
+
var hostCalls = /* @__PURE__ */ new Map();
|
|
958
|
+
var hostListeners = /* @__PURE__ */ new Set();
|
|
959
|
+
var nextHostId = 0;
|
|
960
|
+
function hostError(error) {
|
|
961
|
+
const err = new Error(String(error?.message ?? "the host request failed"));
|
|
962
|
+
if (typeof error?.name === "string") err.name = error.name;
|
|
963
|
+
if (error?.code !== void 0) err.code = error.code;
|
|
964
|
+
return err;
|
|
965
|
+
}
|
|
966
|
+
var host = {
|
|
967
|
+
/** Resolves with the page's answer; aborting `signal` cancels the call on the page too. */
|
|
968
|
+
request(method, params, { signal, transfer } = {}) {
|
|
969
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
970
|
+
const id = ++nextHostId;
|
|
971
|
+
return new Promise((resolve, reject) => {
|
|
972
|
+
const onAbort = () => {
|
|
973
|
+
hostCalls.delete(id);
|
|
974
|
+
post({ kind: "host", id, cancel: true });
|
|
975
|
+
reject(signal.reason);
|
|
976
|
+
};
|
|
977
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
978
|
+
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
979
|
+
try {
|
|
980
|
+
globalThis.postMessage({ kind: "host", id, method, params }, transfer ?? []);
|
|
981
|
+
} catch (err) {
|
|
982
|
+
done();
|
|
983
|
+
throw err;
|
|
984
|
+
}
|
|
985
|
+
hostCalls.set(id, { resolve, reject, done });
|
|
986
|
+
});
|
|
987
|
+
},
|
|
988
|
+
/** Calls `listener(event, value)` for each event the page sends; returns a function that stops it. */
|
|
989
|
+
onEvent(listener) {
|
|
990
|
+
hostListeners.add(listener);
|
|
991
|
+
return () => hostListeners.delete(listener);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
function receiveHost(msg) {
|
|
995
|
+
if (msg.id === void 0) {
|
|
996
|
+
for (const listener of hostListeners) {
|
|
997
|
+
try {
|
|
998
|
+
listener(msg.event, msg.value);
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
reportUncaught("Uncaught", err);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const call = hostCalls.get(msg.id);
|
|
1006
|
+
if (!call) return;
|
|
1007
|
+
hostCalls.delete(msg.id);
|
|
1008
|
+
call.done();
|
|
1009
|
+
if (msg.error !== void 0) call.reject(hostError(msg.error));
|
|
1010
|
+
else call.resolve(msg.result);
|
|
1011
|
+
}
|
|
1012
|
+
async function preload(urls, base) {
|
|
1013
|
+
for (const specifier of Array.isArray(urls) ? urls : []) {
|
|
1014
|
+
try {
|
|
1015
|
+
const mod = await import(new URL(specifier, base).href);
|
|
1016
|
+
if (typeof mod.default === "function") await mod.default({ host, jupyter: jupyter.namespace });
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
reportUncaught(`Preloading ${specifier} failed`, err);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
955
1022
|
var handle = null;
|
|
956
1023
|
function install({ base, cdn: cdnBase }) {
|
|
957
1024
|
if (typeof cdnBase === "string") cdn = cdnBase.endsWith("/") ? cdnBase : cdnBase + "/";
|
|
@@ -965,6 +1032,8 @@ function install({ base, cdn: cdnBase }) {
|
|
|
965
1032
|
resolvePath,
|
|
966
1033
|
barrier: () => 0,
|
|
967
1034
|
broadcastResult: jupyter.broadcastResult,
|
|
1035
|
+
// execute_request's user_expressions (#340).
|
|
1036
|
+
formatJson: async (value) => JSON.stringify(await jupyter.namespace.format(value)),
|
|
968
1037
|
async commDispatch(msgType, content, metadata, buffers64) {
|
|
969
1038
|
const buffers = buffers64.map((b) => Uint8Array.from(atob(b), (c) => c.charCodeAt(0)));
|
|
970
1039
|
return await jupyter.commDispatch(msgType, content, metadata, buffers);
|
|
@@ -985,6 +1054,7 @@ function install({ base, cdn: cdnBase }) {
|
|
|
985
1054
|
async function answer(req) {
|
|
986
1055
|
if (req.op === "info") {
|
|
987
1056
|
install(req);
|
|
1057
|
+
await preload(req.preload, req.base);
|
|
988
1058
|
return { userAgent: globalThis.navigator?.userAgent ?? "" };
|
|
989
1059
|
}
|
|
990
1060
|
if (handle === null) throw new Error("the driver wasn't sent info first");
|
|
@@ -992,6 +1062,7 @@ async function answer(req) {
|
|
|
992
1062
|
}
|
|
993
1063
|
globalThis.addEventListener("message", (event) => {
|
|
994
1064
|
const req = event.data;
|
|
1065
|
+
if (req?.op === "host") return receiveHost(req);
|
|
995
1066
|
answer(req).then(
|
|
996
1067
|
(reply) => post({ kind: "reply", id: req.id, ...reply }),
|
|
997
1068
|
(err) => post({ kind: "reply", id: req.id, failure: String(err?.stack ?? err) })
|