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