UNPKG

@jupyterlab/services

Version:

Client APIs for the Jupyter services REST APIs

297 lines 9.63 kB
"use strict"; // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. Object.defineProperty(exports, "__esModule", { value: true }); exports.TerminalManager = void 0; const polling_1 = require("@lumino/polling"); const signaling_1 = require("@lumino/signaling"); const __1 = require(".."); const basemanager_1 = require("../basemanager"); const restapi_1 = require("./restapi"); const default_1 = require("./default"); /** * A terminal session manager. */ class TerminalManager extends basemanager_1.BaseManager { /** * Construct a new terminal manager. */ constructor(options = {}) { var _a; super(options); this._isReady = false; // As an optimization, we unwrap the models to just store the names. this._names = []; this._terminalConnections = new Set(); this._runningChanged = new signaling_1.Signal(this); this._connectionFailure = new signaling_1.Signal(this); // Check if terminals are available if (!this.isAvailable()) { this._ready = Promise.reject('Terminals unavailable'); this._ready.catch(_ => undefined); return; } // Start polling with exponential backoff. this._pollModels = new polling_1.Poll({ auto: false, factory: () => this.requestRunning(), frequency: { interval: 10 * 1000, backoff: true, max: 300 * 1000 }, name: `@jupyterlab/services:TerminalManager#models`, standby: (_a = options.standby) !== null && _a !== void 0 ? _a : 'when-hidden' }); // Initialize internal data. this._ready = (async () => { await this._pollModels.start(); await this._pollModels.tick; this._isReady = true; })(); } /** * Test whether the manager is ready. */ get isReady() { return this._isReady; } /** * A promise that fulfills when the manager is ready. */ get ready() { return this._ready; } /** * A signal emitted when the running terminals change. */ get runningChanged() { return this._runningChanged; } /** * A signal emitted when there is a connection failure. */ get connectionFailure() { return this._connectionFailure; } /** * Dispose of the resources used by the manager. */ dispose() { if (this.isDisposed) { return; } this._names.length = 0; this._terminalConnections.forEach(x => x.dispose()); this._pollModels.dispose(); super.dispose(); } /** * Whether the terminal service is available. */ isAvailable() { return (0, restapi_1.isAvailable)(); } /* * Connect to a running terminal. * * @param options - The options used to connect to the terminal. * * @returns The new terminal connection instance. * * #### Notes * The manager `serverSettings` will be used. */ connectTo(options) { const terminalConnection = new default_1.TerminalConnection({ ...options, serverSettings: this.serverSettings }); this._onStarted(terminalConnection); if (!this._names.includes(options.model.name)) { // We trust the user to connect to an existing session, but we verify // asynchronously. void this.refreshRunning().catch(() => { /* no-op */ }); } return terminalConnection; } /** * Create an iterator over the most recent running terminals. * * @returns A new iterator over the running terminals. */ running() { return this._models[Symbol.iterator](); } /** * Force a refresh of the running terminals. * * @returns A promise that with the list of running terminals. * * #### Notes * This is intended to be called only in response to a user action, * since the manager maintains its internal state. */ async refreshRunning() { await this._pollModels.refresh(); await this._pollModels.tick; } /** * Create a new terminal session. * * @param options - The options used to create the terminal. * * @returns A promise that resolves with the terminal connection instance. * * #### Notes * The manager `serverSettings` will be used unless overridden in the * options. */ async startNew(options) { const model = await (0, restapi_1.startNew)(this.serverSettings, options === null || options === void 0 ? void 0 : options.name, options === null || options === void 0 ? void 0 : options.cwd); await this.refreshRunning(); return this.connectTo({ model }); } /** * Shut down a terminal session by name. */ async shutdown(name) { await (0, restapi_1.shutdownTerminal)(name, this.serverSettings); await this.refreshRunning(); } /** * Shut down all terminal sessions. * * @returns A promise that resolves when all of the sessions are shut down. */ async shutdownAll() { // Update the list of models to make sure our list is current. await this.refreshRunning(); // Shut down all models. await Promise.all(this._names.map(name => (0, restapi_1.shutdownTerminal)(name, this.serverSettings))); // Update the list of models to clear out our state. await this.refreshRunning(); } /** * Execute a request to the server to poll running terminals and update state. */ async requestRunning() { var _a, _b; let models; try { models = await (0, restapi_1.listRunning)(this.serverSettings); } catch (err) { // Handle network errors, as well as cases where we are on a // JupyterHub and the server is not running. JupyterHub returns a // 503 (<2.0) or 424 (>2.0) in that case. if (err instanceof __1.ServerConnection.NetworkError || ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 503 || ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 424) { this._connectionFailure.emit(err); } throw err; } if (this.isDisposed) { return; } const names = models.map(({ name }) => name).sort(); if (names === this._names) { // Identical models list, so just return return; } this._names = names; this._terminalConnections.forEach(tc => { if (!names.includes(tc.name)) { tc.dispose(); } }); this._runningChanged.emit(this._models); } /** * Handle a session starting. */ _onStarted(terminalConnection) { this._terminalConnections.add(terminalConnection); terminalConnection.disposed.connect(this._onDisposed, this); } /** * Handle a session terminating. */ _onDisposed(terminalConnection) { this._terminalConnections.delete(terminalConnection); // Update the running models to make sure we reflect the server state void this.refreshRunning().catch(() => { /* no-op */ }); } get _models() { return this._names.map(name => { return { name }; }); } } exports.TerminalManager = TerminalManager; /** * The namespace for TerminalManager statics. */ (function (TerminalManager) { /** * A no-op terminal manager to be used when starting terminals is not supported. */ class NoopManager extends TerminalManager { constructor() { super(...arguments); this._readyPromise = new Promise(() => { /* no-op */ }); } /** * Whether the manager is active. */ get isActive() { return false; } /** * Used for testing. */ get parentReady() { return super.ready; } /** * A promise that fulfills when the manager is ready (never). */ get ready() { return this.parentReady.then(() => this._readyPromise); } /** * Create a new terminal session - throw an error since it is not supported. * */ async startNew(options) { return Promise.reject(new Error('Not implemented in no-op Terminal Manager')); } /* * Connect to a running terminal - throw an error since it is not supported. */ connectTo(options) { throw Error('Not implemented in no-op Terminal Manager'); } /** * Shut down a session by id - throw an error since it is not supported. */ async shutdown(id) { return Promise.reject(new Error('Not implemented in no-op Terminal Manager')); } /** * Execute a request to the server to poll running sessions and update state. */ async requestRunning() { return Promise.resolve(); } } TerminalManager.NoopManager = NoopManager; })(TerminalManager || (exports.TerminalManager = TerminalManager = {})); //# sourceMappingURL=manager.js.map