jupyterpack 0.2.1 → 0.4.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.
- package/README.md +2 -0
- package/lib/document/commands.d.ts +8 -0
- package/lib/document/commands.js +76 -0
- package/lib/document/iframePanel.d.ts +4 -1
- package/lib/document/plugin.d.ts +2 -1
- package/lib/document/plugin.js +22 -4
- package/lib/document/toolbar.d.ts +9 -0
- package/lib/document/toolbar.js +20 -0
- package/lib/document/widgetFactory.d.ts +2 -1
- package/lib/document/widgetFactory.js +15 -6
- package/lib/index.d.ts +1 -1
- package/lib/pythonServer/dash/dashServer.d.ts +22 -0
- package/lib/pythonServer/dash/dashServer.js +47 -0
- package/lib/pythonServer/index.d.ts +5 -0
- package/lib/pythonServer/index.js +9 -0
- package/lib/pythonServer/kernelExecutor.d.ts +71 -0
- package/lib/pythonServer/kernelExecutor.js +140 -0
- package/lib/pythonServer/streamlit/streamlitServer.d.ts +14 -0
- package/lib/pythonServer/streamlit/streamlitServer.js +51 -0
- package/lib/pythonServer/tornado/tornadoServer.d.ts +34 -0
- package/lib/pythonServer/tornado/tornadoServer.js +68 -0
- package/lib/pythonWidget/comm.d.ts +11 -0
- package/lib/pythonWidget/comm.js +52 -0
- package/lib/pythonWidget/pythonWidget.d.ts +6 -0
- package/lib/pythonWidget/pythonWidget.js +28 -3
- package/lib/pythonWidget/pythonWidgetModel.d.ts +27 -6
- package/lib/pythonWidget/pythonWidgetModel.js +101 -15
- package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
- package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
- package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
- package/lib/sandpackWidget/sandpackPanel.js +38 -3
- package/lib/swConnection/index.js +2 -2
- package/lib/{pythonWidget/connectionManager.d.ts → swConnection/mainConnectionManager.d.ts} +10 -0
- package/lib/swConnection/mainConnectionManager.js +93 -0
- package/lib/swConnection/sw.js +1 -1
- package/lib/swConnection/swCommManager.d.ts +11 -0
- package/lib/swConnection/{comm_manager.js → swCommManager.js} +5 -0
- package/lib/token.d.ts +2 -1
- package/lib/token.js +1 -0
- package/lib/tools.d.ts +9 -0
- package/lib/tools.js +75 -0
- package/lib/type.d.ts +64 -3
- package/lib/type.js +2 -0
- package/lib/websocket/websocket.d.ts +0 -0
- package/lib/websocket/websocket.js +152 -0
- package/package.json +8 -5
- package/src/document/commands.ts +91 -0
- package/src/document/iframePanel.ts +4 -1
- package/src/document/plugin.ts +28 -7
- package/src/document/toolbar.ts +39 -0
- package/src/document/widgetFactory.ts +17 -6
- package/src/global.d.ts +9 -0
- package/src/pythonServer/dash/dashServer.ts +66 -0
- package/src/pythonServer/index.ts +18 -0
- package/src/pythonServer/kernelExecutor.ts +243 -0
- package/src/pythonServer/streamlit/streamlitServer.ts +67 -0
- package/src/pythonServer/tornado/tornadoServer.ts +97 -0
- package/src/pythonWidget/comm.ts +65 -0
- package/src/pythonWidget/pythonWidget.ts +38 -3
- package/src/pythonWidget/pythonWidgetModel.ts +155 -34
- package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
- package/src/sandpackWidget/sandpackPanel.ts +45 -4
- package/src/swConnection/index.ts +5 -2
- package/src/swConnection/mainConnectionManager.ts +121 -0
- package/src/swConnection/sw.ts +1 -1
- package/src/swConnection/{comm_manager.ts → swCommManager.ts} +6 -0
- package/src/token.ts +5 -1
- package/src/tools.ts +91 -0
- package/src/type.ts +76 -4
- package/src/websocket/websocket.ts +216 -0
- package/style/base.css +7 -0
- package/style/icons/autoreload.svg +16 -0
- package/style/icons/box.svg +12 -0
- package/style/icons/externallink.svg +10 -0
- package/lib/pythonWidget/connectionManager.js +0 -27
- package/lib/pythonWidget/kernelExecutor.d.ts +0 -27
- package/lib/pythonWidget/kernelExecutor.js +0 -104
- package/lib/swConnection/comm_manager.d.ts +0 -6
- package/lib/swConnection/connection_manager.d.ts +0 -18
- package/lib/swConnection/connection_manager.js +0 -27
- package/src/pythonWidget/connectionManager.ts +0 -43
- package/src/pythonWidget/kernelExecutor.ts +0 -140
- package/src/swConnection/connection_manager.ts +0 -43
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { arrayBufferToBase64 } from '../tools';
|
|
2
|
+
import { UUID } from '@lumino/coreutils';
|
|
3
|
+
/**
|
|
4
|
+
* Manages connections between clients and kernel executors.
|
|
5
|
+
* This class handles the registration of kernel executors and the generation of responses
|
|
6
|
+
* for client requests. It maintains a mapping of kernel client IDs to their respective executors.
|
|
7
|
+
* The HTTP requests intercepted by the service worker are forwarded to the appropriate kernel executor.
|
|
8
|
+
* The websocket messages forwarded from the broadcast channel are also forwarded to the appropriate kernel executor.
|
|
9
|
+
* It's running on the main thread
|
|
10
|
+
*/
|
|
11
|
+
export class ConnectionManager {
|
|
12
|
+
constructor(instanceId) {
|
|
13
|
+
this.instanceId = instanceId;
|
|
14
|
+
this._kernelExecutors = new Map();
|
|
15
|
+
this._wsBroadcastChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
|
|
16
|
+
this._initWsChannel();
|
|
17
|
+
}
|
|
18
|
+
async registerConnection(kernelExecutor) {
|
|
19
|
+
const uuid = UUID.uuid4();
|
|
20
|
+
this._kernelExecutors.set(uuid, kernelExecutor);
|
|
21
|
+
return { instanceId: this.instanceId, kernelClientId: uuid };
|
|
22
|
+
}
|
|
23
|
+
async generateResponse(options) {
|
|
24
|
+
const { urlPath, kernelClientId, method, params, requestBody, headers } = options;
|
|
25
|
+
const executor = this._kernelExecutors.get(kernelClientId);
|
|
26
|
+
if (!executor) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const response = await executor.getResponse({
|
|
30
|
+
urlPath,
|
|
31
|
+
method,
|
|
32
|
+
params,
|
|
33
|
+
headers,
|
|
34
|
+
requestBody
|
|
35
|
+
});
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
_initWsChannel() {
|
|
39
|
+
this._wsBroadcastChannel.onmessage = event => {
|
|
40
|
+
const rawData = event.data;
|
|
41
|
+
let data;
|
|
42
|
+
if (typeof rawData === 'string') {
|
|
43
|
+
data = JSON.parse(rawData);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
data = rawData;
|
|
47
|
+
}
|
|
48
|
+
const { action, dest, wsUrl, payload } = data;
|
|
49
|
+
const executor = this._kernelExecutors.get(dest);
|
|
50
|
+
if (!executor) {
|
|
51
|
+
console.error('Missing kernel handle for message', data, dest, this._kernelExecutors);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
switch (action) {
|
|
55
|
+
case 'open': {
|
|
56
|
+
executor.openWebsocket({
|
|
57
|
+
instanceId: this.instanceId,
|
|
58
|
+
kernelId: dest,
|
|
59
|
+
wsUrl,
|
|
60
|
+
protocol: payload.protocol
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'send': {
|
|
65
|
+
let serializedData;
|
|
66
|
+
let isBinary;
|
|
67
|
+
if (payload instanceof ArrayBuffer || ArrayBuffer.isView(payload)) {
|
|
68
|
+
// Convert data to base64 string
|
|
69
|
+
serializedData = arrayBufferToBase64(payload);
|
|
70
|
+
isBinary = true;
|
|
71
|
+
}
|
|
72
|
+
else if (typeof payload === 'string') {
|
|
73
|
+
serializedData = payload;
|
|
74
|
+
isBinary = false;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.error('Unknown message type', payload);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
executor.sendWebsocketMessage({
|
|
81
|
+
instanceId: this.instanceId,
|
|
82
|
+
kernelId: dest,
|
|
83
|
+
wsUrl,
|
|
84
|
+
message: JSON.stringify({ isBinary, data: serializedData })
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
package/lib/swConnection/sw.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages communication between different components using Comlink's MessagePort-based communication.
|
|
3
|
+
* This class handles registration of communication channels and processing of requests.
|
|
4
|
+
* It's running on the service worker thread
|
|
5
|
+
*/
|
|
6
|
+
export declare class CommManager {
|
|
7
|
+
constructor();
|
|
8
|
+
registerComm(instanceId: string, port: MessagePort): void;
|
|
9
|
+
generateResponse(request: Request): Promise<Response>;
|
|
10
|
+
private _commIds;
|
|
11
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { wrap, transfer } from 'comlink';
|
|
2
|
+
/**
|
|
3
|
+
* Manages communication between different components using Comlink's MessagePort-based communication.
|
|
4
|
+
* This class handles registration of communication channels and processing of requests.
|
|
5
|
+
* It's running on the service worker thread
|
|
6
|
+
*/
|
|
2
7
|
export class CommManager {
|
|
3
8
|
constructor() {
|
|
4
9
|
this._commIds = new Map();
|
package/lib/token.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Token } from '@lumino/coreutils';
|
|
2
|
-
import { IConnectionManager } from './type';
|
|
2
|
+
import { IConnectionManager, IJupyterpackDocTracker } from './type';
|
|
3
3
|
export declare const IConnectionManagerToken: Token<IConnectionManager>;
|
|
4
|
+
export declare const IJupyterpackDocTrackerToken: Token<IJupyterpackDocTracker>;
|
package/lib/token.js
CHANGED
package/lib/tools.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
export declare const IS_LITE: boolean;
|
|
3
|
+
export declare const logoIcon: LabIcon;
|
|
4
|
+
export declare const autoReloadIcon: LabIcon;
|
|
5
|
+
export declare const linkIcon: LabIcon;
|
|
1
6
|
export declare function removePrefix(path: string, prefix: string): string;
|
|
2
7
|
export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
|
|
8
|
+
export declare function base64ToArrayBuffer(base64: string): Uint8Array;
|
|
9
|
+
export declare function base64ToString(base64: string): string;
|
|
10
|
+
export declare function stringOrNone(content?: string): string;
|
|
11
|
+
export declare function isBinaryContentType(contentType?: string): boolean;
|
package/lib/tools.js
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import logoStr from '../style/icons/box.svg';
|
|
2
|
+
import autoReloadStr from '../style/icons/autoreload.svg';
|
|
3
|
+
import linkStr from '../style/icons/externallink.svg';
|
|
4
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
5
|
+
export const IS_LITE = !!document.getElementById('jupyter-lite-main');
|
|
6
|
+
export const logoIcon = new LabIcon({
|
|
7
|
+
name: 'jupyterpack:logo',
|
|
8
|
+
svgstr: logoStr
|
|
9
|
+
});
|
|
10
|
+
export const autoReloadIcon = new LabIcon({
|
|
11
|
+
name: 'jupyterpack:autoReload',
|
|
12
|
+
svgstr: autoReloadStr
|
|
13
|
+
});
|
|
14
|
+
export const linkIcon = new LabIcon({
|
|
15
|
+
name: 'jupyterpack:externalLink',
|
|
16
|
+
svgstr: linkStr
|
|
17
|
+
});
|
|
1
18
|
export function removePrefix(path, prefix) {
|
|
2
19
|
if (path.startsWith(prefix)) {
|
|
3
20
|
return path.slice(prefix.length);
|
|
@@ -15,3 +32,61 @@ export function arrayBufferToBase64(buffer) {
|
|
|
15
32
|
}
|
|
16
33
|
return btoa(binary);
|
|
17
34
|
}
|
|
35
|
+
export function base64ToArrayBuffer(base64) {
|
|
36
|
+
const binaryString = atob(base64);
|
|
37
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
38
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
39
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
40
|
+
}
|
|
41
|
+
return bytes;
|
|
42
|
+
}
|
|
43
|
+
export function base64ToString(base64) {
|
|
44
|
+
const bytes = base64ToArrayBuffer(base64);
|
|
45
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
46
|
+
}
|
|
47
|
+
export function stringOrNone(content) {
|
|
48
|
+
return content ? `"${content}"` : 'None';
|
|
49
|
+
}
|
|
50
|
+
export function isBinaryContentType(contentType) {
|
|
51
|
+
if (!contentType) {
|
|
52
|
+
// no Content-Type → assume binary for safety
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
contentType = contentType.toLowerCase().trim();
|
|
56
|
+
const textTypes = [
|
|
57
|
+
'text/',
|
|
58
|
+
'application/json',
|
|
59
|
+
'application/javascript',
|
|
60
|
+
'application/xml',
|
|
61
|
+
'application/xhtml+xml',
|
|
62
|
+
'application/x-www-form-urlencoded',
|
|
63
|
+
'application/sql',
|
|
64
|
+
'application/graphql',
|
|
65
|
+
'application/yaml'
|
|
66
|
+
];
|
|
67
|
+
const binaryIndicators = [
|
|
68
|
+
'image/',
|
|
69
|
+
'audio/',
|
|
70
|
+
'video/',
|
|
71
|
+
'font/',
|
|
72
|
+
'application/octet-stream',
|
|
73
|
+
'application/pdf',
|
|
74
|
+
'application/zip',
|
|
75
|
+
'application/x-protobuf',
|
|
76
|
+
'application/vnd'
|
|
77
|
+
];
|
|
78
|
+
// Starts with text/ or one of the textual types
|
|
79
|
+
if (textTypes.some(t => contentType.startsWith(t))) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// Starts with binary-indicating prefix
|
|
83
|
+
if (binaryIndicators.some(t => contentType.startsWith(t))) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
// If charset is specified → text
|
|
87
|
+
if (contentType.includes('charset=')) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// Default: assume binary
|
|
91
|
+
return true;
|
|
92
|
+
}
|
package/lib/type.d.ts
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
1
2
|
import { KernelMessage } from '@jupyterlab/services';
|
|
2
3
|
import { IDisposable } from '@lumino/disposable';
|
|
4
|
+
import { IWidgetTracker } from '@jupyterlab/apputils';
|
|
5
|
+
import { ISignal } from '@lumino/signaling';
|
|
3
6
|
export interface IDict<T = any> {
|
|
4
7
|
[key: string]: T;
|
|
5
8
|
}
|
|
9
|
+
export interface IBroadcastMessage {
|
|
10
|
+
action: 'message' | 'open' | 'close' | 'error' | 'send' | 'connected' | 'backend_message';
|
|
11
|
+
dest: string;
|
|
12
|
+
wsUrl: string;
|
|
13
|
+
payload?: any;
|
|
14
|
+
}
|
|
6
15
|
export declare enum JupyterPackFramework {
|
|
7
16
|
REACT = "react",
|
|
8
|
-
DASH = "dash"
|
|
17
|
+
DASH = "dash",
|
|
18
|
+
STREAMLIT = "streamlit",
|
|
19
|
+
TORNADO = "tornado"
|
|
9
20
|
}
|
|
10
21
|
export interface IJupyterPackFileFormat {
|
|
11
22
|
entry: string;
|
|
12
23
|
framework: JupyterPackFramework;
|
|
13
24
|
name?: string;
|
|
14
|
-
metadata?:
|
|
25
|
+
metadata?: {
|
|
26
|
+
autoreload?: boolean;
|
|
27
|
+
};
|
|
28
|
+
rootUrl?: string;
|
|
15
29
|
}
|
|
16
30
|
export declare enum MessageAction {
|
|
17
31
|
INIT = "INIT"
|
|
@@ -25,7 +39,37 @@ export interface IKernelExecutorParams {
|
|
|
25
39
|
}
|
|
26
40
|
export interface IKernelExecutor extends IDisposable {
|
|
27
41
|
getResponse(options: IKernelExecutorParams): Promise<IDict>;
|
|
28
|
-
|
|
42
|
+
openWebsocket(options: {
|
|
43
|
+
instanceId: string;
|
|
44
|
+
kernelId: string;
|
|
45
|
+
wsUrl: string;
|
|
46
|
+
protocol?: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
sendWebsocketMessage(options: {
|
|
49
|
+
instanceId: string;
|
|
50
|
+
kernelId: string;
|
|
51
|
+
wsUrl: string;
|
|
52
|
+
message: string;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
executeCode(code: KernelMessage.IExecuteRequestMsg['content'], waitForResult?: boolean): Promise<string | null>;
|
|
55
|
+
init(options: {
|
|
56
|
+
entryPath?: string;
|
|
57
|
+
initCode?: string;
|
|
58
|
+
instanceId: string;
|
|
59
|
+
kernelClientId: string;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
disposePythonServer(): Promise<void>;
|
|
62
|
+
reloadPythonServer(options: {
|
|
63
|
+
entryPath?: string;
|
|
64
|
+
initCode?: string;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
getResponseFunctionFactory(options: {
|
|
67
|
+
urlPath: string;
|
|
68
|
+
method: string;
|
|
69
|
+
headers: IDict;
|
|
70
|
+
params?: string;
|
|
71
|
+
content?: string;
|
|
72
|
+
}): string;
|
|
29
73
|
}
|
|
30
74
|
export interface IConnectionManager {
|
|
31
75
|
registerConnection(kernelExecutor: IKernelExecutor): Promise<{
|
|
@@ -36,3 +80,20 @@ export interface IConnectionManager {
|
|
|
36
80
|
kernelClientId: string;
|
|
37
81
|
} & IKernelExecutorParams): Promise<IDict | null>;
|
|
38
82
|
}
|
|
83
|
+
export type IJupyterpackDocTracker = IWidgetTracker<DocumentWidget>;
|
|
84
|
+
export interface IPythonWidgetModel extends IDisposable {
|
|
85
|
+
connectionManager: IConnectionManager;
|
|
86
|
+
serverReloaded: ISignal<IPythonWidgetModel, void>;
|
|
87
|
+
kernelStatusChanged: ISignal<IPythonWidgetModel, 'started' | 'stopped'>;
|
|
88
|
+
reload(): Promise<void>;
|
|
89
|
+
initialize(): Promise<{
|
|
90
|
+
success: true;
|
|
91
|
+
instanceId: string;
|
|
92
|
+
kernelClientId: string;
|
|
93
|
+
rootUrl: string;
|
|
94
|
+
framework: JupyterPackFramework;
|
|
95
|
+
} | {
|
|
96
|
+
success: false;
|
|
97
|
+
error: string;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
package/lib/type.js
CHANGED
|
@@ -2,6 +2,8 @@ export var JupyterPackFramework;
|
|
|
2
2
|
(function (JupyterPackFramework) {
|
|
3
3
|
JupyterPackFramework["REACT"] = "react";
|
|
4
4
|
JupyterPackFramework["DASH"] = "dash";
|
|
5
|
+
JupyterPackFramework["STREAMLIT"] = "streamlit";
|
|
6
|
+
JupyterPackFramework["TORNADO"] = "tornado";
|
|
5
7
|
})(JupyterPackFramework || (JupyterPackFramework = {}));
|
|
6
8
|
export var MessageAction;
|
|
7
9
|
(function (MessageAction) {
|
|
File without changes
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
const urlPath = new URL(window.location.href).pathname;
|
|
4
|
+
const pathAfterExtensionName = urlPath.split('/jupyterpack/static')[1];
|
|
5
|
+
const pathList = pathAfterExtensionName === null || pathAfterExtensionName === void 0 ? void 0 : pathAfterExtensionName.split('/').filter(Boolean);
|
|
6
|
+
const instanceId = pathList === null || pathList === void 0 ? void 0 : pathList[0];
|
|
7
|
+
const kernelClientId = pathList === null || pathList === void 0 ? void 0 : pathList[2];
|
|
8
|
+
if (!instanceId || !kernelClientId) {
|
|
9
|
+
throw new Error('Missing instance Id or kernelClient Id');
|
|
10
|
+
}
|
|
11
|
+
const sendTypedMessage = (msg) => {
|
|
12
|
+
bcWsChannel.postMessage({ ...msg, dest: kernelClientId });
|
|
13
|
+
};
|
|
14
|
+
function base64ToBinary(base64, dataType) {
|
|
15
|
+
const binary = atob(base64); // decode base64
|
|
16
|
+
const len = binary.length;
|
|
17
|
+
const bytes = new Uint8Array(len);
|
|
18
|
+
for (let i = 0; i < len; i++) {
|
|
19
|
+
bytes[i] = binary.charCodeAt(i);
|
|
20
|
+
}
|
|
21
|
+
if (dataType === 'arraybuffer') {
|
|
22
|
+
return bytes.buffer;
|
|
23
|
+
}
|
|
24
|
+
else if (dataType === 'blob') {
|
|
25
|
+
return new Blob([bytes], { type: 'application/octet-stream' });
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new Error("Unsupported type: use 'arraybuffer' or 'blob'");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const decodeServerMessage = (payload, binaryType) => {
|
|
32
|
+
const { data, isBinary } = payload;
|
|
33
|
+
if (isBinary) {
|
|
34
|
+
// Decode base64 string to array buffer or blob
|
|
35
|
+
return base64ToBinary(data, binaryType);
|
|
36
|
+
}
|
|
37
|
+
return data;
|
|
38
|
+
};
|
|
39
|
+
const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
|
|
40
|
+
class BroadcastChannelWebSocket {
|
|
41
|
+
constructor(url, protocols) {
|
|
42
|
+
this.onclose = () => {
|
|
43
|
+
// no-op
|
|
44
|
+
};
|
|
45
|
+
this.onerror = () => {
|
|
46
|
+
// no-op
|
|
47
|
+
};
|
|
48
|
+
this.onmessage = () => {
|
|
49
|
+
// no-op
|
|
50
|
+
};
|
|
51
|
+
this.onopen = () => {
|
|
52
|
+
// no-op
|
|
53
|
+
};
|
|
54
|
+
this.CONNECTING = 0;
|
|
55
|
+
this.OPEN = 1;
|
|
56
|
+
this.CLOSING = 2;
|
|
57
|
+
this.CLOSED = 3;
|
|
58
|
+
this._eventHandlers = { message: [], open: [], close: [], error: [] };
|
|
59
|
+
this._bcMessageHandler = async (event) => {
|
|
60
|
+
const rawData = event.data;
|
|
61
|
+
let data;
|
|
62
|
+
if (typeof rawData === 'string') {
|
|
63
|
+
data = JSON.parse(rawData);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
data = rawData;
|
|
67
|
+
}
|
|
68
|
+
const { action, dest, wsUrl, payload } = data;
|
|
69
|
+
if (dest !== kernelClientId || wsUrl !== this.url) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
switch (action) {
|
|
73
|
+
case 'connected': {
|
|
74
|
+
this.readyState = this.OPEN;
|
|
75
|
+
if (this.onopen) {
|
|
76
|
+
this.onopen(event);
|
|
77
|
+
}
|
|
78
|
+
this._eventHandlers.open.forEach(handler => handler({ data: payload }));
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'backend_message': {
|
|
82
|
+
const decoded = decodeServerMessage(payload, this.binaryType);
|
|
83
|
+
if (this.onmessage) {
|
|
84
|
+
this.onmessage({ data: decoded });
|
|
85
|
+
}
|
|
86
|
+
this._eventHandlers.message.forEach(handler => handler({ data: decoded }));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
this._open = () => {
|
|
94
|
+
sendTypedMessage({
|
|
95
|
+
action: 'open',
|
|
96
|
+
payload: {
|
|
97
|
+
protocol: this.protocol
|
|
98
|
+
},
|
|
99
|
+
wsUrl: this.url
|
|
100
|
+
});
|
|
101
|
+
bcWsChannel.addEventListener('message', this._bcMessageHandler);
|
|
102
|
+
};
|
|
103
|
+
const urlObj = new URL(url);
|
|
104
|
+
this.url = urlObj.pathname + urlObj.search + urlObj.hash;
|
|
105
|
+
if (protocols) {
|
|
106
|
+
this.protocol = Array.isArray(protocols)
|
|
107
|
+
? protocols.join(',')
|
|
108
|
+
: protocols;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.protocol = '';
|
|
112
|
+
}
|
|
113
|
+
this.binaryType = 'blob';
|
|
114
|
+
this.bufferedAmount = 0;
|
|
115
|
+
this.extensions = '';
|
|
116
|
+
this.readyState = this.CONNECTING;
|
|
117
|
+
this._open();
|
|
118
|
+
}
|
|
119
|
+
close(code, reason) {
|
|
120
|
+
if (this.readyState === this.CLOSED) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (this.onclose) {
|
|
124
|
+
this.onclose();
|
|
125
|
+
}
|
|
126
|
+
while (this._eventHandlers['close'].length) {
|
|
127
|
+
const cb = this._eventHandlers['close'].pop();
|
|
128
|
+
cb();
|
|
129
|
+
}
|
|
130
|
+
this._eventHandlers['close'] = [];
|
|
131
|
+
bcWsChannel.removeEventListener('message', this._bcMessageHandler);
|
|
132
|
+
this.readyState = this.CLOSED;
|
|
133
|
+
}
|
|
134
|
+
send(data) {
|
|
135
|
+
sendTypedMessage({
|
|
136
|
+
action: 'send',
|
|
137
|
+
payload: data,
|
|
138
|
+
wsUrl: this.url
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
addEventListener(type, listener, options) {
|
|
142
|
+
this._eventHandlers[type].push(listener);
|
|
143
|
+
}
|
|
144
|
+
removeEventListener(type, listener, options) {
|
|
145
|
+
this._eventHandlers[type] = this._eventHandlers[type].filter(handler => handler !== listener);
|
|
146
|
+
}
|
|
147
|
+
dispatchEvent(event) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
window.WebSocket = BroadcastChannelWebSocket;
|
|
152
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterpack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A JupyterLab extension for serving web app.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"url": "https://github.com/trungleduc/jupyterpack.git"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build:demo": "
|
|
32
|
+
"build:demo": "node script/build-demo.mjs",
|
|
33
33
|
"update:demo": "node script/update-demo.mjs",
|
|
34
34
|
"build:worker": "webpack --config sw.webpack.config.js --mode development",
|
|
35
35
|
"build:worker:prod": "webpack --config sw.webpack.config.js --mode production",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@codesandbox/sandpack-client": "^2.19.8",
|
|
63
63
|
"@jupyterlab/application": "^4.0.0",
|
|
64
|
-
"comlink": "^4.4.2"
|
|
64
|
+
"comlink": "^4.4.2",
|
|
65
|
+
"strip-ansi": "^7.1.2"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
68
|
"@jupyterlab/builder": "^4.0.0",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"eslint-plugin-prettier": "^5.0.0",
|
|
79
80
|
"npm-run-all2": "^7.0.1",
|
|
80
81
|
"prettier": "^3.0.0",
|
|
81
|
-
"rimraf": "^
|
|
82
|
+
"rimraf": "^6.1.0",
|
|
82
83
|
"source-map-loader": "^1.0.2",
|
|
83
84
|
"style-loader": "^3.3.1",
|
|
84
85
|
"stylelint": "^15.10.1",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"stylelint-config-standard": "^34.0.0",
|
|
87
88
|
"stylelint-csstree-validator": "^3.0.0",
|
|
88
89
|
"stylelint-prettier": "^4.0.0",
|
|
90
|
+
"tar": "^7.5.2",
|
|
89
91
|
"ts-loader": "^9.2.6",
|
|
90
92
|
"typescript": "~5.8.0",
|
|
91
93
|
"webpack": "^5.76.3",
|
|
@@ -102,7 +104,8 @@
|
|
|
102
104
|
},
|
|
103
105
|
"jupyterlab": {
|
|
104
106
|
"extension": true,
|
|
105
|
-
"outputDir": "jupyterpack/labextension"
|
|
107
|
+
"outputDir": "jupyterpack/labextension",
|
|
108
|
+
"webpackConfig": "./extension.webpack.config.js"
|
|
106
109
|
},
|
|
107
110
|
"eslintIgnore": [
|
|
108
111
|
"node_modules",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { refreshIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
3
|
+
import { Panel } from '@lumino/widgets';
|
|
4
|
+
|
|
5
|
+
import { autoReloadIcon, linkIcon } from '../tools';
|
|
6
|
+
import { IJupyterpackDocTracker } from '../type';
|
|
7
|
+
import { IFramePanel } from './iframePanel';
|
|
8
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
9
|
+
|
|
10
|
+
export const CommandIDs = {
|
|
11
|
+
RELOAD: 'jupyterpack:reload',
|
|
12
|
+
TOGGLE_AUTORELOAD: 'jupyterpack:toggleAutoreload',
|
|
13
|
+
OPEN_SPECTA: 'jupyterpack:openInSpecta'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const labBaseUrl = PageConfig.getOption('baseUrl');
|
|
17
|
+
|
|
18
|
+
function getCurrentIframPanel(
|
|
19
|
+
tracker: IJupyterpackDocTracker
|
|
20
|
+
): IFramePanel | undefined {
|
|
21
|
+
const current = tracker.currentWidget?.content as Panel | undefined;
|
|
22
|
+
if (!current) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const widget = current.widgets[0] as IFramePanel | undefined;
|
|
26
|
+
if (!widget) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
return widget;
|
|
30
|
+
}
|
|
31
|
+
export function addCommands(
|
|
32
|
+
commands: CommandRegistry,
|
|
33
|
+
tracker: IJupyterpackDocTracker
|
|
34
|
+
) {
|
|
35
|
+
commands.addCommand(CommandIDs.RELOAD, {
|
|
36
|
+
caption: 'Reload',
|
|
37
|
+
isEnabled: () => {
|
|
38
|
+
return tracker.currentWidget !== null;
|
|
39
|
+
},
|
|
40
|
+
icon: refreshIcon,
|
|
41
|
+
execute: async () => {
|
|
42
|
+
const widget = getCurrentIframPanel(tracker);
|
|
43
|
+
if (widget) {
|
|
44
|
+
await widget.reload();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const commandState = { toggled: false };
|
|
49
|
+
commands.addCommand(CommandIDs.TOGGLE_AUTORELOAD, {
|
|
50
|
+
isEnabled: () => {
|
|
51
|
+
return tracker.currentWidget !== null;
|
|
52
|
+
},
|
|
53
|
+
isToggled: () => {
|
|
54
|
+
const widget = getCurrentIframPanel(tracker);
|
|
55
|
+
return Boolean(widget?.autoreload);
|
|
56
|
+
},
|
|
57
|
+
icon: autoReloadIcon,
|
|
58
|
+
caption: e => {
|
|
59
|
+
return commandState.toggled
|
|
60
|
+
? 'Auto-reload enabled'
|
|
61
|
+
: 'Auto-reload disabled';
|
|
62
|
+
},
|
|
63
|
+
execute: async () => {
|
|
64
|
+
const widget = getCurrentIframPanel(tracker);
|
|
65
|
+
if (widget) {
|
|
66
|
+
widget.autoreload = !widget?.autoreload;
|
|
67
|
+
|
|
68
|
+
commands.notifyCommandChanged(CommandIDs.TOGGLE_AUTORELOAD);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
commands.addCommand(CommandIDs.OPEN_SPECTA, {
|
|
73
|
+
caption: 'Open in Specta',
|
|
74
|
+
isEnabled: () => {
|
|
75
|
+
return tracker.currentWidget !== null;
|
|
76
|
+
},
|
|
77
|
+
icon: linkIcon,
|
|
78
|
+
execute: async () => {
|
|
79
|
+
const context = tracker.currentWidget?.context;
|
|
80
|
+
if (!context) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const spectaUrl = new URL(
|
|
84
|
+
URLExt.join(labBaseUrl, 'specta'),
|
|
85
|
+
window.location.origin
|
|
86
|
+
);
|
|
87
|
+
spectaUrl.searchParams.set('path', context.path);
|
|
88
|
+
window.open(spectaUrl.toString(), '_blank');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Widget } from '@lumino/widgets';
|
|
2
2
|
|
|
3
|
-
export class IFramePanel extends Widget {
|
|
3
|
+
export abstract class IFramePanel extends Widget {
|
|
4
4
|
constructor() {
|
|
5
5
|
super();
|
|
6
6
|
this.addClass('jupyterpack-iframe-panel');
|
|
@@ -20,6 +20,9 @@ export class IFramePanel extends Widget {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
abstract reload(): Promise<void>;
|
|
24
|
+
abstract autoreload: boolean;
|
|
25
|
+
abstract isReady: Promise<void>;
|
|
23
26
|
protected _iframe: HTMLIFrameElement;
|
|
24
27
|
protected _spinner: HTMLDivElement;
|
|
25
28
|
}
|