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.
Files changed (83) hide show
  1. package/README.md +2 -0
  2. package/lib/document/commands.d.ts +8 -0
  3. package/lib/document/commands.js +76 -0
  4. package/lib/document/iframePanel.d.ts +4 -1
  5. package/lib/document/plugin.d.ts +2 -1
  6. package/lib/document/plugin.js +22 -4
  7. package/lib/document/toolbar.d.ts +9 -0
  8. package/lib/document/toolbar.js +20 -0
  9. package/lib/document/widgetFactory.d.ts +2 -1
  10. package/lib/document/widgetFactory.js +15 -6
  11. package/lib/index.d.ts +1 -1
  12. package/lib/pythonServer/dash/dashServer.d.ts +22 -0
  13. package/lib/pythonServer/dash/dashServer.js +47 -0
  14. package/lib/pythonServer/index.d.ts +5 -0
  15. package/lib/pythonServer/index.js +9 -0
  16. package/lib/pythonServer/kernelExecutor.d.ts +71 -0
  17. package/lib/pythonServer/kernelExecutor.js +140 -0
  18. package/lib/pythonServer/streamlit/streamlitServer.d.ts +14 -0
  19. package/lib/pythonServer/streamlit/streamlitServer.js +51 -0
  20. package/lib/pythonServer/tornado/tornadoServer.d.ts +34 -0
  21. package/lib/pythonServer/tornado/tornadoServer.js +68 -0
  22. package/lib/pythonWidget/comm.d.ts +11 -0
  23. package/lib/pythonWidget/comm.js +52 -0
  24. package/lib/pythonWidget/pythonWidget.d.ts +6 -0
  25. package/lib/pythonWidget/pythonWidget.js +28 -3
  26. package/lib/pythonWidget/pythonWidgetModel.d.ts +27 -6
  27. package/lib/pythonWidget/pythonWidgetModel.js +101 -15
  28. package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
  29. package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
  30. package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
  31. package/lib/sandpackWidget/sandpackPanel.js +38 -3
  32. package/lib/swConnection/index.js +2 -2
  33. package/lib/{pythonWidget/connectionManager.d.ts → swConnection/mainConnectionManager.d.ts} +10 -0
  34. package/lib/swConnection/mainConnectionManager.js +93 -0
  35. package/lib/swConnection/sw.js +1 -1
  36. package/lib/swConnection/swCommManager.d.ts +11 -0
  37. package/lib/swConnection/{comm_manager.js → swCommManager.js} +5 -0
  38. package/lib/token.d.ts +2 -1
  39. package/lib/token.js +1 -0
  40. package/lib/tools.d.ts +9 -0
  41. package/lib/tools.js +75 -0
  42. package/lib/type.d.ts +64 -3
  43. package/lib/type.js +2 -0
  44. package/lib/websocket/websocket.d.ts +0 -0
  45. package/lib/websocket/websocket.js +152 -0
  46. package/package.json +8 -5
  47. package/src/document/commands.ts +91 -0
  48. package/src/document/iframePanel.ts +4 -1
  49. package/src/document/plugin.ts +28 -7
  50. package/src/document/toolbar.ts +39 -0
  51. package/src/document/widgetFactory.ts +17 -6
  52. package/src/global.d.ts +9 -0
  53. package/src/pythonServer/dash/dashServer.ts +66 -0
  54. package/src/pythonServer/index.ts +18 -0
  55. package/src/pythonServer/kernelExecutor.ts +243 -0
  56. package/src/pythonServer/streamlit/streamlitServer.ts +67 -0
  57. package/src/pythonServer/tornado/tornadoServer.ts +97 -0
  58. package/src/pythonWidget/comm.ts +65 -0
  59. package/src/pythonWidget/pythonWidget.ts +38 -3
  60. package/src/pythonWidget/pythonWidgetModel.ts +155 -34
  61. package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
  62. package/src/sandpackWidget/sandpackPanel.ts +45 -4
  63. package/src/swConnection/index.ts +5 -2
  64. package/src/swConnection/mainConnectionManager.ts +121 -0
  65. package/src/swConnection/sw.ts +1 -1
  66. package/src/swConnection/{comm_manager.ts → swCommManager.ts} +6 -0
  67. package/src/token.ts +5 -1
  68. package/src/tools.ts +91 -0
  69. package/src/type.ts +76 -4
  70. package/src/websocket/websocket.ts +216 -0
  71. package/style/base.css +7 -0
  72. package/style/icons/autoreload.svg +16 -0
  73. package/style/icons/box.svg +12 -0
  74. package/style/icons/externallink.svg +10 -0
  75. package/lib/pythonWidget/connectionManager.js +0 -27
  76. package/lib/pythonWidget/kernelExecutor.d.ts +0 -27
  77. package/lib/pythonWidget/kernelExecutor.js +0 -104
  78. package/lib/swConnection/comm_manager.d.ts +0 -6
  79. package/lib/swConnection/connection_manager.d.ts +0 -18
  80. package/lib/swConnection/connection_manager.js +0 -27
  81. package/src/pythonWidget/connectionManager.ts +0 -43
  82. package/src/pythonWidget/kernelExecutor.ts +0 -140
  83. package/src/swConnection/connection_manager.ts +0 -43
@@ -0,0 +1,65 @@
1
+ import { Kernel, KernelMessage } from '@jupyterlab/services';
2
+
3
+ const COMM_NAME = 'jupyterpack:broadcast:comm';
4
+
5
+ export class CommBroadcastManager {
6
+ constructor() {
7
+ this._kernels = new Map();
8
+ this._comms = new Map();
9
+ }
10
+
11
+ registerKernel(kernel: Kernel.IKernelConnection) {
12
+ this._kernels.set(kernel.id, kernel);
13
+ kernel.registerCommTarget(COMM_NAME, (comm, msg) =>
14
+ this._handle_comm_open(comm, msg, kernel.id)
15
+ );
16
+ }
17
+ unregisterKernel(kernelId?: string) {
18
+ if (kernelId) {
19
+ this._kernels.delete(kernelId);
20
+ const comms = this._comms.get(kernelId) ?? [];
21
+ comms.forEach(comm => comm.dispose());
22
+ this._comms.delete(kernelId);
23
+ }
24
+ }
25
+
26
+ private _handle_comm_open = async (
27
+ comm: Kernel.IComm,
28
+ msg: KernelMessage.ICommOpenMsg,
29
+ kernelId: string
30
+ ): Promise<void> => {
31
+ if (this._comms.has(kernelId)) {
32
+ this._comms.get(kernelId)?.push(comm);
33
+ } else {
34
+ this._comms.set(kernelId, [comm]);
35
+ }
36
+ const channelName = msg.metadata.channel_name as string | undefined;
37
+ if (!channelName) {
38
+ return;
39
+ }
40
+ if (!this._broadcastChannels.has(channelName)) {
41
+ this._broadcastChannels.set(
42
+ channelName,
43
+ new BroadcastChannel(channelName)
44
+ );
45
+ }
46
+ const broadcastChannel = this._broadcastChannels.get(channelName)!;
47
+ comm.onMsg = commMsg => {
48
+ const { data } = commMsg.content;
49
+ broadcastChannel.postMessage(data);
50
+ };
51
+ };
52
+
53
+ dispose() {
54
+ this._kernels.clear();
55
+ this._comms.clear();
56
+ this._broadcastChannels.forEach(it => {
57
+ it.close();
58
+ });
59
+ this._broadcastChannels.clear();
60
+ }
61
+
62
+ private _kernels: Map<string, Kernel.IKernelConnection> = new Map();
63
+ private _comms: Map<string, Kernel.IComm[]> = new Map();
64
+ private _broadcastChannels: Map<string, BroadcastChannel> = new Map();
65
+ }
@@ -1,29 +1,64 @@
1
1
  import { PythonWidgetModel } from './pythonWidgetModel';
2
- import { PageConfig } from '@jupyterlab/coreutils';
2
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
3
3
  import { IFramePanel } from '../document/iframePanel';
4
+ import { PromiseDelegate } from '@lumino/coreutils';
4
5
 
5
6
  export class PythonWidget extends IFramePanel {
6
7
  constructor(options: PythonWidget.IOptions) {
7
8
  super();
8
9
  this._model = options.model;
9
10
  this._model.initialize().then(connectionData => {
10
- if (!connectionData) {
11
+ if (!connectionData.success) {
12
+ this.toggleSpinner(false);
13
+ this._iframe.contentDocument!.body.innerText = `Failed to start server: ${connectionData.error}`;
11
14
  return;
12
15
  }
16
+ this._isReady.resolve();
13
17
  const iframe = this._iframe;
14
18
  const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
15
- iframe.src = `${fullLabextensionsUrl}/jupyterpack/static/${connectionData.instanceId}/dash/${connectionData.kernelClientId}/`;
19
+
20
+ const iframeUrl = URLExt.join(
21
+ fullLabextensionsUrl,
22
+ 'jupyterpack/static',
23
+ connectionData.instanceId,
24
+ connectionData.framework,
25
+ connectionData.kernelClientId,
26
+ connectionData.rootUrl
27
+ );
28
+ iframe.src = iframeUrl;
29
+
16
30
  iframe.addEventListener('load', () => {
17
31
  this.toggleSpinner(false);
18
32
  });
33
+ this._model.serverReloaded.connect(() => {
34
+ this._iframe?.contentWindow?.location?.reload();
35
+ });
19
36
  });
20
37
  }
21
38
 
39
+ get autoreload() {
40
+ return this._model.autoreload;
41
+ }
42
+ set autoreload(value: boolean) {
43
+ this._model.autoreload = value;
44
+ }
45
+ get isReady(): Promise<void> {
46
+ return this._isReady.promise;
47
+ }
22
48
  get model(): PythonWidgetModel {
23
49
  return this._model;
24
50
  }
25
51
 
52
+ async reload(): Promise<void> {
53
+ await this._model.reload();
54
+ }
55
+
56
+ dispose(): void {
57
+ this._model.dispose();
58
+ }
59
+
26
60
  private _model: PythonWidgetModel;
61
+ private _isReady = new PromiseDelegate<void>();
27
62
  }
28
63
 
29
64
  export namespace PythonWidget {
@@ -1,53 +1,98 @@
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';
13
10
 
14
- export class PythonWidgetModel implements IDisposable {
11
+ import { PYTHON_SERVER } from '../pythonServer';
12
+ import { Signal } from '@lumino/signaling';
13
+ import {
14
+ IConnectionManager,
15
+ IJupyterPackFileFormat,
16
+ IKernelExecutor,
17
+ IPythonWidgetModel,
18
+ JupyterPackFramework
19
+ } from '../type';
20
+ import { CommBroadcastManager } from './comm';
21
+ import { IS_LITE } from '../tools';
22
+
23
+ export class PythonWidgetModel implements IPythonWidgetModel {
15
24
  constructor(options: PythonWidgetModel.IOptions) {
16
25
  this._context = options.context;
17
26
  this._manager = options.manager;
18
27
  this._connectionManager = options.connectionManager;
19
28
  this._contentsManager = options.contentsManager;
29
+ this._jpackModel = options.jpackModel;
30
+ this._localPath = PathExt.dirname(this._context.localPath);
31
+ this._autoreload = Boolean(this._jpackModel?.metadata?.autoreload);
32
+
33
+ this._contentsManager.fileChanged.connect(this._onFileChanged, this);
34
+ }
35
+
36
+ get autoreload() {
37
+ return this._autoreload;
20
38
  }
21
39
 
40
+ set autoreload(val: boolean) {
41
+ this._autoreload = val;
42
+ }
22
43
  get isDisposed(): boolean {
23
44
  return this._isDisposed;
24
45
  }
25
46
  get connectionManager(): IConnectionManager {
26
47
  return this._connectionManager;
27
48
  }
28
- async initialize(): Promise<{
29
- instanceId: string;
30
- kernelClientId: string;
31
- } | null> {
32
- if (this._kernelStarted) {
33
- return null;
49
+ get serverReloaded() {
50
+ return this._serverReloaded;
51
+ }
52
+ get kernelStatusChanged() {
53
+ return this._kernelStatusChanged;
54
+ }
55
+
56
+ async reload() {
57
+ if (!this._kernelStarted) {
58
+ return;
34
59
  }
35
- const filePath = this._context.localPath;
36
- const spkContent =
37
- this._context.model.toJSON() as any as IJupyterPackFileFormat;
60
+ const { spkContent, entryContent } = await this._loadData();
61
+ await this._executor?.reloadPythonServer({
62
+ entryPath: spkContent.entry,
63
+ initCode: entryContent.content
64
+ });
65
+ this._serverReloaded.emit();
66
+ }
38
67
 
39
- const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
68
+ async initialize(): Promise<
69
+ | {
70
+ success: true;
71
+ instanceId: string;
72
+ kernelClientId: string;
73
+ rootUrl: string;
74
+ framework: JupyterPackFramework;
75
+ }
76
+ | { success: false; error: string }
77
+ > {
78
+ if (this._kernelStarted) {
79
+ return {
80
+ success: false,
81
+ error: 'Server is called twice'
82
+ };
83
+ }
40
84
 
41
- const entryContent = await this._contentsManager.get(entryPath, {
42
- content: true,
43
- format: 'text'
44
- });
85
+ const { filePath, spkContent, rootUrl, entryContent } =
86
+ await this._loadData();
45
87
  const sessionManager = this._manager.sessions;
46
88
  await sessionManager.ready;
47
89
  await this._manager.kernelspecs.ready;
48
90
  const specs = this._manager.kernelspecs.specs;
49
91
  if (!specs) {
50
- return null;
92
+ return {
93
+ success: false,
94
+ error: 'Missing kernel spec'
95
+ };
51
96
  }
52
97
  const { kernelspecs } = specs;
53
98
  let kernelName = Object.keys(kernelspecs)[0];
@@ -55,20 +100,44 @@ export class PythonWidgetModel implements IDisposable {
55
100
  kernelName = specs.default;
56
101
  }
57
102
 
58
- this._sessionConnection = await sessionManager.startNew({
59
- name: filePath,
60
- path: filePath,
61
- kernel: {
62
- name: kernelName
103
+ this._sessionConnection = await sessionManager.startNew(
104
+ {
105
+ name: filePath,
106
+ path: filePath,
107
+ kernel: {
108
+ name: kernelName
109
+ },
110
+ type: 'notebook'
63
111
  },
64
- type: 'notebook'
65
- });
66
- const executor = new KernelExecutor({
112
+ {
113
+ kernelConnectionOptions: { handleComms: true }
114
+ }
115
+ );
116
+ const kernel = this._sessionConnection.kernel;
117
+ if (kernel) {
118
+ this._kernelStatusChanged.emit('started');
119
+ this._commBroadcastManager.registerKernel(kernel);
120
+ kernel.disposed.connect(() => {
121
+ this._kernelStatusChanged.emit('stopped');
122
+ this._commBroadcastManager.unregisterKernel(kernel.id);
123
+ });
124
+ }
125
+
126
+ const framework = spkContent.framework;
127
+ const ServerClass = PYTHON_SERVER.get(framework);
128
+ if (!ServerClass) {
129
+ return {
130
+ success: false,
131
+ error: `Framework "${framework}" is not supported. Please check your .spk file.`
132
+ };
133
+ }
134
+ const executor = (this._executor = new ServerClass({
67
135
  sessionConnection: this._sessionConnection
68
- });
136
+ }));
69
137
  const data = await this._connectionManager.registerConnection(executor);
70
138
  await executor.init({
71
139
  initCode: entryContent.content,
140
+ entryPath: spkContent.entry,
72
141
  ...data
73
142
  });
74
143
  const finish = new PromiseDelegate<void>();
@@ -82,12 +151,50 @@ export class PythonWidgetModel implements IDisposable {
82
151
 
83
152
  await finish.promise;
84
153
  this._kernelStarted = true;
85
- return data;
154
+ return { ...data, rootUrl, framework, success: true };
86
155
  }
87
- dispose(): void {
156
+ async dispose(): Promise<void> {
157
+ if (this._isDisposed) {
158
+ return;
159
+ }
160
+ if (!IS_LITE) {
161
+ this._sessionConnection?.kernel?.shutdown();
162
+ }
163
+ void this._executor?.disposePythonServer();
164
+ this._contentsManager.fileChanged.disconnect(this._onFileChanged);
165
+ this._commBroadcastManager.dispose();
88
166
  this._isDisposed = true;
89
167
  }
90
168
 
169
+ private async _loadData() {
170
+ const filePath = this._context.localPath;
171
+ const spkContent = this._jpackModel;
172
+
173
+ const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
174
+ const rootUrl = spkContent.rootUrl ?? '/';
175
+ const entryContent = await this._contentsManager.get(entryPath, {
176
+ content: true,
177
+ format: 'text'
178
+ });
179
+ return { filePath, spkContent, rootUrl, entryContent };
180
+ }
181
+
182
+ private async _onFileChanged(
183
+ sender: Contents.IManager,
184
+ args: Contents.IChangedArgs
185
+ ) {
186
+ if (this._autoreload && args.type === 'save') {
187
+ if (
188
+ args.newValue?.path &&
189
+ args.newValue.path.startsWith(this._localPath)
190
+ ) {
191
+ await this.reload();
192
+ }
193
+ }
194
+ }
195
+
196
+ private _commBroadcastManager = new CommBroadcastManager();
197
+
91
198
  private _isDisposed = false;
92
199
  private _kernelStarted = false;
93
200
  private _sessionConnection: Session.ISessionConnection | undefined;
@@ -95,10 +202,24 @@ export class PythonWidgetModel implements IDisposable {
95
202
  private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
96
203
  private _connectionManager: IConnectionManager;
97
204
  private _contentsManager: Contents.IManager;
205
+ private _jpackModel: IJupyterPackFileFormat;
206
+ private _executor?: IKernelExecutor;
207
+ private _localPath: string;
208
+
209
+ private _serverReloaded: Signal<IPythonWidgetModel, void> = new Signal<
210
+ IPythonWidgetModel,
211
+ void
212
+ >(this);
213
+ private _kernelStatusChanged: Signal<
214
+ IPythonWidgetModel,
215
+ 'started' | 'stopped'
216
+ > = new Signal(this);
217
+ private _autoreload: boolean;
98
218
  }
99
219
 
100
220
  export namespace PythonWidgetModel {
101
221
  export interface IOptions {
222
+ jpackModel: IJupyterPackFileFormat;
102
223
  context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
103
224
  manager: ServiceManager.IManager;
104
225
  connectionManager: IConnectionManager;
@@ -3,16 +3,17 @@ import { ISignal, Signal } from '@lumino/signaling';
3
3
 
4
4
  import { IDict } from '../type';
5
5
  import { removePrefix } from '../tools';
6
+ import { IDisposable } from '@lumino/disposable';
6
7
 
7
- export class SandpackFilesModel {
8
+ export class SandpackFilesModel implements IDisposable {
8
9
  constructor(options: { contentsManager: Contents.IManager; path: string }) {
9
10
  this._contentManager = options.contentsManager;
10
11
  this._path = options.path;
11
12
  this._contentManager.fileChanged.connect(this._onFileChanged, this);
12
13
  }
13
14
 
14
- async getAllFiles(): Promise<IDict<{ code: string }>> {
15
- if (!this._allFiles) {
15
+ async getAllFiles(force = false): Promise<IDict<{ code: string }>> {
16
+ if (!this._allFiles || force) {
16
17
  const files = await this._contentManager.get(this._path, {
17
18
  content: true
18
19
  });
@@ -21,6 +22,9 @@ export class SandpackFilesModel {
21
22
 
22
23
  return this._allFiles;
23
24
  }
25
+ get isDisposed(): boolean {
26
+ return this._isDisposed;
27
+ }
24
28
 
25
29
  get fileChanged(): ISignal<
26
30
  SandpackFilesModel,
@@ -29,6 +33,14 @@ export class SandpackFilesModel {
29
33
  return this._fileChanged;
30
34
  }
31
35
 
36
+ dispose(): void {
37
+ if (this._isDisposed) {
38
+ return;
39
+ }
40
+ this._isDisposed = true;
41
+ this._contentManager.fileChanged.disconnect(this._onFileChanged);
42
+ }
43
+
32
44
  async flattenDirectory(
33
45
  dirContent: Contents.IModel
34
46
  ): Promise<IDict<{ code: string }>> {
@@ -138,4 +150,6 @@ export class SandpackFilesModel {
138
150
  private _contentManager: Contents.IManager;
139
151
 
140
152
  private _allFiles?: IDict<{ code: string }>;
153
+
154
+ private _isDisposed = false;
141
155
  }
@@ -7,8 +7,9 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
7
7
  import { Contents } from '@jupyterlab/services';
8
8
 
9
9
  import { IFramePanel } from '../document/iframePanel';
10
- import { IDict } from '../type';
10
+ import { IDict, IJupyterPackFileFormat } from '../type';
11
11
  import { SandpackFilesModel } from './sandpackFilesModel';
12
+ import { PromiseDelegate } from '@lumino/coreutils';
12
13
 
13
14
  export class SandpackPanel extends IFramePanel {
14
15
  constructor(options: {
@@ -17,17 +18,44 @@ export class SandpackPanel extends IFramePanel {
17
18
  }) {
18
19
  super();
19
20
  this._contentsManager = options.contentsManager;
21
+ const { context } = options;
20
22
  options.context.ready.then(async () => {
21
- await this.init(options.context.localPath);
23
+ const jpackModel =
24
+ context.model.toJSON() as any as IJupyterPackFileFormat;
25
+ await this.init(context.localPath, jpackModel);
22
26
  });
23
27
  }
24
28
 
25
- async init(localPath: string) {
29
+ dispose(): void {
30
+ this._fileModel?.fileChanged.disconnect(this._onFileChanged);
31
+ this._fileModel?.dispose();
32
+ this._spClient?.destroy();
33
+ super.dispose();
34
+ }
35
+
36
+ get isReady() {
37
+ return this._isReady.promise;
38
+ }
39
+
40
+ get autoreload(): boolean {
41
+ return this._autoreload;
42
+ }
43
+
44
+ set autoreload(val: boolean) {
45
+ this._autoreload = val;
46
+ }
47
+
48
+ async init(localPath: string, jpackModel: IJupyterPackFileFormat) {
49
+ if (jpackModel?.metadata?.autoreload === true) {
50
+ this._autoreload = true;
51
+ }
52
+
26
53
  const currentDir = localPath.split('/').slice(0, -1).join('/');
27
54
  const filesModel = new SandpackFilesModel({
28
55
  contentsManager: this._contentsManager,
29
56
  path: currentDir
30
57
  });
58
+ this._fileModel = filesModel;
31
59
  const allFiles = await filesModel.getAllFiles();
32
60
 
33
61
  const options: ClientOptions = {
@@ -43,6 +71,7 @@ export class SandpackPanel extends IFramePanel {
43
71
  options
44
72
  );
45
73
  await this.connectSignals(filesModel, this._spClient);
74
+ this._isReady.resolve();
46
75
  }
47
76
 
48
77
  async connectSignals(
@@ -66,11 +95,20 @@ export class SandpackPanel extends IFramePanel {
66
95
  });
67
96
  }
68
97
 
98
+ async reload(): Promise<void> {
99
+ if (this._spClient && this._fileModel) {
100
+ const allFiles = await this._fileModel.getAllFiles();
101
+ this._spClient.updateSandbox({
102
+ files: allFiles
103
+ });
104
+ }
105
+ }
106
+
69
107
  private _onFileChanged(
70
108
  sender: SandpackFilesModel,
71
109
  { allFiles }: { allFiles: IDict<{ code: string }> }
72
110
  ) {
73
- if (this._spClient) {
111
+ if (this._autoreload && this._spClient) {
74
112
  this._spClient.updateSandbox({
75
113
  files: allFiles
76
114
  });
@@ -79,4 +117,7 @@ export class SandpackPanel extends IFramePanel {
79
117
 
80
118
  private _spClient?: SandpackClient;
81
119
  private _contentsManager: Contents.IManager;
120
+ private _fileModel: SandpackFilesModel | undefined;
121
+ private _autoreload = false;
122
+ private _isReady = new PromiseDelegate<void>();
82
123
  }
@@ -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
  /**