UNPKG

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