@difizen/libro-kernel 0.0.0-snapshot-20241017072317

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