jupyterpack 0.4.0 → 0.5.2

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 (50) hide show
  1. package/README.md +107 -3
  2. package/lib/document/widgetFactory.js +3 -1
  3. package/lib/pythonServer/baseServer.d.ts +78 -0
  4. package/lib/pythonServer/baseServer.js +175 -0
  5. package/lib/pythonServer/dash/dashServer.d.ts +4 -17
  6. package/lib/pythonServer/dash/dashServer.js +14 -22
  7. package/lib/pythonServer/dash/deps.d.ts +2 -0
  8. package/lib/pythonServer/dash/deps.js +4 -0
  9. package/lib/pythonServer/index.d.ts +3 -3
  10. package/lib/pythonServer/index.js +5 -1
  11. package/lib/pythonServer/kernelExecutor.d.ts +2 -58
  12. package/lib/pythonServer/kernelExecutor.js +0 -72
  13. package/lib/pythonServer/shiny/deps.d.ts +2 -0
  14. package/lib/pythonServer/shiny/deps.js +4 -0
  15. package/lib/pythonServer/shiny/shinyServer.d.ts +10 -0
  16. package/lib/pythonServer/shiny/shinyServer.js +54 -0
  17. package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
  18. package/lib/pythonServer/starlette/starletteServer.js +49 -0
  19. package/lib/pythonServer/streamlit/deps.d.ts +2 -0
  20. package/lib/pythonServer/streamlit/deps.js +10 -0
  21. package/lib/pythonServer/streamlit/streamlitServer.d.ts +4 -9
  22. package/lib/pythonServer/streamlit/streamlitServer.js +16 -15
  23. package/lib/pythonServer/tornado/tornadoServer.d.ts +2 -24
  24. package/lib/pythonServer/tornado/tornadoServer.js +10 -35
  25. package/lib/pythonWidget/pythonWidgetModel.js +2 -0
  26. package/lib/swConnection/mainConnectionManager.d.ts +4 -4
  27. package/lib/swConnection/mainConnectionManager.js +23 -12
  28. package/lib/tools.d.ts +1 -0
  29. package/lib/tools.js +8 -0
  30. package/lib/type.d.ts +27 -9
  31. package/lib/type.js +2 -0
  32. package/lib/websocket/websocket.js +5 -1
  33. package/package.json +1 -1
  34. package/src/document/widgetFactory.ts +3 -1
  35. package/src/pythonServer/baseServer.ts +285 -0
  36. package/src/pythonServer/dash/dashServer.ts +18 -35
  37. package/src/pythonServer/dash/deps.ts +6 -0
  38. package/src/pythonServer/index.ts +9 -5
  39. package/src/pythonServer/kernelExecutor.ts +3 -156
  40. package/src/pythonServer/shiny/deps.ts +6 -0
  41. package/src/pythonServer/shiny/shinyServer.ts +63 -0
  42. package/src/pythonServer/starlette/starletteServer.ts +59 -0
  43. package/src/pythonServer/streamlit/deps.ts +12 -0
  44. package/src/pythonServer/streamlit/streamlitServer.ts +19 -20
  45. package/src/pythonServer/tornado/tornadoServer.ts +11 -55
  46. package/src/pythonWidget/pythonWidgetModel.ts +5 -2
  47. package/src/swConnection/mainConnectionManager.ts +28 -20
  48. package/src/tools.ts +9 -0
  49. package/src/type.ts +34 -12
  50. package/src/websocket/websocket.ts +9 -1
@@ -0,0 +1,59 @@
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+
4
+ export class StarletteServer extends BasePythonServer {
5
+ async init(options: {
6
+ initCode?: string;
7
+ instanceId: string;
8
+ kernelClientId: string;
9
+ }) {
10
+ await super.init(options);
11
+ const { initCode, instanceId, kernelClientId } = options;
12
+ const baseURL = this.buildBaseURL({
13
+ instanceId,
14
+ kernelClientId,
15
+ framework: JupyterPackFramework.STARLETTE
16
+ });
17
+ const bootstrapCode = `
18
+ from jupyterpack.common import set_base_url_env
19
+ set_base_url_env("${baseURL}")
20
+ `;
21
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
22
+ if (initCode) {
23
+ const initCodeWithUrl = initCode.replaceAll('{{base_url}}', baseURL);
24
+ await this.kernelExecutor.executeCode({ code: initCodeWithUrl });
25
+ const loaderCode = `
26
+ from jupyterpack.asgi import AsgiServer
27
+ ${this._server_var} = AsgiServer(app, "${baseURL}")
28
+ `;
29
+
30
+ await this.kernelExecutor.executeCode({ code: loaderCode });
31
+ }
32
+ }
33
+
34
+ async disposePythonServer(): Promise<void> {
35
+ await this.kernelExecutor.executeCode({
36
+ code: `${this._server_var}.dispose()`
37
+ });
38
+ for (const element of this._openedWebsockets) {
39
+ await this.closeWebsocket(element);
40
+ }
41
+ }
42
+
43
+ async reloadPythonServer(options: {
44
+ entryPath?: string;
45
+ initCode?: string;
46
+ }): Promise<void> {
47
+ const { initCode } = options;
48
+ if (initCode) {
49
+ await this.kernelExecutor.executeCode({
50
+ code: initCode.replaceAll('{{base_url}}', this._baseUrl ?? '')
51
+ });
52
+ const reloadCode = `
53
+ await ${this._server_var}.dispose()
54
+ ${this._server_var}.reload(app)
55
+ `;
56
+ await this.kernelExecutor.executeCode({ code: reloadCode }, true);
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,12 @@
1
+ import { IDependencies } from '../../type';
2
+
3
+ export const DEPENDENCIES: IDependencies = {
4
+ mamba: [
5
+ 'pyarrow',
6
+ 'altair',
7
+ 'blinker>=1.5.0,<2',
8
+ 'cachetools>=4.0,<7',
9
+ 'protobuf'
10
+ ],
11
+ pip: ['streamlit==1.50.0']
12
+ };
@@ -1,13 +1,15 @@
1
- import { JupyterPackFramework } from '../../type';
2
- import { TornadoServer } from '../tornado/tornadoServer';
1
+ import { IPythonServerInitOptions, JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+ import { DEPENDENCIES } from './deps';
4
+
5
+ export class StreamlitServer extends BasePythonServer {
6
+ async init(options: IPythonServerInitOptions) {
7
+ const mergedOptions: IPythonServerInitOptions = {
8
+ ...options,
9
+ dependencies: this.mergeDependencies(options.dependencies, DEPENDENCIES)
10
+ };
11
+ await super.init(mergedOptions);
3
12
 
4
- export class StreamlitServer extends TornadoServer {
5
- async init(options: {
6
- entryPath?: string;
7
- initCode?: string;
8
- instanceId: string;
9
- kernelClientId: string;
10
- }) {
11
13
  const { instanceId, kernelClientId, entryPath } = options;
12
14
  if (!entryPath) {
13
15
  throw new Error(
@@ -21,25 +23,24 @@ export class StreamlitServer extends TornadoServer {
21
23
  });
22
24
 
23
25
  const patchCode = `
24
- from jupyterpack.common import set_base_url_env, patch_tornado, patch_all
25
- patch_all()
26
+ from jupyterpack.common import set_base_url_env, patch_tornado
26
27
  patch_tornado()
27
28
  set_base_url_env("${baseURL}")
28
29
  `;
29
- await this.executeCode({ code: patchCode });
30
+ await this.kernelExecutor.executeCode({ code: patchCode });
30
31
 
31
32
  const bootstrapCode = `
32
33
  from jupyterpack.streamlit import patch_streamlit
33
34
  patch_streamlit()
34
35
  `;
35
- await this.executeCode({ code: bootstrapCode });
36
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
36
37
 
37
38
  const stCode = `
38
39
  from jupyterpack.streamlit import StreamlitServer, create_streamlit_app
39
40
  __jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${baseURL}")
40
- ${this._SERVER_VAR} = StreamlitServer(__jupyterpack_tor_app, "${baseURL}", __jupyterpack_st_server)
41
+ ${this._server_var} = StreamlitServer(__jupyterpack_tor_app, "${baseURL}", __jupyterpack_st_server)
41
42
  `;
42
- await this.executeCode({ code: stCode });
43
+ await this.kernelExecutor.executeCode({ code: stCode });
43
44
  }
44
45
 
45
46
  async reloadPythonServer(options: {
@@ -51,17 +52,15 @@ export class StreamlitServer extends TornadoServer {
51
52
  return;
52
53
  }
53
54
  const reloadCode = `
54
- ${this._SERVER_VAR}.dispose()
55
+ ${this._server_var}.dispose()
55
56
  __jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${this._baseUrl}")
56
- ${this._SERVER_VAR}.reload(__jupyterpack_tor_app, __jupyterpack_st_server)
57
+ ${this._server_var}.reload(__jupyterpack_tor_app, __jupyterpack_st_server)
57
58
  `;
58
- await this.executeCode(
59
+ await this.kernelExecutor.executeCode(
59
60
  {
60
61
  code: reloadCode
61
62
  },
62
63
  true
63
64
  );
64
65
  }
65
-
66
- protected _SERVER_VAR = '__jupyterpack_streamlit_server';
67
66
  }
@@ -1,8 +1,7 @@
1
- import { stringOrNone } from '../../tools';
2
- import { IDict, JupyterPackFramework } from '../../type';
3
- import { KernelExecutor } from '../kernelExecutor';
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
4
3
 
5
- export class TornadoServer extends KernelExecutor {
4
+ export class TornadoServer extends BasePythonServer {
6
5
  async init(options: {
7
6
  initCode?: string;
8
7
  instanceId: string;
@@ -22,76 +21,33 @@ export class TornadoServer extends KernelExecutor {
22
21
  patch_tornado()
23
22
 
24
23
  `;
25
- await this.executeCode({ code: bootstrapCode });
24
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
26
25
  if (initCode) {
27
26
  const initCodeWithUrl = initCode.replaceAll('{{base_url}}', baseURL);
28
- await this.executeCode({ code: initCodeWithUrl });
27
+ await this.kernelExecutor.executeCode({ code: initCodeWithUrl });
29
28
  const loaderCode = `
30
29
  from jupyterpack.tornado import TornadoServer
31
- ${this._SERVER_VAR} = TornadoServer(app, "${baseURL}")
30
+ ${this._server_var} = TornadoServer(app, "${baseURL}")
32
31
  `;
33
32
 
34
- await this.executeCode({ code: loaderCode });
33
+ await this.kernelExecutor.executeCode({ code: loaderCode });
35
34
  }
36
35
  }
37
36
 
38
- getResponseFunctionFactory(options: {
39
- urlPath: string;
40
- method: string;
41
- headers: IDict;
42
- params?: string;
43
- content?: string;
44
- }) {
45
- const { method, urlPath, headers, params, content } = options;
46
- const code = `await ${this._SERVER_VAR}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
47
- return code;
48
- }
49
-
50
- openWebsocketFunctionFactory(options: {
51
- instanceId: string;
52
- kernelId: string;
53
- wsUrl: string;
54
- protocol?: string;
55
- }): string {
56
- const { instanceId, kernelId, wsUrl, protocol } = options;
57
-
58
- const code = `await ${this._SERVER_VAR}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
59
- return code;
60
- }
61
-
62
- sendWebsocketMessageFunctionFactory(options: {
63
- instanceId: string;
64
- kernelId: string;
65
- wsUrl: string;
66
- message: string;
67
- }): string {
68
- const { instanceId, kernelId, wsUrl, message } = options;
69
- const code = `await ${this._SERVER_VAR}.receive_ws_message("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
70
- return code;
71
- }
72
-
73
- async disposePythonServer(): Promise<void> {
74
- await this.executeCode({
75
- code: `${this._SERVER_VAR}.dispose()`
76
- });
77
- }
78
-
79
37
  async reloadPythonServer(options: {
80
38
  entryPath?: string;
81
39
  initCode?: string;
82
40
  }): Promise<void> {
83
41
  const { initCode } = options;
84
42
  if (initCode) {
85
- await this.executeCode({
43
+ await this.kernelExecutor.executeCode({
86
44
  code: initCode.replaceAll('{{base_url}}', this._baseUrl ?? '')
87
45
  });
88
46
  const reloadCode = `
89
- ${this._SERVER_VAR}.dispose()
90
- ${this._SERVER_VAR}.reload(app)
47
+ ${this._server_var}.dispose()
48
+ ${this._server_var}.reload(app)
91
49
  `;
92
- await this.executeCode({ code: reloadCode }, true);
50
+ await this.kernelExecutor.executeCode({ code: reloadCode }, true);
93
51
  }
94
52
  }
95
-
96
- protected _SERVER_VAR = '__jupyterpack_tornado_server';
97
53
  }
@@ -11,9 +11,9 @@ import { PromiseDelegate } from '@lumino/coreutils';
11
11
  import { PYTHON_SERVER } from '../pythonServer';
12
12
  import { Signal } from '@lumino/signaling';
13
13
  import {
14
+ IBasePythonServer,
14
15
  IConnectionManager,
15
16
  IJupyterPackFileFormat,
16
- IKernelExecutor,
17
17
  IPythonWidgetModel,
18
18
  JupyterPackFramework
19
19
  } from '../type';
@@ -135,9 +135,12 @@ export class PythonWidgetModel implements IPythonWidgetModel {
135
135
  sessionConnection: this._sessionConnection
136
136
  }));
137
137
  const data = await this._connectionManager.registerConnection(executor);
138
+
138
139
  await executor.init({
139
140
  initCode: entryContent.content,
140
141
  entryPath: spkContent.entry,
142
+ dependencies: spkContent.dependencies,
143
+ disableDependencies: spkContent.disableDependencies,
141
144
  ...data
142
145
  });
143
146
  const finish = new PromiseDelegate<void>();
@@ -203,7 +206,7 @@ export class PythonWidgetModel implements IPythonWidgetModel {
203
206
  private _connectionManager: IConnectionManager;
204
207
  private _contentsManager: Contents.IManager;
205
208
  private _jpackModel: IJupyterPackFileFormat;
206
- private _executor?: IKernelExecutor;
209
+ private _executor?: IBasePythonServer;
207
210
  private _localPath: string;
208
211
 
209
212
  private _serverReloaded: Signal<IPythonWidgetModel, void> = new Signal<
@@ -1,9 +1,9 @@
1
- import { arrayBufferToBase64 } from '../tools';
1
+ import { arrayBufferToBase64, stringToBase64 } from '../tools';
2
2
  import {
3
+ IBasePythonServer,
3
4
  IBroadcastMessage,
4
5
  IConnectionManager,
5
- IDict,
6
- IKernelExecutor
6
+ IDict
7
7
  } from '../type';
8
8
  import { UUID } from '@lumino/coreutils';
9
9
 
@@ -16,20 +16,19 @@ import { UUID } from '@lumino/coreutils';
16
16
  * It's running on the main thread
17
17
  */
18
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
- }
19
+ constructor(public instanceId: string) {}
25
20
 
26
21
  async registerConnection(
27
- kernelExecutor: IKernelExecutor
22
+ pythonServer: IBasePythonServer
28
23
  ): Promise<{ instanceId: string; kernelClientId: string }> {
29
24
  const uuid = UUID.uuid4();
30
25
 
31
- this._kernelExecutors.set(uuid, kernelExecutor);
32
-
26
+ this._pythonServers.set(uuid, pythonServer);
27
+ const wsbc = new BroadcastChannel(
28
+ `/jupyterpack/ws/${this.instanceId}/${uuid}`
29
+ );
30
+ this._initWsChannel(wsbc);
31
+ this._wsBroadcastChannelMap.set(`${this.instanceId}/${uuid}`, wsbc);
33
32
  return { instanceId: this.instanceId, kernelClientId: uuid };
34
33
  }
35
34
 
@@ -43,7 +42,7 @@ export class ConnectionManager implements IConnectionManager {
43
42
  }): Promise<IDict | null> {
44
43
  const { urlPath, kernelClientId, method, params, requestBody, headers } =
45
44
  options;
46
- const executor = this._kernelExecutors.get(kernelClientId);
45
+ const executor = this._pythonServers.get(kernelClientId);
47
46
  if (!executor) {
48
47
  return null;
49
48
  }
@@ -57,8 +56,8 @@ export class ConnectionManager implements IConnectionManager {
57
56
  });
58
57
  return response;
59
58
  }
60
- private _initWsChannel() {
61
- this._wsBroadcastChannel.onmessage = event => {
59
+ private _initWsChannel(broadcastChannel: BroadcastChannel) {
60
+ broadcastChannel.onmessage = event => {
62
61
  const rawData = event.data;
63
62
  let data: IBroadcastMessage;
64
63
  if (typeof rawData === 'string') {
@@ -68,13 +67,13 @@ export class ConnectionManager implements IConnectionManager {
68
67
  }
69
68
 
70
69
  const { action, dest, wsUrl, payload } = data;
71
- const executor = this._kernelExecutors.get(dest);
70
+ const executor = this._pythonServers.get(dest);
72
71
  if (!executor) {
73
72
  console.error(
74
73
  'Missing kernel handle for message',
75
74
  data,
76
75
  dest,
77
- this._kernelExecutors
76
+ this._pythonServers
78
77
  );
79
78
  return;
80
79
  }
@@ -89,6 +88,14 @@ export class ConnectionManager implements IConnectionManager {
89
88
  });
90
89
  break;
91
90
  }
91
+ case 'close': {
92
+ executor.closeWebsocket({
93
+ instanceId: this.instanceId,
94
+ kernelId: dest,
95
+ wsUrl
96
+ });
97
+ break;
98
+ }
92
99
  case 'send': {
93
100
  let serializedData: string;
94
101
  let isBinary: boolean;
@@ -97,7 +104,8 @@ export class ConnectionManager implements IConnectionManager {
97
104
  serializedData = arrayBufferToBase64(payload as any);
98
105
  isBinary = true;
99
106
  } else if (typeof payload === 'string') {
100
- serializedData = payload;
107
+ // convert string to base64 string to avoid encoding problem
108
+ serializedData = stringToBase64(payload);
101
109
  isBinary = false;
102
110
  } else {
103
111
  console.error('Unknown message type', payload);
@@ -116,6 +124,6 @@ export class ConnectionManager implements IConnectionManager {
116
124
  }
117
125
  };
118
126
  }
119
- private _kernelExecutors = new Map<string, IKernelExecutor>();
120
- private _wsBroadcastChannel: BroadcastChannel;
127
+ private _pythonServers = new Map<string, IBasePythonServer>();
128
+ private _wsBroadcastChannelMap: Map<string, BroadcastChannel> = new Map();
121
129
  }
package/src/tools.ts CHANGED
@@ -53,6 +53,15 @@ export function base64ToString(base64: string): string {
53
53
  return new TextDecoder('utf-8').decode(bytes);
54
54
  }
55
55
 
56
+ export function stringToBase64(str: string): string {
57
+ const bytes = new TextEncoder().encode(str);
58
+ let binary = '';
59
+ for (let i = 0; i < bytes.length; i++) {
60
+ binary += String.fromCharCode(bytes[i]);
61
+ }
62
+ return window.btoa(binary);
63
+ }
64
+
56
65
  export function stringOrNone(content?: string) {
57
66
  return content ? `"${content}"` : 'None';
58
67
  }
package/src/type.ts CHANGED
@@ -26,7 +26,9 @@ export enum JupyterPackFramework {
26
26
  REACT = 'react',
27
27
  DASH = 'dash',
28
28
  STREAMLIT = 'streamlit',
29
- TORNADO = 'tornado'
29
+ TORNADO = 'tornado',
30
+ SHINY = 'shiny',
31
+ STARLETTE = 'starlette'
30
32
  }
31
33
  export interface IJupyterPackFileFormat {
32
34
  entry: string;
@@ -36,6 +38,8 @@ export interface IJupyterPackFileFormat {
36
38
  autoreload?: boolean;
37
39
  };
38
40
  rootUrl?: string;
41
+ dependencies?: IDependencies;
42
+ disableDependencies?: boolean;
39
43
  }
40
44
 
41
45
  export enum MessageAction {
@@ -49,7 +53,15 @@ export interface IKernelExecutorParams {
49
53
  params?: string;
50
54
  requestBody?: ArrayBuffer;
51
55
  }
56
+
52
57
  export interface IKernelExecutor extends IDisposable {
58
+ executeCode(
59
+ code: KernelMessage.IExecuteRequestMsg['content'],
60
+ waitForResult?: boolean
61
+ ): Promise<string | null>;
62
+ }
63
+
64
+ export interface IBasePythonServer extends IDisposable {
53
65
  getResponse(options: IKernelExecutorParams): Promise<IDict>;
54
66
  openWebsocket(options: {
55
67
  instanceId: string;
@@ -57,22 +69,18 @@ export interface IKernelExecutor extends IDisposable {
57
69
  wsUrl: string;
58
70
  protocol?: string;
59
71
  }): Promise<void>;
60
- sendWebsocketMessage(options: {
72
+ closeWebsocket(options: {
61
73
  instanceId: string;
62
74
  kernelId: string;
63
75
  wsUrl: string;
64
- message: string;
65
76
  }): Promise<void>;
66
- executeCode(
67
- code: KernelMessage.IExecuteRequestMsg['content'],
68
- waitForResult?: boolean
69
- ): Promise<string | null>;
70
- init(options: {
71
- entryPath?: string;
72
- initCode?: string;
77
+ sendWebsocketMessage(options: {
73
78
  instanceId: string;
74
- kernelClientId: string;
79
+ kernelId: string;
80
+ wsUrl: string;
81
+ message: string;
75
82
  }): Promise<void>;
83
+ init(options: IPythonServerInitOptions): Promise<void>;
76
84
  disposePythonServer(): Promise<void>;
77
85
  reloadPythonServer(options: {
78
86
  entryPath?: string;
@@ -89,7 +97,7 @@ export interface IKernelExecutor extends IDisposable {
89
97
 
90
98
  export interface IConnectionManager {
91
99
  registerConnection(
92
- kernelExecutor: IKernelExecutor
100
+ kernelExecutor: IBasePythonServer
93
101
  ): Promise<{ instanceId: string; kernelClientId: string }>;
94
102
  generateResponse(
95
103
  option: { kernelClientId: string } & IKernelExecutorParams
@@ -114,3 +122,17 @@ export interface IPythonWidgetModel extends IDisposable {
114
122
  | { success: false; error: string }
115
123
  >;
116
124
  }
125
+
126
+ export interface IDependencies {
127
+ mamba?: string[];
128
+ pip?: string[];
129
+ }
130
+
131
+ export interface IPythonServerInitOptions {
132
+ entryPath?: string;
133
+ initCode?: string;
134
+ instanceId: string;
135
+ kernelClientId: string;
136
+ dependencies?: IDependencies;
137
+ disableDependencies?: boolean;
138
+ }
@@ -59,7 +59,9 @@
59
59
  }
60
60
  return data;
61
61
  };
62
- const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
62
+ const bcWsChannel = new BroadcastChannel(
63
+ `/jupyterpack/ws/${instanceId}/${kernelClientId}`
64
+ );
63
65
 
64
66
  class BroadcastChannelWebSocket implements WebSocket {
65
67
  constructor(url: string | URL, protocols?: string | string[]) {
@@ -109,6 +111,10 @@
109
111
  cb();
110
112
  }
111
113
  this._eventHandlers['close'] = [];
114
+ sendTypedMessage({
115
+ action: 'close',
116
+ wsUrl: this.url
117
+ });
112
118
  bcWsChannel.removeEventListener('message', this._bcMessageHandler);
113
119
 
114
120
  this.readyState = this.CLOSED;
@@ -168,10 +174,12 @@
168
174
  } else {
169
175
  data = rawData;
170
176
  }
177
+
171
178
  const { action, dest, wsUrl, payload } = data;
172
179
  if (dest !== kernelClientId || wsUrl !== this.url) {
173
180
  return;
174
181
  }
182
+
175
183
  switch (action) {
176
184
  case 'connected': {
177
185
  this.readyState = this.OPEN;