@difizen/libro-kernel 0.0.2-alpha.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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/es/basemanager.d.ts +94 -0
  4. package/es/basemanager.d.ts.map +1 -0
  5. package/es/basemanager.js +110 -0
  6. package/es/contents/contents-drive.d.ts +189 -0
  7. package/es/contents/contents-drive.d.ts.map +1 -0
  8. package/es/contents/contents-drive.js +792 -0
  9. package/es/contents/contents-manager.d.ts +229 -0
  10. package/es/contents/contents-manager.d.ts.map +1 -0
  11. package/es/contents/contents-manager.js +551 -0
  12. package/es/contents/contents-module.d.ts +3 -0
  13. package/es/contents/contents-module.d.ts.map +1 -0
  14. package/es/contents/contents-module.js +4 -0
  15. package/es/contents/contents-protocol.d.ts +487 -0
  16. package/es/contents/contents-protocol.d.ts.map +1 -0
  17. package/es/contents/contents-protocol.js +1 -0
  18. package/es/contents/index.d.ts +6 -0
  19. package/es/contents/index.d.ts.map +1 -0
  20. package/es/contents/index.js +5 -0
  21. package/es/contents/validate.d.ts +10 -0
  22. package/es/contents/validate.d.ts.map +1 -0
  23. package/es/contents/validate.js +22 -0
  24. package/es/index.d.ts +10 -0
  25. package/es/index.d.ts.map +1 -0
  26. package/es/index.js +9 -0
  27. package/es/index.less +0 -0
  28. package/es/kernel/comm.d.ts +92 -0
  29. package/es/kernel/comm.d.ts.map +1 -0
  30. package/es/kernel/comm.js +216 -0
  31. package/es/kernel/future.d.ts +178 -0
  32. package/es/kernel/future.d.ts.map +1 -0
  33. package/es/kernel/future.js +587 -0
  34. package/es/kernel/index.d.ts +8 -0
  35. package/es/kernel/index.d.ts.map +1 -0
  36. package/es/kernel/index.js +8 -0
  37. package/es/kernel/kernel-connection.d.ts +550 -0
  38. package/es/kernel/kernel-connection.d.ts.map +1 -0
  39. package/es/kernel/kernel-connection.js +1957 -0
  40. package/es/kernel/kernel-module.d.ts +3 -0
  41. package/es/kernel/kernel-module.d.ts.map +1 -0
  42. package/es/kernel/kernel-module.js +32 -0
  43. package/es/kernel/libro-kernel-manager.d.ts +69 -0
  44. package/es/kernel/libro-kernel-manager.d.ts.map +1 -0
  45. package/es/kernel/libro-kernel-manager.js +349 -0
  46. package/es/kernel/libro-kernel-protocol.d.ts +675 -0
  47. package/es/kernel/libro-kernel-protocol.d.ts.map +1 -0
  48. package/es/kernel/libro-kernel-protocol.js +60 -0
  49. package/es/kernel/libro-kernel-utils.d.ts +95 -0
  50. package/es/kernel/libro-kernel-utils.d.ts.map +1 -0
  51. package/es/kernel/libro-kernel-utils.js +130 -0
  52. package/es/kernel/libro-kernel.d.ts +14 -0
  53. package/es/kernel/libro-kernel.d.ts.map +1 -0
  54. package/es/kernel/libro-kernel.js +54 -0
  55. package/es/kernel/messages.d.ts +845 -0
  56. package/es/kernel/messages.d.ts.map +1 -0
  57. package/es/kernel/messages.js +457 -0
  58. package/es/kernel/restapi.d.ts +78 -0
  59. package/es/kernel/restapi.d.ts.map +1 -0
  60. package/es/kernel/restapi.js +367 -0
  61. package/es/kernel/serialize.d.ts +10 -0
  62. package/es/kernel/serialize.d.ts.map +1 -0
  63. package/es/kernel/serialize.js +214 -0
  64. package/es/kernel/validate.d.ts +15 -0
  65. package/es/kernel/validate.d.ts.map +1 -0
  66. package/es/kernel/validate.js +125 -0
  67. package/es/kernelspec/index.d.ts +5 -0
  68. package/es/kernelspec/index.d.ts.map +1 -0
  69. package/es/kernelspec/index.js +4 -0
  70. package/es/kernelspec/kernelspec-module.d.ts +3 -0
  71. package/es/kernelspec/kernelspec-module.d.ts.map +1 -0
  72. package/es/kernelspec/kernelspec-module.js +4 -0
  73. package/es/kernelspec/kernelspec.d.ts +33 -0
  74. package/es/kernelspec/kernelspec.d.ts.map +1 -0
  75. package/es/kernelspec/kernelspec.js +1 -0
  76. package/es/kernelspec/manager.d.ts +81 -0
  77. package/es/kernelspec/manager.d.ts.map +1 -0
  78. package/es/kernelspec/manager.js +248 -0
  79. package/es/kernelspec/restapi.d.ts +71 -0
  80. package/es/kernelspec/restapi.d.ts.map +1 -0
  81. package/es/kernelspec/restapi.js +107 -0
  82. package/es/kernelspec/validate.d.ts +10 -0
  83. package/es/kernelspec/validate.d.ts.map +1 -0
  84. package/es/kernelspec/validate.js +69 -0
  85. package/es/libro-kernel-connection-manager.d.ts +19 -0
  86. package/es/libro-kernel-connection-manager.d.ts.map +1 -0
  87. package/es/libro-kernel-connection-manager.js +142 -0
  88. package/es/module.d.ts +3 -0
  89. package/es/module.d.ts.map +1 -0
  90. package/es/module.js +9 -0
  91. package/es/page-config.d.ts +36 -0
  92. package/es/page-config.d.ts.map +1 -0
  93. package/es/page-config.js +129 -0
  94. package/es/protocol.d.ts +13 -0
  95. package/es/protocol.d.ts.map +1 -0
  96. package/es/protocol.js +8 -0
  97. package/es/server/connection-error.d.ts +36 -0
  98. package/es/server/connection-error.d.ts.map +1 -0
  99. package/es/server/connection-error.js +109 -0
  100. package/es/server/index.d.ts +6 -0
  101. package/es/server/index.d.ts.map +1 -0
  102. package/es/server/index.js +5 -0
  103. package/es/server/server-connection-protocol.d.ts +49 -0
  104. package/es/server/server-connection-protocol.d.ts.map +1 -0
  105. package/es/server/server-connection-protocol.js +0 -0
  106. package/es/server/server-connection.d.ts +25 -0
  107. package/es/server/server-connection.d.ts.map +1 -0
  108. package/es/server/server-connection.js +159 -0
  109. package/es/server/server-manager.d.ts +22 -0
  110. package/es/server/server-manager.d.ts.map +1 -0
  111. package/es/server/server-manager.js +163 -0
  112. package/es/server/server-module.d.ts +3 -0
  113. package/es/server/server-module.d.ts.map +1 -0
  114. package/es/server/server-module.js +4 -0
  115. package/es/session/index.d.ts +5 -0
  116. package/es/session/index.d.ts.map +1 -0
  117. package/es/session/index.js +4 -0
  118. package/es/session/libro-session-manager.d.ts +71 -0
  119. package/es/session/libro-session-manager.d.ts.map +1 -0
  120. package/es/session/libro-session-manager.js +539 -0
  121. package/es/session/libro-session-protocol.d.ts +50 -0
  122. package/es/session/libro-session-protocol.d.ts.map +1 -0
  123. package/es/session/libro-session-protocol.js +21 -0
  124. package/es/session/libro-session.d.ts +12 -0
  125. package/es/session/libro-session.d.ts.map +1 -0
  126. package/es/session/libro-session.js +19 -0
  127. package/es/session/restapi.d.ts +28 -0
  128. package/es/session/restapi.d.ts.map +1 -0
  129. package/es/session/restapi.js +214 -0
  130. package/es/session/session-module.d.ts +3 -0
  131. package/es/session/session-module.d.ts.map +1 -0
  132. package/es/session/session-module.js +18 -0
  133. package/es/session/validate.d.ts +14 -0
  134. package/es/session/validate.d.ts.map +1 -0
  135. package/es/session/validate.js +37 -0
  136. package/es/utils.d.ts +4 -0
  137. package/es/utils.d.ts.map +1 -0
  138. package/es/utils.js +29 -0
  139. package/es/validate-property.d.ts +2 -0
  140. package/es/validate-property.d.ts.map +1 -0
  141. package/es/validate-property.js +35 -0
  142. package/package.json +62 -0
  143. package/src/basemanager.ts +133 -0
  144. package/src/contents/contents-drive.ts +495 -0
  145. package/src/contents/contents-manager.ts +465 -0
  146. package/src/contents/contents-module.ts +6 -0
  147. package/src/contents/contents-protocol.ts +604 -0
  148. package/src/contents/index.ts +5 -0
  149. package/src/contents/validate.ts +29 -0
  150. package/src/index.tsx +9 -0
  151. package/src/kernel/comm.ts +220 -0
  152. package/src/kernel/future.ts +474 -0
  153. package/src/kernel/index.ts +7 -0
  154. package/src/kernel/kernel-connection.ts +1770 -0
  155. package/src/kernel/kernel-module.ts +50 -0
  156. package/src/kernel/libro-kernel-manager.ts +199 -0
  157. package/src/kernel/libro-kernel-protocol.ts +858 -0
  158. package/src/kernel/libro-kernel-utils.ts +152 -0
  159. package/src/kernel/libro-kernel.ts +39 -0
  160. package/src/kernel/messages.ts +1104 -0
  161. package/src/kernel/restapi.ts +183 -0
  162. package/src/kernel/serialize.ts +262 -0
  163. package/src/kernel/validate.ts +101 -0
  164. package/src/kernelspec/index.ts +5 -0
  165. package/src/kernelspec/kernelspec-module.ts +9 -0
  166. package/src/kernelspec/kernelspec.ts +37 -0
  167. package/src/kernelspec/manager.ts +173 -0
  168. package/src/kernelspec/restapi.ts +104 -0
  169. package/src/kernelspec/validate.ts +80 -0
  170. package/src/libro-kernel-connection-manager.ts +73 -0
  171. package/src/module.ts +19 -0
  172. package/src/page-config.ts +106 -0
  173. package/src/protocol.ts +24 -0
  174. package/src/server/connection-error.ts +60 -0
  175. package/src/server/index.ts +5 -0
  176. package/src/server/server-connection-protocol.ts +57 -0
  177. package/src/server/server-connection.ts +144 -0
  178. package/src/server/server-manager.ts +76 -0
  179. package/src/server/server-module.ts +9 -0
  180. package/src/session/index.ts +4 -0
  181. package/src/session/libro-session-manager.ts +377 -0
  182. package/src/session/libro-session-protocol.ts +61 -0
  183. package/src/session/libro-session.ts +33 -0
  184. package/src/session/restapi.ts +126 -0
  185. package/src/session/session-module.ts +26 -0
  186. package/src/session/validate.ts +39 -0
  187. package/src/utils.ts +28 -0
  188. package/src/validate-property.ts +38 -0
@@ -0,0 +1,1770 @@
1
+ import type { JSONObject } from '@difizen/libro-common';
2
+ import { deepCopy, URL } from '@difizen/libro-common';
3
+ import type { Disposable, Event as ManaEvent } from '@difizen/mana-app';
4
+ import { prop } from '@difizen/mana-app';
5
+ import { Deferred, Emitter } from '@difizen/mana-app';
6
+ import { inject, transient } from '@difizen/mana-app';
7
+ import { v4 } from 'uuid';
8
+
9
+ import type { ISpecModel } from '../kernelspec/index.js';
10
+ import { KernelSpecRestAPI } from '../kernelspec/index.js';
11
+ import { NetworkError, ServerConnection } from '../server/index.js';
12
+ import type { ISettings } from '../server/index.js';
13
+
14
+ import { CommHandler } from './comm.js';
15
+ import type { KernelFutureHandler } from './future.js';
16
+ import { KernelControlFutureHandler, KernelShellFutureHandler } from './future.js';
17
+ import type {
18
+ ConnectionStatus,
19
+ IAnyMessageArgs,
20
+ IComm,
21
+ IControlFuture,
22
+ IFuture,
23
+ IKernelConnection,
24
+ IKernelModel,
25
+ IShellFuture,
26
+ } from './libro-kernel-protocol.js';
27
+ import {
28
+ KernelConnectionOptions,
29
+ LibroKernelConnectionFactory,
30
+ } from './libro-kernel-protocol.js';
31
+ import {
32
+ isDisplayDataMsg,
33
+ isExecuteResultMsg,
34
+ isInfoRequestMsg,
35
+ isUpdateDisplayDataMsg,
36
+ } from './libro-kernel-utils.js';
37
+ import * as KernelMessage from './messages.js';
38
+ import * as restapi from './restapi.js';
39
+ import { KernelRestAPI } from './restapi.js';
40
+ import { deserialize, serialize } from './serialize.js';
41
+ import * as validate from './validate.js';
42
+
43
+ // 以下 三个 没必要follow jupyter lab
44
+
45
+ // Stub for requirejs.
46
+ declare let requirejs: any;
47
+
48
+ /**
49
+ * A protected namespace for the Kernel.
50
+ */
51
+ namespace Private {
52
+ /**
53
+ * Log the current kernel status.
54
+ */
55
+ export function logKernelStatus(kernel: IKernelConnection): void {
56
+ switch (kernel.status) {
57
+ case 'idle':
58
+ case 'busy':
59
+ case 'unknown':
60
+ return;
61
+ default:
62
+ // eslint-disable-next-line no-console
63
+ console.debug(`Kernel: ${kernel.status} (${kernel.id})`);
64
+ break;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Send a kernel message to the kernel and resolve the reply message.
70
+ */
71
+ export async function handleShellMessage<T extends KernelMessage.ShellMessageType>(
72
+ kernel: IKernelConnection,
73
+ msg: KernelMessage.IShellMessage<T>,
74
+ ): Promise<KernelMessage.IShellMessage<KernelMessage.ShellMessageType>> {
75
+ const future = kernel.sendShellMessage(msg, true);
76
+ return future.done;
77
+ }
78
+
79
+ /**
80
+ * Try to load an object from a module or a registry.
81
+ *
82
+ * Try to load an object from a module asynchronously if a module
83
+ * is specified, otherwise tries to load an object from the global
84
+ * registry, if the global registry is provided.
85
+ *
86
+ * #### Notes
87
+ * Loading a module uses requirejs.
88
+ */
89
+ export function loadObject(
90
+ name: string,
91
+ moduleName: string | undefined,
92
+ registry?: Record<string, any>,
93
+ ): Promise<any> {
94
+ return new Promise((resolve, reject) => {
95
+ // Try loading the module using require.js
96
+ if (moduleName) {
97
+ if (typeof requirejs === 'undefined') {
98
+ throw new Error('requirejs not found');
99
+ }
100
+ requirejs(
101
+ [moduleName],
102
+ (mod: any) => {
103
+ if (mod[name] === void 0) {
104
+ const msg = `Object '${name}' not found in module '${moduleName}'`;
105
+ reject(new Error(msg));
106
+ } else {
107
+ resolve(mod[name]);
108
+ }
109
+ },
110
+ reject,
111
+ );
112
+ } else {
113
+ if (registry?.[name]) {
114
+ resolve(registry[name]);
115
+ } else {
116
+ reject(new Error(`Object '${name}' not found in registry`));
117
+ }
118
+ }
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Get a random integer between min and max, inclusive of both.
124
+ *
125
+ * #### Notes
126
+ * From
127
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive
128
+ *
129
+ * From the MDN page: It might be tempting to use Math.round() to accomplish
130
+ * that, but doing so would cause your random numbers to follow a non-uniform
131
+ * distribution, which may not be acceptable for your needs.
132
+ */
133
+ export function getRandomIntInclusive(min: number, max: number): number {
134
+ const _min = Math.ceil(min);
135
+ const _max = Math.floor(max);
136
+ return Math.floor(Math.random() * (_max - _min + 1)) + _min;
137
+ }
138
+ }
139
+
140
+ const KERNEL_INFO_TIMEOUT = 3000;
141
+ const RESTARTING_KERNEL_SESSION = '_RESTARTING_';
142
+ const STARTING_KERNEL_SESSION = '';
143
+
144
+ /**
145
+ * Implementation of the Kernel object.
146
+ *
147
+ * #### Notes
148
+ * Messages from the server are handled in the order they were received and
149
+ * asynchronously. Any message handler can return a promise, and message
150
+ * handling will pause until the promise is fulfilled.
151
+ */
152
+ @transient()
153
+ export class KernelConnection implements IKernelConnection {
154
+ @inject(KernelSpecRestAPI) kernelSpecRestAPI: KernelSpecRestAPI;
155
+ @inject(KernelRestAPI) kernelRestAPI: KernelRestAPI;
156
+ @inject(LibroKernelConnectionFactory)
157
+ libroKernelConnectionFactory: LibroKernelConnectionFactory;
158
+ /**
159
+ * Construct a kernel object.
160
+ */
161
+ constructor(
162
+ @inject(KernelConnectionOptions) options: KernelConnectionOptions,
163
+ @inject(ServerConnection) serverConnection: ServerConnection,
164
+ ) {
165
+ this.serverSettings = { ...serverConnection.settings, ...options.serverSettings };
166
+ this._name = options.model.name;
167
+ this._id = options.model.id;
168
+
169
+ this._clientId = options.clientId ?? v4();
170
+ this._username = options.username ?? '';
171
+ this.handleComms = options.handleComms ?? true;
172
+
173
+ this._createSocket();
174
+ }
175
+
176
+ send(msg: string | ArrayBuffer) {
177
+ this._ws?.send(msg);
178
+ }
179
+
180
+ get onDisposed(): ManaEvent<void> {
181
+ return this.onDisposedEmitter.event;
182
+ }
183
+
184
+ /**
185
+ * The server settings for the kernel.
186
+ */
187
+ readonly serverSettings: ISettings;
188
+
189
+ /**
190
+ * Handle comm messages
191
+ *
192
+ * #### Notes
193
+ * The comm message protocol currently has implicit assumptions that only
194
+ * one kernel connection is handling comm messages. This option allows a
195
+ * kernel connection to opt out of handling comms.
196
+ *
197
+ * See https://github.com/jupyter/jupyter_client/issues/263
198
+ */
199
+ readonly handleComms: boolean;
200
+
201
+ /**
202
+ * A signal emitted when the kernel status changes.
203
+ */
204
+ get statusChanged(): ManaEvent<KernelMessage.Status> {
205
+ return this.statusChangedEmitter.event;
206
+ }
207
+
208
+ /**
209
+ * A signal emitted when the kernel status changes.
210
+ */
211
+ get connectionStatusChanged(): ManaEvent<ConnectionStatus> {
212
+ return this.connectionStatusChangedEmitter.event;
213
+ }
214
+
215
+ /**
216
+ * A signal emitted for iopub kernel messages.
217
+ *
218
+ * #### Notes
219
+ * This signal is emitted after the iopub message is handled asynchronously.
220
+ */
221
+ get iopubMessage(): ManaEvent<KernelMessage.IIOPubMessage> {
222
+ return this.iopubMessageEmitter.event;
223
+ }
224
+
225
+ /**
226
+ * A signal emitted for unhandled kernel message.
227
+ *
228
+ * #### Notes
229
+ * This signal is emitted for a message that was not handled. It is emitted
230
+ * during the asynchronous message handling code.
231
+ */
232
+ get unhandledMessage(): ManaEvent<KernelMessage.IMessage> {
233
+ return this.unhandledMessageEmitter.event;
234
+ }
235
+
236
+ /**
237
+ * The kernel model
238
+ */
239
+ get model(): IKernelModel {
240
+ return (
241
+ this._model || {
242
+ id: this.id,
243
+ name: this.name,
244
+ reason: this._reason,
245
+ }
246
+ );
247
+ }
248
+
249
+ /**
250
+ * A signal emitted for any kernel message.
251
+ *
252
+ * #### Notes
253
+ * This signal is emitted when a message is received, before it is handled
254
+ * asynchronously.
255
+ *
256
+ * This message is emitted when a message is queued for sending (either in
257
+ * the websocket buffer, or our own pending message buffer). The message may
258
+ * actually be sent across the wire at a later time.
259
+ *
260
+ * The message emitted in this signal should not be modified in any way.
261
+ */
262
+ get anyMessage(): ManaEvent<IAnyMessageArgs> {
263
+ return this.anyMessageEmitter.event;
264
+ }
265
+
266
+ /**
267
+ * A signal emitted when a kernel has pending inputs from the user.
268
+ */
269
+ get pendingInput(): ManaEvent<boolean> {
270
+ return this.pendingInputEmitter.event;
271
+ }
272
+
273
+ /**
274
+ * The id of the server-side kernel.
275
+ */
276
+ get id(): string {
277
+ return this._id;
278
+ }
279
+
280
+ /**
281
+ * The name of the server-side kernel.
282
+ */
283
+ get name(): string {
284
+ return this._name;
285
+ }
286
+
287
+ /**
288
+ * The client username.
289
+ */
290
+ get username(): string {
291
+ return this._username;
292
+ }
293
+
294
+ /**
295
+ * The client unique id.
296
+ */
297
+ get clientId(): string {
298
+ return this._clientId;
299
+ }
300
+
301
+ /**
302
+ * The current status of the kernel.
303
+ */
304
+ get status(): KernelMessage.Status {
305
+ return this._status;
306
+ }
307
+
308
+ /**
309
+ * The current connection status of the kernel connection.
310
+ */
311
+ get connectionStatus(): ConnectionStatus {
312
+ return this._connectionStatus;
313
+ }
314
+
315
+ /**
316
+ * Test whether the kernel has been disposed.
317
+ */
318
+ get isDisposed(): boolean {
319
+ return this._isDisposed;
320
+ }
321
+
322
+ /**
323
+ * The cached kernel info.
324
+ *
325
+ * @returns A promise that resolves to the kernel info.
326
+ */
327
+ get info(): Promise<KernelMessage.IInfoReply> {
328
+ return this._info.promise;
329
+ }
330
+
331
+ /**
332
+ * The kernel spec.
333
+ *
334
+ * @returns A promise that resolves to the kernel spec.
335
+ */
336
+ get spec(): Promise<ISpecModel | undefined> {
337
+ if (this._specPromise) {
338
+ return this._specPromise;
339
+ }
340
+ this._specPromise = this.kernelSpecRestAPI
341
+ .getSpecs(this.serverSettings)
342
+ .then((specs) => {
343
+ return specs.kernelspecs[this._name];
344
+ });
345
+ return this._specPromise;
346
+ }
347
+
348
+ /**
349
+ * Clone the current kernel with a new clientId.
350
+ */
351
+ clone(
352
+ options: Pick<
353
+ KernelConnectionOptions,
354
+ 'clientId' | 'username' | 'handleComms'
355
+ > = {},
356
+ ): IKernelConnection {
357
+ return this.libroKernelConnectionFactory({
358
+ model: this.model,
359
+ username: this.username,
360
+ // handleComms defaults to false since that is safer
361
+ handleComms: false,
362
+ ...options,
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Dispose of the resources held by the kernel.
368
+ */
369
+ dispose(): void {
370
+ if (this.isDisposed) {
371
+ return;
372
+ }
373
+ this.onDisposedEmitter.fire();
374
+
375
+ this._updateConnectionStatus('disconnected');
376
+ this._clearKernelState();
377
+ this._pendingMessages = [];
378
+ this._clearSocket();
379
+
380
+ this.connectionStatusChangedEmitter.dispose();
381
+ this.statusChangedEmitter.dispose();
382
+ this.onDisposedEmitter.dispose();
383
+ this.iopubMessageEmitter.dispose();
384
+ this.anyMessageEmitter.dispose();
385
+ this.pendingInputEmitter.dispose();
386
+ this.unhandledMessageEmitter.dispose();
387
+ this._isDisposed = true;
388
+ }
389
+
390
+ /**
391
+ * Send a shell message to the kernel.
392
+ *
393
+ * #### Notes
394
+ * Send a message to the kernel's shell channel, yielding a future object
395
+ * for accepting replies.
396
+ *
397
+ * If `expectReply` is given and `true`, the future is disposed when both a
398
+ * shell reply and an idle status message are received. If `expectReply`
399
+ * is not given or is `false`, the future is resolved when an idle status
400
+ * message is received.
401
+ * If `disposeOnDone` is not given or is `true`, the Future is disposed at this point.
402
+ * If `disposeOnDone` is given and `false`, it is up to the caller to dispose of the Future.
403
+ *
404
+ * All replies are validated as valid kernel messages.
405
+ *
406
+ * If the kernel status is `dead`, this will throw an error.
407
+ */
408
+ sendShellMessage<T extends KernelMessage.ShellMessageType>(
409
+ msg: KernelMessage.IShellMessage<T>,
410
+ expectReply = false,
411
+ disposeOnDone = true,
412
+ ): IShellFuture<KernelMessage.IShellMessage<T>> {
413
+ return this._sendKernelShellControl(
414
+ KernelShellFutureHandler as any,
415
+ msg,
416
+ expectReply,
417
+ disposeOnDone,
418
+ ) as IShellFuture<KernelMessage.IShellMessage<T>>;
419
+ }
420
+
421
+ /**
422
+ * Send a control message to the kernel.
423
+ *
424
+ * #### Notes
425
+ * Send a message to the kernel's control channel, yielding a future object
426
+ * for accepting replies.
427
+ *
428
+ * If `expectReply` is given and `true`, the future is disposed when both a
429
+ * control reply and an idle status message are received. If `expectReply`
430
+ * is not given or is `false`, the future is resolved when an idle status
431
+ * message is received.
432
+ * If `disposeOnDone` is not given or is `true`, the Future is disposed at this point.
433
+ * If `disposeOnDone` is given and `false`, it is up to the caller to dispose of the Future.
434
+ *
435
+ * All replies are validated as valid kernel messages.
436
+ *
437
+ * If the kernel status is `dead`, this will throw an error.
438
+ */
439
+ sendControlMessage<T extends KernelMessage.ControlMessageType>(
440
+ msg: KernelMessage.IControlMessage<T>,
441
+ expectReply = false,
442
+ disposeOnDone = true,
443
+ ): IControlFuture<KernelMessage.IControlMessage<T>> {
444
+ return this._sendKernelShellControl(
445
+ KernelControlFutureHandler as any,
446
+ msg,
447
+ expectReply,
448
+ disposeOnDone,
449
+ ) as IControlFuture<KernelMessage.IControlMessage<T>>;
450
+ }
451
+
452
+ protected _sendKernelShellControl<
453
+ REQUEST extends KernelMessage.IShellControlMessage,
454
+ REPLY extends KernelMessage.IShellControlMessage,
455
+ KFH extends new (...params: any[]) => KernelFutureHandler<REQUEST, REPLY>,
456
+ T extends KernelMessage.IMessage,
457
+ >(
458
+ ctor: KFH,
459
+ msg: T,
460
+ expectReply = false,
461
+ disposeOnDone = true,
462
+ ): IFuture<KernelMessage.IShellControlMessage, KernelMessage.IShellControlMessage> {
463
+ this._sendMessage(msg);
464
+ this.anyMessageEmitter.fire({ msg, direction: 'send' });
465
+
466
+ const future = new ctor(
467
+ () => {
468
+ const msgId = msg.header.msg_id;
469
+ this._futures.delete(msgId);
470
+ // Remove stored display id information.
471
+ const displayIds = this._msgIdToDisplayIds.get(msgId);
472
+ if (!displayIds) {
473
+ return;
474
+ }
475
+ displayIds.forEach((displayId) => {
476
+ const msgIds = this._displayIdToParentIds.get(displayId);
477
+ if (msgIds) {
478
+ const idx = msgIds.indexOf(msgId);
479
+ if (idx === -1) {
480
+ return;
481
+ }
482
+ if (msgIds.length === 1) {
483
+ this._displayIdToParentIds.delete(displayId);
484
+ } else {
485
+ msgIds.splice(idx, 1);
486
+ this._displayIdToParentIds.set(displayId, msgIds);
487
+ }
488
+ }
489
+ });
490
+ this._msgIdToDisplayIds.delete(msgId);
491
+ },
492
+ msg,
493
+ expectReply,
494
+ disposeOnDone,
495
+ this,
496
+ );
497
+ this._futures.set(msg.header.msg_id, future as any);
498
+ return future as any;
499
+ }
500
+
501
+ /**
502
+ * Send a message on the websocket.
503
+ *
504
+ * If queue is true, queue the message for later sending if we cannot send
505
+ * now. Otherwise throw an error.
506
+ *
507
+ * #### Notes
508
+ * As an exception to the queueing, if we are sending a kernel_info_request
509
+ * message while we think the kernel is restarting, we send the message
510
+ * immediately without queueing. This is so that we can trigger a message
511
+ * back, which will then clear the kernel restarting state.
512
+ */
513
+ protected _sendMessage(msg: KernelMessage.IMessage, queue = true) {
514
+ if (this.status === 'dead') {
515
+ throw new Error('Kernel is dead');
516
+ }
517
+
518
+ // If we have a kernel_info_request and we are starting or restarting, send the
519
+ // kernel_info_request immediately if we can, and if not throw an error so
520
+ // we can retry later. On restarting we do this because we must get at least one message
521
+ // from the kernel to reset the kernel session (thus clearing the restart
522
+ // status sentinel).
523
+ if (
524
+ (this._kernelSession === STARTING_KERNEL_SESSION ||
525
+ this._kernelSession === RESTARTING_KERNEL_SESSION) &&
526
+ isInfoRequestMsg(msg)
527
+ ) {
528
+ if (this.connectionStatus === 'connected') {
529
+ this._ws!.send(serialize(msg, this._ws!.protocol));
530
+ return;
531
+ } else {
532
+ throw new Error('Could not send message: status is not connected');
533
+ }
534
+ }
535
+
536
+ // If there are pending messages, add to the queue so we keep messages in order
537
+ if (queue && this._pendingMessages.length > 0) {
538
+ this._pendingMessages.push(msg);
539
+ return;
540
+ }
541
+
542
+ // Send if the ws allows it, otherwise queue the message.
543
+ if (
544
+ this.connectionStatus === 'connected' &&
545
+ this._kernelSession !== RESTARTING_KERNEL_SESSION
546
+ ) {
547
+ this._ws!.send(serialize(msg, this._ws!.protocol));
548
+ } else if (queue) {
549
+ this._pendingMessages.push(msg);
550
+ } else {
551
+ throw new Error('Could not send message');
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Interrupt a kernel.
557
+ *
558
+ * #### Notes
559
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
560
+ *
561
+ * The promise is fulfilled on a valid response and rejected otherwise.
562
+ *
563
+ * It is assumed that the API call does not mutate the kernel id or name.
564
+ *
565
+ * The promise will be rejected if the kernel status is `Dead` or if the
566
+ * request fails or the response is invalid.
567
+ */
568
+ async interrupt(): Promise<void> {
569
+ this.hasPendingInput = false;
570
+ if (this.status === 'dead') {
571
+ throw new Error('Kernel is dead');
572
+ }
573
+ return this.kernelRestAPI.interruptKernel(this.id, this.serverSettings);
574
+ }
575
+
576
+ /**
577
+ * Request a kernel restart.
578
+ *
579
+ * #### Notes
580
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels)
581
+ * and validates the response model.
582
+ *
583
+ * Any existing Future or Comm objects are cleared once the kernel has
584
+ * actually be restarted.
585
+ *
586
+ * The promise is fulfilled on a valid server response (after the kernel restarts)
587
+ * and rejected otherwise.
588
+ *
589
+ * It is assumed that the API call does not mutate the kernel id or name.
590
+ *
591
+ * The promise will be rejected if the request fails or the response is
592
+ * invalid.
593
+ */
594
+ async restart(): Promise<void> {
595
+ if (this.status === 'dead') {
596
+ throw new Error('Kernel is dead');
597
+ }
598
+ this._updateStatus('restarting');
599
+ this._clearKernelState();
600
+ this._kernelSession = RESTARTING_KERNEL_SESSION;
601
+ await this.kernelRestAPI.restartKernel(this.id, this.serverSettings);
602
+ // Reconnect to the kernel to address cases where kernel ports
603
+ // have changed during the restart.
604
+ await this.reconnect();
605
+ this.hasPendingInput = false;
606
+ }
607
+
608
+ /**
609
+ * Reconnect to a kernel.
610
+ *
611
+ * #### Notes
612
+ * This may try multiple times to reconnect to a kernel, and will sever any
613
+ * existing connection.
614
+ */
615
+ reconnect(): Promise<void> {
616
+ this._errorIfDisposed();
617
+ const result = new Deferred<void>();
618
+ let toDispose: Disposable | undefined = undefined;
619
+
620
+ // Set up a listener for the connection status changing, which accepts or
621
+ // rejects after the retries are done.
622
+ const fulfill = (status: ConnectionStatus) => {
623
+ if (status === 'connected') {
624
+ result.resolve();
625
+ if (toDispose) {
626
+ toDispose.dispose();
627
+ toDispose = undefined;
628
+ }
629
+ } else if (status === 'disconnected') {
630
+ result.reject(new Error('Kernel connection disconnected'));
631
+ if (toDispose) {
632
+ toDispose.dispose();
633
+ toDispose = undefined;
634
+ }
635
+ }
636
+ };
637
+ toDispose = this.connectionStatusChanged(fulfill);
638
+
639
+ // Reset the reconnect limit so we start the connection attempts fresh
640
+ this._reconnectAttempt = 0;
641
+
642
+ // Start the reconnection process, which will also clear any existing
643
+ // connection.
644
+ this._reconnect();
645
+
646
+ // Return the promise that should resolve on connection or reject if the
647
+ // retries don't work.
648
+ return result.promise;
649
+ }
650
+
651
+ /**
652
+ * Shutdown a kernel.
653
+ *
654
+ * #### Notes
655
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
656
+ *
657
+ * The promise is fulfilled on a valid response and rejected otherwise.
658
+ *
659
+ * On a valid response, disposes this kernel connection.
660
+ *
661
+ * If the kernel is already `dead`, disposes this kernel connection without
662
+ * a server request.
663
+ */
664
+ async shutdown(): Promise<void> {
665
+ if (this.status !== 'dead') {
666
+ await this.kernelRestAPI.shutdownKernel(this.id, this.serverSettings);
667
+ }
668
+ this.handleShutdown();
669
+ }
670
+
671
+ /**
672
+ * Handles a kernel shutdown.
673
+ *
674
+ * #### Notes
675
+ * This method should be called if we know from outside information that a
676
+ * kernel is dead (for example, we cannot find the kernel model on the
677
+ * server).
678
+ */
679
+ handleShutdown(): void {
680
+ this._updateStatus('dead');
681
+ this.dispose();
682
+ }
683
+
684
+ /**
685
+ * Send a `kernel_info_request` message.
686
+ *
687
+ * #### Notes
688
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info).
689
+ *
690
+ * Fulfills with the `kernel_info_response` content when the shell reply is
691
+ * received and validated.
692
+ */
693
+ async requestKernelInfo(): Promise<KernelMessage.IInfoReplyMsg | undefined> {
694
+ const msg = KernelMessage.createMessage({
695
+ msgType: 'kernel_info_request',
696
+ channel: 'shell',
697
+ username: this._username,
698
+ session: this._clientId,
699
+ content: {},
700
+ });
701
+ let reply: KernelMessage.IInfoReplyMsg | undefined;
702
+ try {
703
+ reply = (await Private.handleShellMessage(this, msg)) as
704
+ | KernelMessage.IInfoReplyMsg
705
+ | undefined;
706
+ } catch (e) {
707
+ // If we rejected because the future was disposed, ignore and return.
708
+ if (this.isDisposed) {
709
+ return;
710
+ } else {
711
+ throw e;
712
+ }
713
+ }
714
+ this._errorIfDisposed();
715
+
716
+ if (!reply) {
717
+ return;
718
+ }
719
+
720
+ // Kernels sometimes do not include a status field on kernel_info_reply
721
+ // messages, so set a default for now.
722
+ // See https://github.com/jupyterlab/jupyterlab/issues/6760
723
+ if (reply.content.status === undefined) {
724
+ (reply.content as any).status = 'ok';
725
+ }
726
+
727
+ if (reply.content.status !== 'ok') {
728
+ this._info.reject('Kernel info reply errored');
729
+ return reply;
730
+ }
731
+
732
+ this._info.resolve(reply.content);
733
+
734
+ this._kernelSession = reply.header.session;
735
+
736
+ return reply;
737
+ }
738
+
739
+ /**
740
+ * Send a `complete_request` message.
741
+ *
742
+ * #### Notes
743
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
744
+ *
745
+ * Fulfills with the `complete_reply` content when the shell reply is
746
+ * received and validated.
747
+ */
748
+ requestComplete(
749
+ content: KernelMessage.ICompleteRequestMsg['content'],
750
+ ): Promise<KernelMessage.ICompleteReplyMsg> {
751
+ const msg = KernelMessage.createMessage({
752
+ msgType: 'complete_request',
753
+ channel: 'shell',
754
+ username: this._username,
755
+ session: this._clientId,
756
+ content,
757
+ });
758
+ return Private.handleShellMessage(
759
+ this,
760
+ msg,
761
+ ) as Promise<KernelMessage.ICompleteReplyMsg>;
762
+ }
763
+
764
+ /**
765
+ * Send an `inspect_request` message.
766
+ *
767
+ * #### Notes
768
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#introspection).
769
+ *
770
+ * Fulfills with the `inspect_reply` content when the shell reply is
771
+ * received and validated.
772
+ */
773
+ requestInspect(
774
+ content: KernelMessage.IInspectRequestMsg['content'],
775
+ ): Promise<KernelMessage.IInspectReplyMsg> {
776
+ const msg = KernelMessage.createMessage({
777
+ msgType: 'inspect_request',
778
+ channel: 'shell',
779
+ username: this._username,
780
+ session: this._clientId,
781
+ content: content,
782
+ });
783
+ return Private.handleShellMessage(
784
+ this,
785
+ msg,
786
+ ) as Promise<KernelMessage.IInspectReplyMsg>;
787
+ }
788
+
789
+ /**
790
+ * Send a `history_request` message.
791
+ *
792
+ * #### Notes
793
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#history).
794
+ *
795
+ * Fulfills with the `history_reply` content when the shell reply is
796
+ * received and validated.
797
+ */
798
+ requestHistory(
799
+ content: KernelMessage.IHistoryRequestMsg['content'],
800
+ ): Promise<KernelMessage.IHistoryReplyMsg> {
801
+ const msg = KernelMessage.createMessage({
802
+ msgType: 'history_request',
803
+ channel: 'shell',
804
+ username: this._username,
805
+ session: this._clientId,
806
+ content,
807
+ });
808
+ return Private.handleShellMessage(
809
+ this,
810
+ msg,
811
+ ) as Promise<KernelMessage.IHistoryReplyMsg>;
812
+ }
813
+
814
+ /**
815
+ * Send an `execute_request` message.
816
+ *
817
+ * #### Notes
818
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute).
819
+ *
820
+ * Future `onReply` is called with the `execute_reply` content when the
821
+ * shell reply is received and validated. The future will resolve when
822
+ * this message is received and the `idle` iopub status is received.
823
+ * The future will also be disposed at this point unless `disposeOnDone`
824
+ * is specified and `false`, in which case it is up to the caller to dispose
825
+ * of the future.
826
+ *
827
+ * **See also:** [[IExecuteReply]]
828
+ */
829
+ requestExecute(
830
+ content: KernelMessage.IExecuteRequestMsg['content'],
831
+ disposeOnDone = true,
832
+ metadata?: JSONObject,
833
+ ): IShellFuture<KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg> {
834
+ const defaults: JSONObject = {
835
+ silent: false,
836
+ store_history: true,
837
+ user_expressions: {},
838
+ allow_stdin: true,
839
+ stop_on_error: true,
840
+ };
841
+ const msg = KernelMessage.createMessage({
842
+ msgType: 'execute_request',
843
+ channel: 'shell',
844
+ username: this._username,
845
+ session: this._clientId,
846
+ content: { ...defaults, ...content },
847
+ metadata,
848
+ });
849
+ return this.sendShellMessage(msg, true, disposeOnDone) as IShellFuture<
850
+ KernelMessage.IExecuteRequestMsg,
851
+ KernelMessage.IExecuteReplyMsg
852
+ >;
853
+ }
854
+
855
+ /**
856
+ * Send an experimental `debug_request` message.
857
+ *
858
+ * @hidden
859
+ *
860
+ * #### Notes
861
+ * Debug messages are experimental messages that are not in the official
862
+ * kernel message specification. As such, this function is *NOT* considered
863
+ * part of the public API, and may change without notice.
864
+ */
865
+ requestDebug(
866
+ content: KernelMessage.IDebugRequestMsg['content'],
867
+ disposeOnDone = true,
868
+ ): IControlFuture<KernelMessage.IDebugRequestMsg, KernelMessage.IDebugReplyMsg> {
869
+ const msg = KernelMessage.createMessage({
870
+ msgType: 'debug_request',
871
+ channel: 'control',
872
+ username: this._username,
873
+ session: this._clientId,
874
+ content,
875
+ });
876
+ return this.sendControlMessage(msg, true, disposeOnDone) as IControlFuture<
877
+ KernelMessage.IDebugRequestMsg,
878
+ KernelMessage.IDebugReplyMsg
879
+ >;
880
+ }
881
+
882
+ /**
883
+ * Send an `is_complete_request` message.
884
+ *
885
+ * #### Notes
886
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness).
887
+ *
888
+ * Fulfills with the `is_complete_response` content when the shell reply is
889
+ * received and validated.
890
+ */
891
+ requestIsComplete(
892
+ content: KernelMessage.IIsCompleteRequestMsg['content'],
893
+ ): Promise<KernelMessage.IIsCompleteReplyMsg> {
894
+ const msg = KernelMessage.createMessage({
895
+ msgType: 'is_complete_request',
896
+ channel: 'shell',
897
+ username: this._username,
898
+ session: this._clientId,
899
+ content,
900
+ });
901
+ return Private.handleShellMessage(
902
+ this,
903
+ msg,
904
+ ) as Promise<KernelMessage.IIsCompleteReplyMsg>;
905
+ }
906
+
907
+ /**
908
+ * Send a `comm_info_request` message.
909
+ *
910
+ * #### Notes
911
+ * Fulfills with the `comm_info_reply` content when the shell reply is
912
+ * received and validated.
913
+ */
914
+ requestCommInfo(
915
+ content: KernelMessage.ICommInfoRequestMsg['content'],
916
+ ): Promise<KernelMessage.ICommInfoReplyMsg> {
917
+ const msg = KernelMessage.createMessage({
918
+ msgType: 'comm_info_request',
919
+ channel: 'shell',
920
+ username: this._username,
921
+ session: this._clientId,
922
+ content,
923
+ });
924
+ return Private.handleShellMessage(
925
+ this,
926
+ msg,
927
+ ) as Promise<KernelMessage.ICommInfoReplyMsg>;
928
+ }
929
+
930
+ /**
931
+ * Send an `input_reply` message.
932
+ *
933
+ * #### Notes
934
+ * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-sockets).
935
+ */
936
+ sendInputReply(
937
+ content: KernelMessage.IInputReplyMsg['content'],
938
+ parent_header: KernelMessage.IInputReplyMsg['parent_header'],
939
+ ): void {
940
+ const msg = KernelMessage.createMessage({
941
+ msgType: 'input_reply',
942
+ channel: 'stdin',
943
+ username: this._username,
944
+ session: this._clientId,
945
+ content,
946
+ });
947
+ msg.parent_header = parent_header;
948
+
949
+ this._sendMessage(msg);
950
+ this.anyMessageEmitter.fire({ msg, direction: 'send' });
951
+
952
+ this.hasPendingInput = false;
953
+ }
954
+
955
+ /**
956
+ * Create a new comm.
957
+ *
958
+ * #### Notes
959
+ * If a client-side comm already exists with the given commId, an error is thrown.
960
+ * If the kernel does not handle comms, an error is thrown.
961
+ */
962
+ createComm(targetName: string, commId: string = v4()): IComm {
963
+ if (!this.handleComms) {
964
+ throw new Error('Comms are disabled on this kernel connection');
965
+ }
966
+ if (this._comms.has(commId)) {
967
+ throw new Error('Comm is already created');
968
+ }
969
+
970
+ const comm = new CommHandler(targetName, commId, this, () => {
971
+ this._unregisterComm(commId);
972
+ });
973
+ this._comms.set(commId, comm);
974
+ return comm;
975
+ }
976
+
977
+ /**
978
+ * Check if a comm exists.
979
+ */
980
+ hasComm(commId: string): boolean {
981
+ return this._comms.has(commId);
982
+ }
983
+
984
+ /**
985
+ * Register a comm target handler.
986
+ *
987
+ * @param targetName - The name of the comm target.
988
+ *
989
+ * @param callback - The callback invoked for a comm open message.
990
+ *
991
+ * @returns A disposable used to unregister the comm target.
992
+ *
993
+ * #### Notes
994
+ * Only one comm target can be registered to a target name at a time, an
995
+ * existing callback for the same target name will be overridden. A registered
996
+ * comm target handler will take precedence over a comm which specifies a
997
+ * `target_module`.
998
+ *
999
+ * If the callback returns a promise, kernel message processing will pause
1000
+ * until the returned promise is fulfilled.
1001
+ */
1002
+ registerCommTarget(
1003
+ targetName: string,
1004
+ callback: (
1005
+ comm: IComm,
1006
+ msg: KernelMessage.ICommOpenMsg,
1007
+ ) => void | PromiseLike<void>,
1008
+ ): void {
1009
+ if (!this.handleComms) {
1010
+ return;
1011
+ }
1012
+
1013
+ this._targetRegistry[targetName] = callback;
1014
+ }
1015
+
1016
+ /**
1017
+ * Remove a comm target handler.
1018
+ *
1019
+ * @param targetName - The name of the comm target to remove.
1020
+ *
1021
+ * @param callback - The callback to remove.
1022
+ *
1023
+ * #### Notes
1024
+ * The comm target is only removed if the callback argument matches.
1025
+ */
1026
+ removeCommTarget(
1027
+ targetName: string,
1028
+ callback: (
1029
+ comm: IComm,
1030
+ msg: KernelMessage.ICommOpenMsg,
1031
+ ) => void | PromiseLike<void>,
1032
+ ): void {
1033
+ if (!this.handleComms) {
1034
+ return;
1035
+ }
1036
+
1037
+ if (!this.isDisposed && this._targetRegistry[targetName] === callback) {
1038
+ delete this._targetRegistry[targetName];
1039
+ }
1040
+ }
1041
+
1042
+ /**
1043
+ * Register an IOPub message hook.
1044
+ *
1045
+ * @param msg_id - The parent_header message id the hook will intercept.
1046
+ *
1047
+ * @param hook - The callback invoked for the message.
1048
+ *
1049
+ * #### Notes
1050
+ * The IOPub hook system allows you to preempt the handlers for IOPub
1051
+ * messages that are responses to a given message id.
1052
+ *
1053
+ * The most recently registered hook is run first. A hook can return a
1054
+ * boolean or a promise to a boolean, in which case all kernel message
1055
+ * processing pauses until the promise is fulfilled. If a hook return value
1056
+ * resolves to false, any later hooks will not run and the function will
1057
+ * return a promise resolving to false. If a hook throws an error, the error
1058
+ * is logged to the console and the next hook is run. If a hook is
1059
+ * registered during the hook processing, it will not run until the next
1060
+ * message. If a hook is removed during the hook processing, it will be
1061
+ * deactivated immediately.
1062
+ *
1063
+ * See also [[IFuture.registerMessageHook]].
1064
+ */
1065
+ registerMessageHook(
1066
+ msgId: string,
1067
+ hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>,
1068
+ ): void {
1069
+ const future = this._futures?.get(msgId);
1070
+ if (future) {
1071
+ future.registerMessageHook(hook);
1072
+ }
1073
+ }
1074
+
1075
+ /**
1076
+ * Remove an IOPub message hook.
1077
+ *
1078
+ * @param msg_id - The parent_header message id the hook intercepted.
1079
+ *
1080
+ * @param hook - The callback invoked for the message.
1081
+ *
1082
+ */
1083
+ removeMessageHook(
1084
+ msgId: string,
1085
+ hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>,
1086
+ ): void {
1087
+ const future = this._futures?.get(msgId);
1088
+ if (future) {
1089
+ future.removeMessageHook(hook);
1090
+ }
1091
+ }
1092
+
1093
+ /**
1094
+ * Remove the input guard, if any.
1095
+ */
1096
+ removeInputGuard() {
1097
+ this.hasPendingInput = false;
1098
+ }
1099
+
1100
+ /**
1101
+ * Handle a message with a display id.
1102
+ *
1103
+ * @returns Whether the message was handled.
1104
+ */
1105
+ protected async _handleDisplayId(
1106
+ displayId: string,
1107
+ msg: KernelMessage.IMessage,
1108
+ ): Promise<boolean> {
1109
+ const msgId = (msg.parent_header as KernelMessage.IHeader).msg_id;
1110
+ let parentIds = this._displayIdToParentIds.get(displayId);
1111
+ if (parentIds) {
1112
+ // We've seen it before, update existing outputs with same display_id
1113
+ // by handling display_data as update_display_data.
1114
+ const updateMsg: KernelMessage.IMessage = {
1115
+ header: deepCopy(
1116
+ msg.header as unknown as JSONObject,
1117
+ ) as unknown as KernelMessage.IHeader,
1118
+ parent_header: deepCopy(
1119
+ msg.parent_header as unknown as JSONObject,
1120
+ ) as unknown as KernelMessage.IHeader,
1121
+ metadata: deepCopy(msg.metadata),
1122
+ content: deepCopy(msg.content as JSONObject),
1123
+ channel: msg.channel,
1124
+ buffers: msg.buffers ? msg.buffers.slice() : [],
1125
+ };
1126
+ (updateMsg.header as any).msg_type = 'update_display_data';
1127
+
1128
+ await Promise.all(
1129
+ parentIds.map(async (parentId) => {
1130
+ const future = this._futures && this._futures.get(parentId);
1131
+ if (future) {
1132
+ await future.handleMsg(updateMsg);
1133
+ }
1134
+ }),
1135
+ );
1136
+ }
1137
+
1138
+ // We're done here if it's update_display.
1139
+ if (msg.header.msg_type === 'update_display_data') {
1140
+ // It's an update, don't proceed to the normal display.
1141
+ return true;
1142
+ }
1143
+
1144
+ // Regular display_data with id, record it for future updating
1145
+ // in _displayIdToParentIds for future lookup.
1146
+ parentIds = this._displayIdToParentIds.get(displayId) ?? [];
1147
+ if (parentIds.indexOf(msgId) === -1) {
1148
+ parentIds.push(msgId);
1149
+ }
1150
+ this._displayIdToParentIds.set(displayId, parentIds);
1151
+
1152
+ // Add to our map of display ids for this message.
1153
+ const displayIds = this._msgIdToDisplayIds.get(msgId) ?? [];
1154
+ if (displayIds.indexOf(msgId) === -1) {
1155
+ displayIds.push(msgId);
1156
+ }
1157
+ this._msgIdToDisplayIds.set(msgId, displayIds);
1158
+
1159
+ // Let the message propagate to the intended recipient.
1160
+ return false;
1161
+ }
1162
+
1163
+ /**
1164
+ * Forcefully clear the socket state.
1165
+ *
1166
+ * #### Notes
1167
+ * This will clear all socket state without calling any handlers and will
1168
+ * not update the connection status. If you call this method, you are
1169
+ * responsible for updating the connection status as needed and recreating
1170
+ * the socket if you plan to reconnect.
1171
+ */
1172
+ protected _clearSocket = (): void => {
1173
+ if (this._ws !== null) {
1174
+ // Clear the websocket event handlers and the socket itself.
1175
+ this._ws.onopen = this._noOp;
1176
+ this._ws.onclose = this._noOp;
1177
+ this._ws.onerror = this._noOp;
1178
+ this._ws.onmessage = this._noOp;
1179
+ this._ws.close();
1180
+ this._ws = null;
1181
+ }
1182
+ };
1183
+
1184
+ /**
1185
+ * Handle status iopub messages from the kernel.
1186
+ */
1187
+ protected _updateStatus(status: KernelMessage.Status): void {
1188
+ if (this._status === status || this._status === 'dead') {
1189
+ return;
1190
+ }
1191
+
1192
+ this._status = status;
1193
+ Private.logKernelStatus(this);
1194
+ this.statusChangedEmitter.fire(status);
1195
+ if (status === 'dead') {
1196
+ this.dispose();
1197
+ }
1198
+ }
1199
+
1200
+ /**
1201
+ * Send pending messages to the kernel.
1202
+ */
1203
+ protected _sendPending(): void {
1204
+ // We check to make sure we are still connected each time. For
1205
+ // example, if a websocket buffer overflows, it may close, so we should
1206
+ // stop sending messages.
1207
+ while (
1208
+ this.connectionStatus === 'connected' &&
1209
+ this._kernelSession !== RESTARTING_KERNEL_SESSION &&
1210
+ this._pendingMessages.length > 0
1211
+ ) {
1212
+ this._sendMessage(this._pendingMessages[0], false);
1213
+
1214
+ // We shift the message off the queue after the message is sent so that
1215
+ // if there is an exception, the message is still pending.
1216
+ this._pendingMessages.shift();
1217
+ }
1218
+ }
1219
+
1220
+ /**
1221
+ * Clear the internal state.
1222
+ */
1223
+ protected _clearKernelState(): void {
1224
+ this._kernelSession = '';
1225
+ this._pendingMessages = [];
1226
+ this._futures.forEach((future) => {
1227
+ future.dispose();
1228
+ });
1229
+ this._comms.forEach((comm) => {
1230
+ comm.dispose();
1231
+ });
1232
+ this._msgChain = Promise.resolve();
1233
+ this._futures = new Map<
1234
+ string,
1235
+ KernelFutureHandler<
1236
+ KernelMessage.IShellControlMessage,
1237
+ KernelMessage.IShellControlMessage
1238
+ >
1239
+ >();
1240
+ this._comms = new Map<string, IComm>();
1241
+ this._displayIdToParentIds.clear();
1242
+ this._msgIdToDisplayIds.clear();
1243
+ }
1244
+
1245
+ /**
1246
+ * Check to make sure it is okay to proceed to handle a message.
1247
+ *
1248
+ * #### Notes
1249
+ * Because we handle messages asynchronously, before a message is handled the
1250
+ * kernel might be disposed or restarted (and have a different session id).
1251
+ * This function throws an error in each of these cases. This is meant to be
1252
+ * called at the start of an asynchronous message handler to cancel message
1253
+ * processing if the message no longer is valid.
1254
+ */
1255
+ protected _assertCurrentMessage(msg: KernelMessage.IMessage) {
1256
+ this._errorIfDisposed();
1257
+
1258
+ if (msg.header.session !== this._kernelSession) {
1259
+ throw new Error(`Canceling handling of old message: ${msg.header.msg_type}`);
1260
+ }
1261
+ }
1262
+
1263
+ /**
1264
+ * Handle a `comm_open` kernel message.
1265
+ */
1266
+ protected async _handleCommOpen(msg: KernelMessage.ICommOpenMsg): Promise<void> {
1267
+ this._assertCurrentMessage(msg);
1268
+ const content = msg.content;
1269
+ const comm = new CommHandler(content.target_name, content.comm_id, this, () => {
1270
+ this._unregisterComm(content.comm_id);
1271
+ });
1272
+ this._comms.set(content.comm_id, comm);
1273
+
1274
+ try {
1275
+ const target = await Private.loadObject(
1276
+ content.target_name,
1277
+ content.target_module,
1278
+ this._targetRegistry,
1279
+ );
1280
+ await target(comm, msg);
1281
+ } catch (e) {
1282
+ // Close the comm asynchronously. We cannot block message processing on
1283
+ // kernel messages to wait for another kernel message.
1284
+ comm.close();
1285
+ console.error('Exception opening new comm');
1286
+ throw e;
1287
+ }
1288
+ }
1289
+
1290
+ /**
1291
+ * Handle 'comm_close' kernel message.
1292
+ */
1293
+ protected async _handleCommClose(msg: KernelMessage.ICommCloseMsg): Promise<void> {
1294
+ this._assertCurrentMessage(msg);
1295
+ const content = msg.content;
1296
+ const comm = this._comms.get(content.comm_id);
1297
+ if (!comm) {
1298
+ console.error('Comm not found for comm id ' + content.comm_id);
1299
+ return;
1300
+ }
1301
+ this._unregisterComm(comm.commId);
1302
+ const onClose = comm.onClose;
1303
+ if (onClose) {
1304
+ // tslint:disable-next-line:await-promise
1305
+ await onClose(msg);
1306
+ }
1307
+ (comm as CommHandler).dispose();
1308
+ }
1309
+
1310
+ /**
1311
+ * Handle a 'comm_msg' kernel message.
1312
+ */
1313
+ protected async _handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise<void> {
1314
+ this._assertCurrentMessage(msg);
1315
+ const content = msg.content;
1316
+ const comm = this._comms.get(content.comm_id);
1317
+ if (!comm) {
1318
+ return;
1319
+ }
1320
+ const onMsg = comm.onMsg;
1321
+ if (onMsg) {
1322
+ // tslint:disable-next-line:await-promise
1323
+ await onMsg(msg);
1324
+ }
1325
+ }
1326
+
1327
+ /**
1328
+ * Unregister a comm instance.
1329
+ */
1330
+ protected _unregisterComm(commId: string) {
1331
+ this._comms.delete(commId);
1332
+ }
1333
+
1334
+ /**
1335
+ * Create the kernel websocket connection and add socket status handlers.
1336
+ */
1337
+ protected _createSocket = (useProtocols = false) => {
1338
+ this._errorIfDisposed();
1339
+
1340
+ // Make sure the socket is clear
1341
+ this._clearSocket();
1342
+
1343
+ // Update the connection status to reflect opening a new connection.
1344
+ this._updateConnectionStatus('connecting');
1345
+
1346
+ const settings = this.serverSettings;
1347
+
1348
+ const partialUrl = URL.join(
1349
+ settings.wsUrl,
1350
+ restapi.KERNEL_SERVICE_URL,
1351
+ encodeURIComponent(this._id),
1352
+ );
1353
+
1354
+ // Strip any authentication from the display string.
1355
+ // eslint-disable-next-line no-useless-escape
1356
+ const display = partialUrl.replace(/^((?:\w+:)?\/\/)(?:[^@\/]+@)/, '$1');
1357
+ // eslint-disable-next-line no-console
1358
+ console.debug(`Starting WebSocket: ${display}`);
1359
+
1360
+ let url = URL.join(
1361
+ partialUrl,
1362
+ 'channels?session_id=' + encodeURIComponent(this._clientId),
1363
+ );
1364
+
1365
+ // If token authentication is in use.
1366
+ const token = settings.token;
1367
+ if (settings.appendToken && token !== '') {
1368
+ url = url + `&token=${encodeURIComponent(token)}`;
1369
+ }
1370
+
1371
+ // Try opening the websocket with our list of subprotocols.
1372
+ // If the server doesn't handle subprotocols,
1373
+ // the accepted protocol will be ''.
1374
+ // But we cannot send '' as a subprotocol, so if connection fails,
1375
+ // reconnect without subprotocols.
1376
+ const supportedProtocols = useProtocols ? this._supportedProtocols : [];
1377
+ this._ws = new settings.WebSocket(url, supportedProtocols);
1378
+
1379
+ // Ensure incoming binary messages are not Blobs
1380
+ this._ws.binaryType = 'arraybuffer';
1381
+
1382
+ let alreadyCalledOnclose = false;
1383
+
1384
+ const getKernelModel = async (evt: Event) => {
1385
+ if (this._isDisposed) {
1386
+ return;
1387
+ }
1388
+ this._reason = '';
1389
+ this._model = undefined;
1390
+ try {
1391
+ const model = await this.kernelRestAPI.getKernelModel(this._id, settings);
1392
+ this._model = model;
1393
+ if (model?.execution_state === 'dead') {
1394
+ this._updateStatus('dead');
1395
+ } else {
1396
+ this._onWSClose(evt);
1397
+ }
1398
+ } catch (err: any) {
1399
+ // Try again, if there is a network failure
1400
+ // Handle network errors, as well as cases where we are on a
1401
+ // JupyterHub and the server is not running. JupyterHub returns a
1402
+ // 503 (<2.0) or 424 (>2.0) in that case.
1403
+ if (
1404
+ err instanceof NetworkError ||
1405
+ err.response?.status === 503 ||
1406
+ err.response?.status === 424
1407
+ ) {
1408
+ const timeout = Private.getRandomIntInclusive(10, 30) * 1e3;
1409
+ setTimeout(getKernelModel, timeout, evt);
1410
+ } else {
1411
+ this._reason = 'Kernel died unexpectedly';
1412
+ this._updateStatus('dead');
1413
+ }
1414
+ }
1415
+ return;
1416
+ };
1417
+
1418
+ const earlyClose = async (evt: Event) => {
1419
+ // If the websocket was closed early, that could mean
1420
+ // that the kernel is actually dead. Try getting
1421
+ // information about the kernel from the API call,
1422
+ // if that fails, then assume the kernel is dead,
1423
+ // otherwise just follow the typical websocket closed
1424
+ // protocol.
1425
+ if (alreadyCalledOnclose) {
1426
+ return;
1427
+ }
1428
+ alreadyCalledOnclose = true;
1429
+ await getKernelModel(evt);
1430
+
1431
+ return;
1432
+ };
1433
+
1434
+ this._ws.onmessage = this._onWSMessage;
1435
+ this._ws.onopen = this._onWSOpen;
1436
+ this._ws.onclose = earlyClose;
1437
+ this._ws.onerror = earlyClose;
1438
+ };
1439
+
1440
+ /**
1441
+ * Handle connection status changes.
1442
+ */
1443
+ protected _updateConnectionStatus(connectionStatus: ConnectionStatus): void {
1444
+ if (this._connectionStatus === connectionStatus) {
1445
+ return;
1446
+ }
1447
+
1448
+ this._connectionStatus = connectionStatus;
1449
+
1450
+ // If we are not 'connecting', reset any reconnection attempts.
1451
+ if (connectionStatus !== 'connecting') {
1452
+ this._reconnectAttempt = 0;
1453
+ clearTimeout(this._reconnectTimeout);
1454
+ }
1455
+
1456
+ if (this.status !== 'dead') {
1457
+ if (connectionStatus === 'connected') {
1458
+ const restarting = this._kernelSession === RESTARTING_KERNEL_SESSION;
1459
+
1460
+ // Send a kernel info request to make sure we send at least one
1461
+ // message to get kernel status back. Always request kernel info
1462
+ // first, to get kernel status back and ensure iopub is fully
1463
+ // established. If we are restarting, this message will skip the queue
1464
+ // and be sent immediately.
1465
+ const p = this.requestKernelInfo();
1466
+
1467
+ // Send any pending messages after the kernelInfo resolves, or after a
1468
+ // timeout as a failsafe.
1469
+
1470
+ let sendPendingCalled = false;
1471
+ let timeoutHandle: any = null;
1472
+ const sendPendingOnce = () => {
1473
+ if (sendPendingCalled) {
1474
+ return;
1475
+ }
1476
+ sendPendingCalled = true;
1477
+ if (restarting && this._kernelSession === RESTARTING_KERNEL_SESSION) {
1478
+ // We were restarting and a message didn't arrive to set the
1479
+ // session, but we just assume the restart succeeded and send any
1480
+ // pending messages.
1481
+
1482
+ // FIXME: it would be better to retry the kernel_info_request here
1483
+ this._kernelSession = '';
1484
+ }
1485
+ if (timeoutHandle) {
1486
+ clearTimeout(timeoutHandle);
1487
+ }
1488
+ if (this._pendingMessages.length > 0) {
1489
+ this._sendPending();
1490
+ }
1491
+ };
1492
+ void p.then(sendPendingOnce);
1493
+ // FIXME: if sent while zmq subscriptions are not established,
1494
+ // kernelInfo may not resolve, so use a timeout to ensure we don't hang forever.
1495
+ // It may be preferable to retry kernelInfo rather than give up after one timeout.
1496
+ timeoutHandle = setTimeout(sendPendingOnce, KERNEL_INFO_TIMEOUT);
1497
+ } else {
1498
+ // If the connection is down, then we do not know what is happening
1499
+ // with the kernel, so set the status to unknown.
1500
+ this._updateStatus('unknown');
1501
+ }
1502
+ }
1503
+
1504
+ // Notify others that the connection status changed.
1505
+ this.connectionStatusChangedEmitter.fire(connectionStatus);
1506
+ }
1507
+
1508
+ protected async _handleMessage(msg: KernelMessage.IMessage): Promise<void> {
1509
+ let handled = false;
1510
+
1511
+ // Check to see if we have a display_id we need to reroute.
1512
+ if (
1513
+ msg.parent_header &&
1514
+ msg.channel === 'iopub' &&
1515
+ (isDisplayDataMsg(msg) || isUpdateDisplayDataMsg(msg) || isExecuteResultMsg(msg))
1516
+ ) {
1517
+ // display_data messages may re-route based on their display_id.
1518
+ const _transient = (msg.content.transient ?? {}) as JSONObject;
1519
+ const displayId = _transient['display_id'] as string;
1520
+ if (displayId) {
1521
+ handled = await this._handleDisplayId(displayId, msg);
1522
+ // The await above may make this message out of date, so check again.
1523
+ this._assertCurrentMessage(msg);
1524
+ }
1525
+ }
1526
+
1527
+ if (!handled && msg.parent_header) {
1528
+ const parentHeader = msg.parent_header as KernelMessage.IHeader;
1529
+ const future = this._futures?.get(parentHeader.msg_id);
1530
+ if (future) {
1531
+ await future.handleMsg(msg);
1532
+ this._assertCurrentMessage(msg);
1533
+ } else {
1534
+ // If the message was sent by us and was not iopub, it is orphaned.
1535
+ const owned = parentHeader.session === this.clientId;
1536
+ if (msg.channel !== 'iopub' && owned) {
1537
+ this.unhandledMessageEmitter.fire(msg);
1538
+ }
1539
+ }
1540
+ }
1541
+ if (msg.channel === 'iopub') {
1542
+ switch (msg.header.msg_type) {
1543
+ case 'status': {
1544
+ // Updating the status is synchronous, and we call no async user code
1545
+ const executionState = (msg as KernelMessage.IStatusMsg).content
1546
+ .execution_state;
1547
+ if (executionState === 'restarting') {
1548
+ // The kernel has been auto-restarted by the server. After
1549
+ // processing for this message is completely done, we want to
1550
+ // handle this restart, so we don't await, but instead schedule
1551
+ // the work as a microtask (i.e., in a promise resolution). We
1552
+ // schedule this here so that it comes before any microtasks that
1553
+ // might be scheduled in the status signal emission below.
1554
+ void Promise.resolve().then(async () => {
1555
+ this._updateStatus('autorestarting');
1556
+ this._clearKernelState();
1557
+
1558
+ // We must reconnect since the kernel connection information may have
1559
+ // changed, and the server only refreshes its zmq connection when a new
1560
+ // websocket is opened.
1561
+ await this.reconnect();
1562
+ return;
1563
+ });
1564
+ }
1565
+ this._updateStatus(executionState);
1566
+ break;
1567
+ }
1568
+ case 'comm_open':
1569
+ if (this.handleComms) {
1570
+ await this._handleCommOpen(msg as KernelMessage.ICommOpenMsg);
1571
+ }
1572
+ break;
1573
+ case 'comm_msg':
1574
+ if (this.handleComms) {
1575
+ await this._handleCommMsg(msg as KernelMessage.ICommMsgMsg);
1576
+ }
1577
+ break;
1578
+ case 'comm_close':
1579
+ if (this.handleComms) {
1580
+ await this._handleCommClose(msg as KernelMessage.ICommCloseMsg);
1581
+ }
1582
+ break;
1583
+ default:
1584
+ break;
1585
+ }
1586
+ // If the message was a status dead message, we might have disposed ourselves.
1587
+ if (!this.isDisposed) {
1588
+ this._assertCurrentMessage(msg);
1589
+ // the message wouldn't be emitted if we were disposed anyway.
1590
+ this.iopubMessageEmitter.fire(msg as KernelMessage.IIOPubMessage);
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ /**
1596
+ * Attempt a connection if we have not exhausted connection attempts.
1597
+ */
1598
+ protected _reconnect() {
1599
+ this._errorIfDisposed();
1600
+
1601
+ // Clear any existing reconnection attempt
1602
+ clearTimeout(this._reconnectTimeout);
1603
+
1604
+ // Update the connection status and schedule a possible reconnection.
1605
+ if (this._reconnectAttempt < this._reconnectLimit) {
1606
+ this._updateConnectionStatus('connecting');
1607
+
1608
+ // The first reconnect attempt should happen immediately, and subsequent
1609
+ // attempts should pick a random number in a growing range so that we
1610
+ // don't overload the server with synchronized reconnection attempts
1611
+ // across multiple kernels.
1612
+ const timeout = Private.getRandomIntInclusive(
1613
+ 0,
1614
+ 1e3 * (Math.pow(2, this._reconnectAttempt) - 1),
1615
+ );
1616
+ console.warn(
1617
+ `Connection lost, reconnecting in ${Math.floor(timeout / 1000)} seconds.`,
1618
+ );
1619
+ // Try reconnection with subprotocols if the server had supported them.
1620
+ // Otherwise, try reconnection without subprotocols.
1621
+ const useProtocols = this._selectedProtocol !== '' ? true : false;
1622
+ this._reconnectTimeout = setTimeout(this._createSocket, timeout, useProtocols);
1623
+ this._reconnectAttempt += 1;
1624
+ } else {
1625
+ this._updateConnectionStatus('disconnected');
1626
+ }
1627
+
1628
+ // Clear the websocket event handlers and the socket itself.
1629
+ this._clearSocket();
1630
+ }
1631
+
1632
+ /**
1633
+ * Utility function to throw an error if this instance is disposed.
1634
+ */
1635
+ protected _errorIfDisposed() {
1636
+ if (this.isDisposed) {
1637
+ throw new Error('Kernel connection is disposed');
1638
+ }
1639
+ }
1640
+
1641
+ // Make websocket callbacks arrow functions so they bind `this`.
1642
+
1643
+ /**
1644
+ * Handle a websocket open event.
1645
+ */
1646
+ protected _onWSOpen = () => {
1647
+ if (
1648
+ this._ws!.protocol !== '' &&
1649
+ !this._supportedProtocols.includes(this._ws!.protocol)
1650
+ ) {
1651
+ console.warn('Server selected unknown kernel wire protocol:', this._ws!.protocol);
1652
+ this._updateStatus('dead');
1653
+ throw new Error(`Unknown kernel wire protocol: ${this._ws!.protocol}`);
1654
+ }
1655
+ // Remember the kernel wire protocol selected by the server.
1656
+ this._selectedProtocol = this._ws!.protocol;
1657
+ this._ws!.onclose = this._onWSClose;
1658
+ this._ws!.onerror = this._onWSClose;
1659
+ this._updateConnectionStatus('connected');
1660
+ };
1661
+
1662
+ /**
1663
+ * Handle a websocket message, validating and routing appropriately.
1664
+ */
1665
+ protected _onWSMessage = (evt: MessageEvent) => {
1666
+ // Notify immediately if there is an error with the message.
1667
+ let msg: KernelMessage.IMessage;
1668
+ try {
1669
+ msg = deserialize(evt.data, this._ws!.protocol);
1670
+ validate.validateMessage(msg);
1671
+ } catch (error: any) {
1672
+ error.message = `Kernel message validation error: ${error.message}`;
1673
+ // We throw the error so that it bubbles up to the top, and displays the right stack.
1674
+ throw error;
1675
+ }
1676
+
1677
+ // Update the current kernel session id
1678
+ this._kernelSession = msg.header.session;
1679
+
1680
+ // Handle the message asynchronously, in the order received.
1681
+ this._msgChain = this._msgChain
1682
+ .then(() => {
1683
+ // Return so that any promises from handling a message are fulfilled
1684
+ // before proceeding to the next message.
1685
+ return this._handleMessage(msg);
1686
+ })
1687
+ .catch((error) => {
1688
+ // Log any errors in handling the message, thus resetting the _msgChain
1689
+ // promise so we can process more messages.
1690
+ // Ignore the "Canceled" errors that are thrown during kernel dispose.
1691
+ if (error.message.startsWith('Canceled future for ')) {
1692
+ console.error(error);
1693
+ }
1694
+ });
1695
+
1696
+ // Emit the message receive signal
1697
+ this.anyMessageEmitter.fire({ msg, direction: 'recv' });
1698
+ };
1699
+
1700
+ /**
1701
+ * Handle a websocket close event.
1702
+ */
1703
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1704
+ protected _onWSClose = (_evt: Event) => {
1705
+ if (!this.isDisposed) {
1706
+ this._reconnect();
1707
+ }
1708
+ };
1709
+
1710
+ get hasPendingInput(): boolean {
1711
+ return this._hasPendingInput;
1712
+ }
1713
+ set hasPendingInput(value: boolean) {
1714
+ this._hasPendingInput = value;
1715
+ this.pendingInputEmitter.fire(value);
1716
+ }
1717
+
1718
+ protected _id = '';
1719
+ protected _name = '';
1720
+ protected _model: IKernelModel | undefined;
1721
+ @prop()
1722
+ protected _status: KernelMessage.Status = 'unknown';
1723
+ protected _connectionStatus: ConnectionStatus = 'connecting';
1724
+ protected _kernelSession = '';
1725
+ protected _clientId: string;
1726
+ protected _isDisposed = false;
1727
+ /**
1728
+ * Websocket to communicate with kernel.
1729
+ */
1730
+ protected _ws: WebSocket | null = null;
1731
+ protected _username = '';
1732
+ protected _reconnectLimit = 7;
1733
+ protected _reconnectAttempt = 0;
1734
+ protected _reconnectTimeout: any = null;
1735
+ protected _supportedProtocols: string[] = Object.values(
1736
+ KernelMessage.supportedKernelWebSocketProtocols,
1737
+ );
1738
+ protected _selectedProtocol = '';
1739
+
1740
+ protected _futures = new Map<
1741
+ string,
1742
+ KernelFutureHandler<
1743
+ KernelMessage.IShellControlMessage,
1744
+ KernelMessage.IShellControlMessage
1745
+ >
1746
+ >();
1747
+ protected _comms = new Map<string, IComm>();
1748
+ protected _targetRegistry: Record<
1749
+ string,
1750
+ (comm: IComm, msg: KernelMessage.ICommOpenMsg) => void
1751
+ > = Object.create(null);
1752
+ protected _info = new Deferred<KernelMessage.IInfoReply>();
1753
+ protected _pendingMessages: KernelMessage.IMessage[] = [];
1754
+ protected _specPromise: Promise<ISpecModel | undefined>;
1755
+ protected statusChangedEmitter = new Emitter<KernelMessage.Status>();
1756
+ protected connectionStatusChangedEmitter = new Emitter<ConnectionStatus>();
1757
+ protected onDisposedEmitter = new Emitter<void>();
1758
+ protected iopubMessageEmitter = new Emitter<KernelMessage.IIOPubMessage>();
1759
+ protected anyMessageEmitter = new Emitter<IAnyMessageArgs>();
1760
+ protected pendingInputEmitter = new Emitter<boolean>();
1761
+ protected unhandledMessageEmitter = new Emitter<KernelMessage.IMessage>();
1762
+ protected _displayIdToParentIds = new Map<string, string[]>();
1763
+ protected _msgIdToDisplayIds = new Map<string, string[]>();
1764
+ protected _msgChain: Promise<void> = Promise.resolve();
1765
+ protected _hasPendingInput = false;
1766
+ protected _reason = '';
1767
+ protected _noOp = () => {
1768
+ /* no-op */
1769
+ };
1770
+ }