@basthon/kernel-base 0.62.21 → 0.63.0

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,21 @@
1
+ import { Remote } from "comlink";
2
+ import { ProxyFetch } from "./worker/proxy";
3
+ declare global {
4
+ interface Window {
5
+ _remoteProxy: Remote<ProxyFetch>;
6
+ _assetsURL?: string;
7
+ }
8
+ }
9
+ /**
10
+ * An error thrown by not implemented API functions.
11
+ */
12
+ declare class NotImplementedError extends Error {
13
+ constructor(funcName: string);
14
+ }
15
+ /**
16
+ * we mock the fetch function to:
17
+ * - redirect queries to local FS
18
+ * - bypass the worker's opaque origin
19
+ */
20
+ declare const mockFetch: (legacy: boolean) => void;
21
+ export { NotImplementedError, mockFetch };
package/lib/commons.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * An error thrown by not implemented API functions.
3
+ */
4
+ class NotImplementedError extends Error {
5
+ constructor(funcName) {
6
+ super(`Function ${funcName} not implemented!`);
7
+ this.name = "NotImplementedError";
8
+ }
9
+ }
10
+ /* fetch from local FS */
11
+ const fetchFromLocalFS = async (localfsScheme, url) => {
12
+ const toDataURL = async (content) => {
13
+ return await new Promise((resolve, reject) => {
14
+ const reader = new FileReader();
15
+ reader.onloadend = () => {
16
+ resolve(reader.result);
17
+ };
18
+ reader.onerror = reject;
19
+ reader.readAsDataURL(new Blob([content]));
20
+ });
21
+ };
22
+ const prefix = localfsScheme;
23
+ const path = url.slice(prefix.length);
24
+ let content = null;
25
+ const options = {
26
+ status: 200,
27
+ statusText: "OK",
28
+ headers: new Headers(),
29
+ };
30
+ try {
31
+ //@ts-ignore
32
+ content = await self.basthon.getFile(path);
33
+ const dataURL = await toDataURL(content);
34
+ const mime = dataURL.substring(dataURL.indexOf(":") + 1, dataURL.indexOf(";"));
35
+ content = content.buffer;
36
+ options.headers.append("Content-Type", mime);
37
+ options.headers.append("Content-Length", content.byteLength);
38
+ }
39
+ catch (e) {
40
+ options.status = 404;
41
+ options.statusText = "Not Found";
42
+ }
43
+ return new Response(content, options);
44
+ };
45
+ /**
46
+ * Get the origin of an url.
47
+ */
48
+ const origin = (url) => new URL(url).origin;
49
+ /**
50
+ * Get the parent path of an url.
51
+ */
52
+ const parent = (url) => {
53
+ const abs = new URL(url).href;
54
+ return abs.substring(0, abs.lastIndexOf("/"));
55
+ };
56
+ /**
57
+ * we mock the fetch function to:
58
+ * - redirect queries to local FS
59
+ * - bypass the worker's opaque origin
60
+ */
61
+ const mockFetch = (legacy) => {
62
+ // already mocked?
63
+ if (self.fetch.name === "mockedFetch")
64
+ return;
65
+ const trueFetch = self.fetch;
66
+ async function mockedFetch(request, init) {
67
+ if (!legacy && (self.importScripts == null || self._assetsURL == null))
68
+ return Response.error();
69
+ // force all params to init object
70
+ if (request instanceof Request)
71
+ init = { ...request, ...init };
72
+ // ensure credentials are not sent
73
+ init = { ...init, ...{ credentials: "omit" } };
74
+ const url = request.url ?? request.toString();
75
+ const localfsScheme = "filesystem:/";
76
+ try {
77
+ // requests towards localfs
78
+ if (url.startsWith(localfsScheme)) {
79
+ return await fetchFromLocalFS(localfsScheme, url);
80
+ }
81
+ else if (!legacy && origin(url) === origin(self._assetsURL)) {
82
+ // requests towards same origin
83
+ // only url under the assets folder are proxyied
84
+ if (parent(url).startsWith(self._assetsURL)) {
85
+ const proxy = self._remoteProxy;
86
+ const { body, options } = await proxy.fetch(url, init);
87
+ return new Response(body, options);
88
+ }
89
+ throw new Error("Ressource is outside the proxy' scope");
90
+ }
91
+ return await trueFetch(url, init);
92
+ }
93
+ catch (e) {
94
+ console.error(`security proxy: request to ${url} throw error: ${e.toString()}`);
95
+ return Response.error();
96
+ }
97
+ }
98
+ self.fetch = mockedFetch;
99
+ };
100
+ export { NotImplementedError, mockFetch };
package/lib/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './kernel';
1
+ export * from "./kernel";
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- export * from './kernel';
1
+ export * from "./kernel";
package/lib/kernel.d.ts CHANGED
@@ -1,15 +1,26 @@
1
+ import { PromiseDelegate } from "promise-delegate";
2
+ import { VERSION } from "./version";
3
+ export { VERSION };
1
4
  /**
2
5
  * API that any Basthon kernel should fill to be supported
3
6
  * in console/notebook.
4
7
  */
5
8
  export declare class KernelBase {
6
9
  private _rootPath;
10
+ private _isReady;
7
11
  private _ready;
8
- private readonly _loaded;
12
+ private _stopped;
9
13
  protected _execution_count: number;
10
- protected _pendingInput: boolean;
14
+ protected _pendingInput?: PromiseDelegate<string>;
11
15
  private _listeners;
16
+ protected _options: any;
17
+ protected _files: Map<string, ArrayBuffer>;
18
+ protected _modules: Map<string, ArrayBuffer>;
12
19
  constructor(options: any);
20
+ /**
21
+ * Is this kernel safe? (loaded from an isolated web worker)
22
+ */
23
+ safeKernel(): boolean;
13
24
  /**
14
25
  * Kernel version number (string).
15
26
  */
@@ -28,10 +39,6 @@ export declare class KernelBase {
28
39
  * Script (module) file extensions
29
40
  */
30
41
  moduleExts(): string[];
31
- /**
32
- * Launch the kernel.
33
- */
34
- launch(): Promise<void>;
35
42
  /**
36
43
  * Execution count getter.
37
44
  */
@@ -40,7 +47,6 @@ export declare class KernelBase {
40
47
  * Async code evaluation that resolves with the result.
41
48
  */
42
49
  evalAsync(code: string, outCallback: (_: string) => void, errCallback: (_: string) => void, data?: any): Promise<any>;
43
- restart(): void;
44
50
  putFile(filename: string, content: ArrayBuffer): Promise<void>;
45
51
  putModule(filename: string, content: ArrayBuffer): Promise<void>;
46
52
  userModules(): string[];
@@ -52,23 +58,48 @@ export declare class KernelBase {
52
58
  * Get a user module file content.
53
59
  */
54
60
  getUserModuleFile(filename: string): Uint8Array;
55
- more(source: string): boolean;
56
- complete(code: string): [string[], number] | [];
61
+ more(source: string): Promise<boolean>;
62
+ complete(code: string): Promise<[string[], number] | []>;
57
63
  banner(): string;
58
64
  ps1(): string;
59
65
  ps2(): string;
60
66
  /**
61
- * Initialize the kernel.
67
+ * Launch the kernel (used to wrap a promise around init).
68
+ */
69
+ protected _init(): Promise<void>;
70
+ /**
71
+ * Initialize the kernel and start it.
62
72
  */
63
73
  init(): Promise<void>;
74
+ /**
75
+ * Start the kernel (used to wrap a promise around start).
76
+ */
77
+ protected _start(): Promise<void>;
78
+ /**
79
+ * Start the kernel.
80
+ */
81
+ start(): Promise<void>;
82
+ /**
83
+ * Perform async part of the stoping process.
84
+ */
85
+ protected _stop(): Promise<void>;
86
+ /**
87
+ * Stop the kernel synchronously (to ensure kernel is stopped after the call).
88
+ */
89
+ stop(): void;
90
+ /**
91
+ * Restart the kernel (stop and start).
92
+ * Use await this.ready() to ensure kernel is started
93
+ */
94
+ restart(): void;
64
95
  /**
65
96
  * Is the kernel ready?
66
97
  */
67
- get ready(): boolean;
98
+ get isReady(): boolean;
68
99
  /**
69
- * Promise that resolve when the kernel is loaded.
100
+ * Promise that resolves when the kernel is ready (started).
70
101
  */
71
- loaded(): Promise<void>;
102
+ ready(): Promise<void>;
72
103
  /**
73
104
  * Root for kernel files. This is always the language directory
74
105
  * inside the version number directory inside the kernel directory.
@@ -103,7 +134,11 @@ export declare class KernelBase {
103
134
  /**
104
135
  * Send eval.input event then wait for the user response and return it.
105
136
  */
106
- inputAsync(prompt: string | null | undefined, password?: boolean, data?: any): Promise<unknown>;
137
+ inputAsync(prompt: string | null | undefined, password?: boolean, data?: any): Promise<string>;
138
+ /**
139
+ * Close a pending input.
140
+ */
141
+ resolvePendingInput(): void;
107
142
  /**
108
143
  * Simple clone via JSON copy.
109
144
  */
package/lib/kernel.js CHANGED
@@ -1,83 +1,27 @@
1
1
  import { PromiseDelegate } from "promise-delegate";
2
2
  import { VERSION } from "./version";
3
- const _localfsScheme = "filesystem:/";
4
- /* we mock the fetch function to redirect queries to local FS */
5
- (function () {
6
- var _a;
7
- const toDataURL = async (content) => {
8
- return await new Promise((resolve, reject) => {
9
- const reader = new FileReader();
10
- reader.onloadend = () => {
11
- resolve(reader.result);
12
- };
13
- reader.onerror = reject;
14
- reader.readAsDataURL(new Blob([content]));
15
- });
16
- };
17
- //@ts-ignore
18
- window._fetch = (_a = window._fetch) !== null && _a !== void 0 ? _a : window.fetch;
19
- window.fetch = async function (request, opts) {
20
- var _a;
21
- //@ts-ignore
22
- const url = (_a = request.url) !== null && _a !== void 0 ? _a : request.toString();
23
- const prefix = _localfsScheme;
24
- if (url.startsWith(prefix)) {
25
- const path = url.slice(prefix.length);
26
- let content = null;
27
- const options = {
28
- status: 200,
29
- statusText: "OK",
30
- headers: new Headers(),
31
- };
32
- try {
33
- //@ts-ignore
34
- content = window.Basthon.getFile(path);
35
- const dataURL = await toDataURL(content);
36
- const mime = dataURL.substring(dataURL.indexOf(":") + 1, dataURL.indexOf(";"));
37
- content = content.buffer;
38
- options.headers.append("Content-Type", mime);
39
- options.headers.append("Content-Length", content.byteLength);
40
- }
41
- catch (e) {
42
- options.status = 404;
43
- options.statusText = "Not Found";
44
- }
45
- return new Response(content, options);
46
- }
47
- //@ts-ignore
48
- return await window._fetch(request, opts);
49
- };
50
- const desc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, "src");
51
- Object.defineProperty(HTMLImageElement.prototype, "src", {
52
- ...desc,
53
- get: function () {
54
- var _a;
55
- return (_a = desc === null || desc === void 0 ? void 0 : desc.get) === null || _a === void 0 ? void 0 : _a.call(this);
56
- },
57
- set: function (e) {
58
- var _a;
59
- const prefix = _localfsScheme;
60
- if (e.startsWith(prefix)) {
61
- const path = e.slice(prefix.length);
62
- //@ts-ignore
63
- const content = window.Basthon.getFile(path).buffer;
64
- (async () => { var _a; return (_a = desc === null || desc === void 0 ? void 0 : desc.set) === null || _a === void 0 ? void 0 : _a.call(this, await toDataURL(content)); })();
65
- }
66
- else {
67
- (_a = desc === null || desc === void 0 ? void 0 : desc.set) === null || _a === void 0 ? void 0 : _a.call(this, e);
68
- }
69
- },
70
- });
71
- })();
72
- /**
73
- * An error thrown by not implemented API functions.
74
- */
75
- class NotImplementedError extends Error {
76
- constructor(funcName) {
77
- super(`Function ${funcName} not implemented!`);
78
- this.name = "NotImplementedError";
79
- }
80
- }
3
+ import { NotImplementedError } from "./commons";
4
+ // const desc = Object.getOwnPropertyDescriptor(
5
+ // HTMLImageElement.prototype,
6
+ // "src"
7
+ // );
8
+ // Object.defineProperty(HTMLImageElement.prototype, "src", {
9
+ // ...desc,
10
+ // get: function () {
11
+ // return desc?.get?.call(this);
12
+ // },
13
+ // set: function (e) {
14
+ // const prefix = _localfsScheme;
15
+ // if (e.startsWith(prefix)) {
16
+ // const path = e.slice(prefix.length);
17
+ // //@ts-ignore
18
+ // const content = window.Basthon.getFile(path).buffer;
19
+ // (async () => desc?.set?.call(this, await toDataURL(content)))();
20
+ // } else {
21
+ // desc?.set?.call(this, e);
22
+ // }
23
+ // },
24
+ // });
81
25
  /**
82
26
  * Event for Basthon's dispatch/listen.
83
27
  */
@@ -87,21 +31,32 @@ class BasthonEvent extends Event {
87
31
  this.detail = data;
88
32
  }
89
33
  }
34
+ export { VERSION };
90
35
  /**
91
36
  * API that any Basthon kernel should fill to be supported
92
37
  * in console/notebook.
93
38
  */
94
39
  export class KernelBase {
95
40
  constructor(options) {
96
- this._ready = false;
97
- this._loaded = new PromiseDelegate();
41
+ this._isReady = false;
42
+ this._ready = new PromiseDelegate();
43
+ this._stopped = Promise.resolve();
98
44
  this._execution_count = 0;
99
- this._pendingInput = false;
45
+ this._pendingInput = undefined;
100
46
  // a map to register wrapped listeners to allow later remove
101
47
  this._listeners = new Map();
48
+ this._files = new Map();
49
+ this._modules = new Map();
50
+ this._options = this.clone(options);
102
51
  // root path where kernel is installed
103
52
  this._rootPath = options.rootPath;
104
53
  }
54
+ /**
55
+ * Is this kernel safe? (loaded from an isolated web worker)
56
+ */
57
+ safeKernel() {
58
+ return false;
59
+ }
105
60
  /**
106
61
  * Kernel version number (string).
107
62
  */
@@ -128,12 +83,6 @@ export class KernelBase {
128
83
  moduleExts() {
129
84
  throw new NotImplementedError("moduleExts");
130
85
  }
131
- /**
132
- * Launch the kernel.
133
- */
134
- async launch() {
135
- throw new NotImplementedError("launch");
136
- }
137
86
  /**
138
87
  * Execution count getter.
139
88
  */
@@ -146,14 +95,11 @@ export class KernelBase {
146
95
  evalAsync(code, outCallback, errCallback, data = null) {
147
96
  throw new NotImplementedError("evalAsync");
148
97
  }
149
- restart() {
150
- throw new NotImplementedError("restart");
151
- }
152
98
  async putFile(filename, content) {
153
- throw new NotImplementedError("putFile");
99
+ this._files.set(filename, content);
154
100
  }
155
101
  async putModule(filename, content) {
156
- throw new NotImplementedError("putModule");
102
+ this._modules.set(filename, content);
157
103
  }
158
104
  userModules() {
159
105
  return [];
@@ -170,10 +116,10 @@ export class KernelBase {
170
116
  getUserModuleFile(filename) {
171
117
  throw new NotImplementedError("getUserModuleFile");
172
118
  }
173
- more(source) {
119
+ async more(source) {
174
120
  throw new NotImplementedError("more");
175
121
  }
176
- complete(code) {
122
+ async complete(code) {
177
123
  return [];
178
124
  }
179
125
  banner() {
@@ -186,32 +132,80 @@ export class KernelBase {
186
132
  return "... ";
187
133
  }
188
134
  /**
189
- * Initialize the kernel.
135
+ * Launch the kernel (used to wrap a promise around init).
136
+ */
137
+ async _init() { }
138
+ /**
139
+ * Initialize the kernel and start it.
190
140
  */
191
141
  async init() {
142
+ if (!this.safeKernel())
143
+ console.warn(`%c⚠ DANGER ⚠: this kernel is not safe!` +
144
+ " Please consider using a safe kernel.", "color: red; font-size: 20px;");
192
145
  try {
193
- await this.launch();
146
+ await this._init();
194
147
  }
195
148
  catch (error) {
196
- this._loaded.reject(error);
149
+ this._ready.reject(error);
197
150
  return;
198
151
  }
199
152
  // connecting eval to basthon.eval.request event.
200
153
  this.addEventListener("eval.request", this.evalFromEvent.bind(this));
201
- this._ready = true;
202
- this._loaded.resolve();
154
+ await this.start();
155
+ }
156
+ /**
157
+ * Start the kernel (used to wrap a promise around start).
158
+ */
159
+ async _start() { }
160
+ /**
161
+ * Start the kernel.
162
+ */
163
+ async start() {
164
+ await this._stopped;
165
+ this._execution_count = 0;
166
+ try {
167
+ await this._start();
168
+ }
169
+ catch (error) {
170
+ this._ready.reject(error);
171
+ return;
172
+ }
173
+ this._files.forEach((v, k) => this.putFile(k, v));
174
+ this._modules.forEach((v, k) => this.putModule(k, v));
175
+ this._isReady = true;
176
+ this._ready.resolve();
177
+ }
178
+ /**
179
+ * Perform async part of the stoping process.
180
+ */
181
+ async _stop() { }
182
+ /**
183
+ * Stop the kernel synchronously (to ensure kernel is stopped after the call).
184
+ */
185
+ stop() {
186
+ this._isReady = false;
187
+ this._ready = new PromiseDelegate();
188
+ this._stopped = this._stop();
189
+ }
190
+ /**
191
+ * Restart the kernel (stop and start).
192
+ * Use await this.ready() to ensure kernel is started
193
+ */
194
+ restart() {
195
+ this.stop(); // sync
196
+ this.start(); // async
203
197
  }
204
198
  /**
205
199
  * Is the kernel ready?
206
200
  */
207
- get ready() {
208
- return this._ready;
201
+ get isReady() {
202
+ return this._isReady;
209
203
  }
210
204
  /**
211
- * Promise that resolve when the kernel is loaded.
205
+ * Promise that resolves when the kernel is ready (started).
212
206
  */
213
- async loaded() {
214
- await this._loaded.promise;
207
+ async ready() {
208
+ return this._ready.promise;
215
209
  }
216
210
  /**
217
211
  * Root for kernel files. This is always the language directory
@@ -287,7 +281,7 @@ export class KernelBase {
287
281
  removeEventListener(eventName, callback) {
288
282
  // get the wrapped callback
289
283
  const listeners = this._listeners.get(eventName);
290
- document.removeEventListener(`basthon.${eventName}`, listeners === null || listeners === void 0 ? void 0 : listeners.get(callback));
284
+ document.removeEventListener(`basthon.${eventName}`, listeners?.get(callback));
291
285
  }
292
286
  /**
293
287
  * Send eval.input event then wait for the user response and return it.
@@ -295,16 +289,21 @@ export class KernelBase {
295
289
  async inputAsync(prompt, password = false, data = undefined) {
296
290
  data = this.clone(data);
297
291
  data.content = { prompt, password };
298
- const promise = new Promise(function (resolve, reject) {
299
- data.resolve = resolve;
300
- data.reject = reject;
301
- });
302
- this._pendingInput = true;
292
+ const pd = new PromiseDelegate();
293
+ data.resolve = pd.resolve.bind(pd);
294
+ data.reject = pd.reject.bind(pd);
295
+ this._pendingInput = pd;
303
296
  this.dispatchEvent("eval.input", data);
304
- const res = await promise;
305
- this._pendingInput = false;
297
+ const res = await pd.promise;
298
+ this._pendingInput = undefined;
306
299
  return res;
307
300
  }
301
+ /**
302
+ * Close a pending input.
303
+ */
304
+ resolvePendingInput() {
305
+ this._pendingInput?.resolve("");
306
+ }
308
307
  /**
309
308
  * Simple clone via JSON copy.
310
309
  */
@@ -317,8 +316,7 @@ export class KernelBase {
317
316
  * Detection is based on extension.
318
317
  */
319
318
  async putRessource(filename, content) {
320
- var _a;
321
- const ext = (_a = filename.split(".").pop()) !== null && _a !== void 0 ? _a : "";
319
+ const ext = filename.split(".").pop() ?? "";
322
320
  if (this.moduleExts().includes(ext)) {
323
321
  return await this.putModule(filename, content);
324
322
  }
@@ -330,7 +328,7 @@ export class KernelBase {
330
328
  * Is an input pending?
331
329
  */
332
330
  pendingInput() {
333
- return this._pendingInput;
331
+ return this._pendingInput != null;
334
332
  }
335
333
  /**
336
334
  * Internal. Code evaluation after an eval.request event.
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.62.21";
1
+ export declare const VERSION = "0.63.0";
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.62.21";
1
+ export const VERSION = "0.63.0";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { expose } from "comlink";
2
+ import { ProxyFetch } from "./proxy";
3
+ const proxy = new ProxyFetch();
4
+ expose(proxy);
@@ -0,0 +1,3 @@
1
+ export * from "./kernel-main";
2
+ export * from "./kernel-worker";
3
+ export * from "./worker-utils";
@@ -0,0 +1,3 @@
1
+ export * from "./kernel-main";
2
+ export * from "./kernel-worker";
3
+ export * from "./worker-utils";
@@ -0,0 +1,73 @@
1
+ import { Remote } from "comlink";
2
+ import { KernelWorkerBase } from "./kernel-worker";
3
+ import { KernelBase } from "../kernel";
4
+ export declare class KernelMainBase<Type extends KernelWorkerBase> extends KernelBase {
5
+ private _worker?;
6
+ private _remote?;
7
+ private _legacy;
8
+ constructor(options: any);
9
+ /**
10
+ * Is this a legacy worker? i.e. not runing in a worker.
11
+ */
12
+ isLegacy(): boolean;
13
+ /**
14
+ * Is this kernel safe? (loaded in an isolated web worker)
15
+ */
16
+ safeKernel(): boolean;
17
+ /**
18
+ * Get a new worker (should be overloaded).
19
+ */
20
+ protected newWorker(): Worker;
21
+ /**
22
+ * Import the code for a legacy worker (running in the main thread).
23
+ */
24
+ protected importLegacyWorker(): Promise<void>;
25
+ /**
26
+ * Create the kernel webworker and setup communication.
27
+ */
28
+ protected setupWorker(): Promise<void>;
29
+ /**
30
+ * Start the worker.
31
+ */
32
+ _start(): Promise<void>;
33
+ /**
34
+ * Comlink remote getter.
35
+ */
36
+ protected get remote(): Remote<Type> | undefined;
37
+ /**
38
+ * Stop the worker.
39
+ */
40
+ _stop(): Promise<void>;
41
+ /**
42
+ * Process messages from worker (if any).
43
+ */
44
+ protected processWorkerMessage(msg: {
45
+ data: any;
46
+ type: string;
47
+ content: any;
48
+ }): void;
49
+ evalAsync(code: string, outCallback: (_: string) => void, errCallback: (_: string) => void, data?: any): Promise<any>;
50
+ /**
51
+ * Put a file on the local (emulated) filesystem.
52
+ */
53
+ putFile(filename: string, content: ArrayBuffer): Promise<void>;
54
+ /**
55
+ * Put an importable module on the local (emulated) filesystem
56
+ * and load dependencies.
57
+ */
58
+ putModule(filename: string, content: ArrayBuffer): Promise<void>;
59
+ /**
60
+ * Complete a code at the end (usefull for tab completion).
61
+ *
62
+ * Returns an array of two elements: the list of completions
63
+ * and the start index.
64
+ */
65
+ complete(code: string): Promise<[string[], number] | []>;
66
+ /**
67
+ * Tell wether we should wait for more code or if it can
68
+ * be run as is.
69
+ *
70
+ * Useful to set ps1/ps2 in console prompt.
71
+ */
72
+ more(code: string): Promise<boolean>;
73
+ }