jupyterpack 0.2.1 → 0.3.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.
Files changed (66) hide show
  1. package/README.md +2 -0
  2. package/lib/document/widgetFactory.js +4 -1
  3. package/lib/pythonServer/common/generatedPythonFiles.d.ts +2 -0
  4. package/lib/pythonServer/common/generatedPythonFiles.js +72 -0
  5. package/lib/pythonServer/dash/dashServer.d.ts +24 -0
  6. package/lib/pythonServer/dash/dashServer.js +39 -0
  7. package/lib/pythonServer/dash/generatedPythonFiles.d.ts +2 -0
  8. package/lib/pythonServer/dash/generatedPythonFiles.js +31 -0
  9. package/lib/pythonServer/index.d.ts +5 -0
  10. package/lib/pythonServer/index.js +9 -0
  11. package/lib/pythonServer/kernelExecutor.d.ts +66 -0
  12. package/lib/pythonServer/kernelExecutor.js +133 -0
  13. package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +2 -0
  14. package/lib/pythonServer/streamlit/generatedPythonFiles.js +147 -0
  15. package/lib/pythonServer/streamlit/streamlitServer.d.ts +33 -0
  16. package/lib/pythonServer/streamlit/streamlitServer.js +55 -0
  17. package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +3 -0
  18. package/lib/pythonServer/tornado/generatedPythonFiles.js +456 -0
  19. package/lib/pythonServer/tornado/tornadoServer.d.ts +32 -0
  20. package/lib/pythonServer/tornado/tornadoServer.js +51 -0
  21. package/lib/pythonWidget/pythonWidget.d.ts +1 -0
  22. package/lib/pythonWidget/pythonWidget.js +9 -3
  23. package/lib/pythonWidget/pythonWidgetModel.d.ts +12 -3
  24. package/lib/pythonWidget/pythonWidgetModel.js +32 -10
  25. package/lib/swConnection/index.js +2 -2
  26. package/lib/{pythonWidget/connectionManager.d.ts → swConnection/mainConnectionManager.d.ts} +10 -0
  27. package/lib/swConnection/mainConnectionManager.js +93 -0
  28. package/lib/swConnection/sw.js +1 -1
  29. package/lib/swConnection/swCommManager.d.ts +11 -0
  30. package/lib/swConnection/{comm_manager.js → swCommManager.js} +5 -0
  31. package/lib/tools.d.ts +4 -0
  32. package/lib/tools.js +58 -0
  33. package/lib/type.d.ts +37 -2
  34. package/lib/type.js +2 -0
  35. package/lib/websocket/websocket.d.ts +0 -0
  36. package/lib/websocket/websocket.js +152 -0
  37. package/package.json +8 -5
  38. package/src/document/widgetFactory.ts +4 -1
  39. package/src/global.d.ts +4 -0
  40. package/src/pythonServer/common/generatedPythonFiles.ts +73 -0
  41. package/src/pythonServer/dash/dashServer.ts +57 -0
  42. package/src/pythonServer/dash/generatedPythonFiles.ts +32 -0
  43. package/src/pythonServer/index.ts +18 -0
  44. package/src/pythonServer/kernelExecutor.ts +229 -0
  45. package/src/pythonServer/streamlit/generatedPythonFiles.ts +148 -0
  46. package/src/pythonServer/streamlit/streamlitServer.ts +87 -0
  47. package/src/pythonServer/tornado/generatedPythonFiles.ts +457 -0
  48. package/src/pythonServer/tornado/tornadoServer.ts +80 -0
  49. package/src/pythonWidget/pythonWidget.ts +20 -3
  50. package/src/pythonWidget/pythonWidgetModel.ts +53 -19
  51. package/src/swConnection/index.ts +5 -2
  52. package/src/swConnection/mainConnectionManager.ts +121 -0
  53. package/src/swConnection/sw.ts +1 -1
  54. package/src/swConnection/{comm_manager.ts → swCommManager.ts} +6 -0
  55. package/src/tools.ts +69 -0
  56. package/src/type.ts +47 -3
  57. package/src/websocket/websocket.ts +216 -0
  58. package/lib/pythonWidget/connectionManager.js +0 -27
  59. package/lib/pythonWidget/kernelExecutor.d.ts +0 -27
  60. package/lib/pythonWidget/kernelExecutor.js +0 -104
  61. package/lib/swConnection/comm_manager.d.ts +0 -6
  62. package/lib/swConnection/connection_manager.d.ts +0 -18
  63. package/lib/swConnection/connection_manager.js +0 -27
  64. package/src/pythonWidget/connectionManager.ts +0 -43
  65. package/src/pythonWidget/kernelExecutor.ts +0 -140
  66. package/src/swConnection/connection_manager.ts +0 -43
@@ -1,15 +1,21 @@
1
+ import { PathExt } from '@jupyterlab/coreutils';
1
2
  import { DocumentRegistry } from '@jupyterlab/docregistry';
2
- import { IDisposable } from '@lumino/disposable';
3
3
  import {
4
- ServiceManager,
5
- Session,
4
+ Contents,
6
5
  Kernel,
7
- Contents
6
+ ServiceManager,
7
+ Session
8
8
  } from '@jupyterlab/services';
9
9
  import { PromiseDelegate } from '@lumino/coreutils';
10
- import { IConnectionManager, IJupyterPackFileFormat } from '../type';
11
- import { KernelExecutor } from './kernelExecutor';
12
- import { PathExt } from '@jupyterlab/coreutils';
10
+ import { IDisposable } from '@lumino/disposable';
11
+
12
+ import { PYTHON_SERVER } from '../pythonServer';
13
+ import {
14
+ IConnectionManager,
15
+ IJupyterPackFileFormat,
16
+ IKernelExecutor,
17
+ JupyterPackFramework
18
+ } from '../type';
13
19
 
14
20
  export class PythonWidgetModel implements IDisposable {
15
21
  constructor(options: PythonWidgetModel.IOptions) {
@@ -17,6 +23,7 @@ export class PythonWidgetModel implements IDisposable {
17
23
  this._manager = options.manager;
18
24
  this._connectionManager = options.connectionManager;
19
25
  this._contentsManager = options.contentsManager;
26
+ this._jpackModel = options.jpackModel;
20
27
  }
21
28
 
22
29
  get isDisposed(): boolean {
@@ -25,19 +32,27 @@ export class PythonWidgetModel implements IDisposable {
25
32
  get connectionManager(): IConnectionManager {
26
33
  return this._connectionManager;
27
34
  }
28
- async initialize(): Promise<{
29
- instanceId: string;
30
- kernelClientId: string;
31
- } | null> {
35
+ async initialize(): Promise<
36
+ | {
37
+ success: true;
38
+ instanceId: string;
39
+ kernelClientId: string;
40
+ rootUrl: string;
41
+ framework: JupyterPackFramework;
42
+ }
43
+ | { success: false; error: string }
44
+ > {
32
45
  if (this._kernelStarted) {
33
- return null;
46
+ return {
47
+ success: false,
48
+ error: 'Server is called twice'
49
+ };
34
50
  }
35
51
  const filePath = this._context.localPath;
36
- const spkContent =
37
- this._context.model.toJSON() as any as IJupyterPackFileFormat;
52
+ const spkContent = this._jpackModel;
38
53
 
39
54
  const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
40
-
55
+ const rootUrl = spkContent.rootUrl ?? '/';
41
56
  const entryContent = await this._contentsManager.get(entryPath, {
42
57
  content: true,
43
58
  format: 'text'
@@ -47,7 +62,10 @@ export class PythonWidgetModel implements IDisposable {
47
62
  await this._manager.kernelspecs.ready;
48
63
  const specs = this._manager.kernelspecs.specs;
49
64
  if (!specs) {
50
- return null;
65
+ return {
66
+ success: false,
67
+ error: 'Missing kernel spec'
68
+ };
51
69
  }
52
70
  const { kernelspecs } = specs;
53
71
  let kernelName = Object.keys(kernelspecs)[0];
@@ -63,12 +81,21 @@ export class PythonWidgetModel implements IDisposable {
63
81
  },
64
82
  type: 'notebook'
65
83
  });
66
- const executor = new KernelExecutor({
84
+ const framework = spkContent.framework;
85
+ const ServerClass = PYTHON_SERVER.get(framework);
86
+ if (!ServerClass) {
87
+ return {
88
+ success: false,
89
+ error: `Framework "${framework}" is not supported. Please check your .spk file.`
90
+ };
91
+ }
92
+ const executor = (this._executor = new ServerClass({
67
93
  sessionConnection: this._sessionConnection
68
- });
94
+ }));
69
95
  const data = await this._connectionManager.registerConnection(executor);
70
96
  await executor.init({
71
97
  initCode: entryContent.content,
98
+ entryPath: spkContent.entry,
72
99
  ...data
73
100
  });
74
101
  const finish = new PromiseDelegate<void>();
@@ -82,9 +109,13 @@ export class PythonWidgetModel implements IDisposable {
82
109
 
83
110
  await finish.promise;
84
111
  this._kernelStarted = true;
85
- return data;
112
+ return { ...data, rootUrl, framework, success: true };
86
113
  }
87
114
  dispose(): void {
115
+ if (this._isDisposed) {
116
+ return;
117
+ }
118
+ void this._executor?.disposePythonServer();
88
119
  this._isDisposed = true;
89
120
  }
90
121
 
@@ -95,10 +126,13 @@ export class PythonWidgetModel implements IDisposable {
95
126
  private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
96
127
  private _connectionManager: IConnectionManager;
97
128
  private _contentsManager: Contents.IManager;
129
+ private _jpackModel: IJupyterPackFileFormat;
130
+ private _executor?: IKernelExecutor;
98
131
  }
99
132
 
100
133
  export namespace PythonWidgetModel {
101
134
  export interface IOptions {
135
+ jpackModel: IJupyterPackFileFormat;
102
136
  context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
103
137
  manager: ServiceManager.IManager;
104
138
  connectionManager: IConnectionManager;
@@ -8,7 +8,7 @@ import { expose } from 'comlink';
8
8
 
9
9
  import { IConnectionManagerToken } from '../token';
10
10
  import { IConnectionManager, MessageAction } from '../type';
11
- import { ConnectionManager } from './connection_manager';
11
+ import { ConnectionManager } from './mainConnectionManager';
12
12
 
13
13
  const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
14
14
  const SCOPE = `${fullLabextensionsUrl}/jupyterpack/static`;
@@ -66,7 +66,6 @@ export const swPlugin: JupyterFrontEndPlugin<IConnectionManager> = {
66
66
  autoStart: true,
67
67
  provides: IConnectionManagerToken,
68
68
  activate: async (app: JupyterFrontEnd): Promise<IConnectionManager> => {
69
- console.log('Activating jupyterpack service worker');
70
69
  const serviceWorker = await initServiceWorker();
71
70
  if (!serviceWorker) {
72
71
  throw new Error(
@@ -75,6 +74,10 @@ export const swPlugin: JupyterFrontEndPlugin<IConnectionManager> = {
75
74
  }
76
75
 
77
76
  const instanceId = UUID.uuid4();
77
+ console.log(
78
+ 'Activating jupyterpack service worker with instance id',
79
+ instanceId
80
+ );
78
81
  const { port1: mainToServiceWorker, port2: serviceWorkerToMain } =
79
82
  new MessageChannel();
80
83
 
@@ -0,0 +1,121 @@
1
+ import { arrayBufferToBase64 } from '../tools';
2
+ import {
3
+ IBroadcastMessage,
4
+ IConnectionManager,
5
+ IDict,
6
+ IKernelExecutor
7
+ } from '../type';
8
+ import { UUID } from '@lumino/coreutils';
9
+
10
+ /**
11
+ * Manages connections between clients and kernel executors.
12
+ * This class handles the registration of kernel executors and the generation of responses
13
+ * for client requests. It maintains a mapping of kernel client IDs to their respective executors.
14
+ * The HTTP requests intercepted by the service worker are forwarded to the appropriate kernel executor.
15
+ * The websocket messages forwarded from the broadcast channel are also forwarded to the appropriate kernel executor.
16
+ * It's running on the main thread
17
+ */
18
+ export class ConnectionManager implements IConnectionManager {
19
+ constructor(public instanceId: string) {
20
+ this._wsBroadcastChannel = new BroadcastChannel(
21
+ `/jupyterpack/ws/${instanceId}`
22
+ );
23
+ this._initWsChannel();
24
+ }
25
+
26
+ async registerConnection(
27
+ kernelExecutor: IKernelExecutor
28
+ ): Promise<{ instanceId: string; kernelClientId: string }> {
29
+ const uuid = UUID.uuid4();
30
+
31
+ this._kernelExecutors.set(uuid, kernelExecutor);
32
+
33
+ return { instanceId: this.instanceId, kernelClientId: uuid };
34
+ }
35
+
36
+ async generateResponse(options: {
37
+ kernelClientId: string;
38
+ urlPath: string;
39
+ method: string;
40
+ headers: IDict;
41
+ requestBody?: ArrayBuffer;
42
+ params?: string;
43
+ }): Promise<IDict | null> {
44
+ const { urlPath, kernelClientId, method, params, requestBody, headers } =
45
+ options;
46
+ const executor = this._kernelExecutors.get(kernelClientId);
47
+ if (!executor) {
48
+ return null;
49
+ }
50
+
51
+ const response = await executor.getResponse({
52
+ urlPath,
53
+ method,
54
+ params,
55
+ headers,
56
+ requestBody
57
+ });
58
+ return response;
59
+ }
60
+ private _initWsChannel() {
61
+ this._wsBroadcastChannel.onmessage = event => {
62
+ const rawData = event.data;
63
+ let data: IBroadcastMessage;
64
+ if (typeof rawData === 'string') {
65
+ data = JSON.parse(rawData);
66
+ } else {
67
+ data = rawData;
68
+ }
69
+
70
+ const { action, dest, wsUrl, payload } = data;
71
+ const executor = this._kernelExecutors.get(dest);
72
+ if (!executor) {
73
+ console.error(
74
+ 'Missing kernel handle for message',
75
+ data,
76
+ dest,
77
+ this._kernelExecutors
78
+ );
79
+ return;
80
+ }
81
+
82
+ switch (action) {
83
+ case 'open': {
84
+ executor.openWebsocket({
85
+ instanceId: this.instanceId,
86
+ kernelId: dest,
87
+ wsUrl,
88
+ protocol: payload.protocol
89
+ });
90
+ break;
91
+ }
92
+ case 'send': {
93
+ let serializedData: string;
94
+ let isBinary: boolean;
95
+ if (payload instanceof ArrayBuffer || ArrayBuffer.isView(payload)) {
96
+ // Convert data to base64 string
97
+ serializedData = arrayBufferToBase64(payload as any);
98
+ isBinary = true;
99
+ } else if (typeof payload === 'string') {
100
+ serializedData = payload;
101
+ isBinary = false;
102
+ } else {
103
+ console.error('Unknown message type', payload);
104
+ return;
105
+ }
106
+ executor.sendWebsocketMessage({
107
+ instanceId: this.instanceId,
108
+ kernelId: dest,
109
+ wsUrl,
110
+ message: JSON.stringify({ isBinary, data: serializedData })
111
+ });
112
+ break;
113
+ }
114
+ default:
115
+ break;
116
+ }
117
+ };
118
+ }
119
+ private _kernelExecutors = new Map<string, IKernelExecutor>();
120
+ private _wsBroadcastChannel: BroadcastChannel;
121
+ }
@@ -1,6 +1,6 @@
1
1
  // import { expose } from 'comlink';
2
2
  import { MessageAction } from '../type';
3
- import { CommManager } from './comm_manager';
3
+ import { CommManager } from './swCommManager';
4
4
 
5
5
  const COMM_MANAGER = new CommManager();
6
6
  /**
@@ -1,5 +1,11 @@
1
1
  import { IConnectionManager, IDict } from '../type';
2
2
  import { wrap, transfer } from 'comlink';
3
+
4
+ /**
5
+ * Manages communication between different components using Comlink's MessagePort-based communication.
6
+ * This class handles registration of communication channels and processing of requests.
7
+ * It's running on the service worker thread
8
+ */
3
9
  export class CommManager {
4
10
  constructor() {}
5
11
  registerComm(instanceId: string, port: MessagePort): void {
package/src/tools.ts CHANGED
@@ -16,3 +16,72 @@ export function arrayBufferToBase64(buffer: ArrayBuffer) {
16
16
  }
17
17
  return btoa(binary);
18
18
  }
19
+
20
+ export function base64ToArrayBuffer(base64: string): Uint8Array {
21
+ const binaryString = atob(base64);
22
+ const bytes = new Uint8Array(binaryString.length);
23
+ for (let i = 0; i < binaryString.length; i++) {
24
+ bytes[i] = binaryString.charCodeAt(i);
25
+ }
26
+ return bytes;
27
+ }
28
+
29
+ export function base64ToString(base64: string): string {
30
+ const bytes = base64ToArrayBuffer(base64);
31
+ return new TextDecoder('utf-8').decode(bytes);
32
+ }
33
+
34
+ export function stringOrNone(content?: string) {
35
+ return content ? `"${content}"` : 'None';
36
+ }
37
+
38
+ export function isBinaryContentType(contentType?: string) {
39
+ if (!contentType) {
40
+ // no Content-Type → assume binary for safety
41
+ return true;
42
+ }
43
+
44
+ contentType = contentType.toLowerCase().trim();
45
+
46
+ const textTypes = [
47
+ 'text/',
48
+ 'application/json',
49
+ 'application/javascript',
50
+ 'application/xml',
51
+ 'application/xhtml+xml',
52
+ 'application/x-www-form-urlencoded',
53
+ 'application/sql',
54
+ 'application/graphql',
55
+ 'application/yaml'
56
+ ];
57
+
58
+ const binaryIndicators = [
59
+ 'image/',
60
+ 'audio/',
61
+ 'video/',
62
+ 'font/',
63
+ 'application/octet-stream',
64
+ 'application/pdf',
65
+ 'application/zip',
66
+ 'application/x-protobuf',
67
+ 'application/vnd'
68
+ ];
69
+
70
+ // Starts with text/ or one of the textual types
71
+ if (textTypes.some(t => contentType.startsWith(t))) {
72
+ return false;
73
+ }
74
+
75
+ // Starts with binary-indicating prefix
76
+ if (binaryIndicators.some(t => contentType.startsWith(t))) {
77
+ return true;
78
+ }
79
+
80
+ // If charset is specified → text
81
+ if (contentType.includes('charset=')) {
82
+ return false;
83
+ }
84
+
85
+ // Default: assume binary
86
+ return true;
87
+ }
package/src/type.ts CHANGED
@@ -5,15 +5,32 @@ export interface IDict<T = any> {
5
5
  [key: string]: T;
6
6
  }
7
7
 
8
+ export interface IBroadcastMessage {
9
+ action:
10
+ | 'message'
11
+ | 'open'
12
+ | 'close'
13
+ | 'error'
14
+ | 'send'
15
+ | 'connected'
16
+ | 'backend_message';
17
+ dest: string;
18
+ wsUrl: string;
19
+ payload?: any;
20
+ }
21
+
8
22
  export enum JupyterPackFramework {
9
23
  REACT = 'react',
10
- DASH = 'dash'
24
+ DASH = 'dash',
25
+ STREAMLIT = 'streamlit',
26
+ TORNADO = 'tornado'
11
27
  }
12
28
  export interface IJupyterPackFileFormat {
13
29
  entry: string;
14
30
  framework: JupyterPackFramework;
15
31
  name?: string;
16
32
  metadata?: IDict;
33
+ rootUrl?: string;
17
34
  }
18
35
 
19
36
  export enum MessageAction {
@@ -29,9 +46,36 @@ export interface IKernelExecutorParams {
29
46
  }
30
47
  export interface IKernelExecutor extends IDisposable {
31
48
  getResponse(options: IKernelExecutorParams): Promise<IDict>;
49
+ openWebsocket(options: {
50
+ instanceId: string;
51
+ kernelId: string;
52
+ wsUrl: string;
53
+ protocol?: string;
54
+ }): Promise<void>;
55
+ sendWebsocketMessage(options: {
56
+ instanceId: string;
57
+ kernelId: string;
58
+ wsUrl: string;
59
+ message: string;
60
+ }): Promise<void>;
32
61
  executeCode(
33
- code: KernelMessage.IExecuteRequestMsg['content']
34
- ): Promise<string>;
62
+ code: KernelMessage.IExecuteRequestMsg['content'],
63
+ waitForResult?: boolean
64
+ ): Promise<string | null>;
65
+ init(options: {
66
+ entryPath?: string;
67
+ initCode?: string;
68
+ instanceId: string;
69
+ kernelClientId: string;
70
+ }): Promise<void>;
71
+ disposePythonServer(): Promise<void>;
72
+ getResponseFunctionFactory(options: {
73
+ urlPath: string;
74
+ method: string;
75
+ headers: IDict;
76
+ params?: string;
77
+ content?: string;
78
+ }): string;
35
79
  }
36
80
 
37
81
  export interface IConnectionManager {
@@ -0,0 +1,216 @@
1
+ (() => {
2
+ const urlPath = new URL(window.location.href).pathname;
3
+ const pathAfterExtensionName: string | undefined = urlPath.split(
4
+ '/jupyterpack/static'
5
+ )[1];
6
+ const pathList = pathAfterExtensionName?.split('/').filter(Boolean);
7
+ const instanceId = pathList?.[0];
8
+ const kernelClientId = pathList?.[2];
9
+ if (!instanceId || !kernelClientId) {
10
+ throw new Error('Missing instance Id or kernelClient Id');
11
+ }
12
+
13
+ interface IBroadcastMessage {
14
+ action:
15
+ | 'message'
16
+ | 'open'
17
+ | 'close'
18
+ | 'error'
19
+ | 'send'
20
+ | 'connected'
21
+ | 'backend_message';
22
+ dest: string;
23
+ wsUrl: string;
24
+ payload?: any;
25
+ }
26
+
27
+ const sendTypedMessage = (msg: Omit<IBroadcastMessage, 'dest'>) => {
28
+ bcWsChannel.postMessage({ ...msg, dest: kernelClientId });
29
+ };
30
+
31
+ function base64ToBinary(base64: string, dataType: BinaryType) {
32
+ const binary = atob(base64); // decode base64
33
+ const len = binary.length;
34
+ const bytes = new Uint8Array(len);
35
+ for (let i = 0; i < len; i++) {
36
+ bytes[i] = binary.charCodeAt(i);
37
+ }
38
+ if (dataType === 'arraybuffer') {
39
+ return bytes.buffer;
40
+ } else if (dataType === 'blob') {
41
+ return new Blob([bytes], { type: 'application/octet-stream' });
42
+ } else {
43
+ throw new Error("Unsupported type: use 'arraybuffer' or 'blob'");
44
+ }
45
+ }
46
+
47
+ const decodeServerMessage = (
48
+ payload: {
49
+ data: string;
50
+ isBinary: boolean;
51
+ },
52
+ binaryType: BinaryType
53
+ ) => {
54
+ const { data, isBinary } = payload;
55
+ if (isBinary) {
56
+ // Decode base64 string to array buffer or blob
57
+
58
+ return base64ToBinary(data, binaryType);
59
+ }
60
+ return data;
61
+ };
62
+ const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
63
+
64
+ class BroadcastChannelWebSocket implements WebSocket {
65
+ constructor(url: string | URL, protocols?: string | string[]) {
66
+ const urlObj = new URL(url);
67
+ this.url = urlObj.pathname + urlObj.search + urlObj.hash;
68
+
69
+ if (protocols) {
70
+ this.protocol = Array.isArray(protocols)
71
+ ? protocols.join(',')
72
+ : protocols;
73
+ } else {
74
+ this.protocol = '';
75
+ }
76
+ this.binaryType = 'blob';
77
+ this.bufferedAmount = 0;
78
+ this.extensions = '';
79
+
80
+ this.readyState = this.CONNECTING;
81
+ this._open();
82
+ }
83
+
84
+ onclose: ((this: WebSocket, ev?: CloseEvent) => any) | null = () => {
85
+ // no-op
86
+ };
87
+ onerror: ((this: WebSocket, ev: Event) => any) | null = () => {
88
+ // no-op
89
+ };
90
+ onmessage:
91
+ | ((this: WebSocket, ev: MessageEvent | { data: any }) => any)
92
+ | null = () => {
93
+ // no-op
94
+ };
95
+ onopen: ((this: WebSocket, ev: Event | { data: any }) => any) | null =
96
+ () => {
97
+ // no-op
98
+ };
99
+ close(code?: unknown, reason?: unknown): void {
100
+ if (this.readyState === this.CLOSED) {
101
+ return;
102
+ }
103
+
104
+ if (this.onclose) {
105
+ this.onclose();
106
+ }
107
+ while (this._eventHandlers['close'].length) {
108
+ const cb = this._eventHandlers['close'].pop();
109
+ cb();
110
+ }
111
+ this._eventHandlers['close'] = [];
112
+ bcWsChannel.removeEventListener('message', this._bcMessageHandler);
113
+
114
+ this.readyState = this.CLOSED;
115
+ }
116
+ send(data: unknown): void {
117
+ sendTypedMessage({
118
+ action: 'send',
119
+ payload: data,
120
+ wsUrl: this.url
121
+ });
122
+ }
123
+
124
+ addEventListener(
125
+ type: 'message' | 'open' | 'close' | 'error',
126
+ listener: unknown,
127
+ options?: unknown
128
+ ): void {
129
+ this._eventHandlers[type].push(listener);
130
+ }
131
+ removeEventListener(
132
+ type: 'message' | 'open' | 'close' | 'error',
133
+ listener: unknown,
134
+ options?: unknown
135
+ ): void {
136
+ this._eventHandlers[type] = this._eventHandlers[type].filter(
137
+ handler => handler !== listener
138
+ );
139
+ }
140
+
141
+ dispatchEvent(event: unknown): boolean {
142
+ return true;
143
+ }
144
+ readyState: number;
145
+ url: string;
146
+ protocol: string;
147
+ binaryType: BinaryType;
148
+ bufferedAmount: number;
149
+ extensions: string;
150
+
151
+ readonly CONNECTING = 0;
152
+ readonly OPEN = 1;
153
+ readonly CLOSING = 2;
154
+ readonly CLOSED = 3;
155
+
156
+ private _eventHandlers: {
157
+ message: any[];
158
+ open: any[];
159
+ close: any[];
160
+ error: any[];
161
+ } = { message: [], open: [], close: [], error: [] };
162
+
163
+ private _bcMessageHandler = async (event: MessageEvent) => {
164
+ const rawData = event.data;
165
+ let data: IBroadcastMessage;
166
+ if (typeof rawData === 'string') {
167
+ data = JSON.parse(rawData);
168
+ } else {
169
+ data = rawData;
170
+ }
171
+ const { action, dest, wsUrl, payload } = data;
172
+ if (dest !== kernelClientId || wsUrl !== this.url) {
173
+ return;
174
+ }
175
+ switch (action) {
176
+ case 'connected': {
177
+ this.readyState = this.OPEN;
178
+
179
+ if (this.onopen) {
180
+ this.onopen(event);
181
+ }
182
+ this._eventHandlers.open.forEach(handler =>
183
+ handler({ data: payload })
184
+ );
185
+ break;
186
+ }
187
+
188
+ case 'backend_message': {
189
+ const decoded = decodeServerMessage(payload, this.binaryType);
190
+ if (this.onmessage) {
191
+ this.onmessage({ data: decoded });
192
+ }
193
+ this._eventHandlers.message.forEach(handler =>
194
+ handler({ data: decoded })
195
+ );
196
+ break;
197
+ }
198
+ default:
199
+ break;
200
+ }
201
+ };
202
+ private _open = () => {
203
+ sendTypedMessage({
204
+ action: 'open',
205
+ payload: {
206
+ protocol: this.protocol
207
+ },
208
+ wsUrl: this.url
209
+ });
210
+
211
+ bcWsChannel.addEventListener('message', this._bcMessageHandler);
212
+ };
213
+ }
214
+
215
+ window.WebSocket = BroadcastChannelWebSocket as any;
216
+ })();
@@ -1,27 +0,0 @@
1
- import { UUID } from '@lumino/coreutils';
2
- export class ConnectionManager {
3
- constructor(instanceId) {
4
- this.instanceId = instanceId;
5
- this._kernelExecutors = new Map();
6
- }
7
- async registerConnection(kernelExecutor) {
8
- const uuid = UUID.uuid4();
9
- this._kernelExecutors.set(uuid, kernelExecutor);
10
- return { instanceId: this.instanceId, kernelClientId: uuid };
11
- }
12
- async generateResponse(options) {
13
- const { urlPath, kernelClientId, method, params, requestBody, headers } = options;
14
- const executor = this._kernelExecutors.get(kernelClientId);
15
- if (!executor) {
16
- return null;
17
- }
18
- const response = await executor.getResponse({
19
- urlPath,
20
- method,
21
- params,
22
- headers,
23
- requestBody
24
- });
25
- return response;
26
- }
27
- }