UNPKG

@jupyterlite/terminal

Version:
138 lines (137 loc) 5.05 kB
import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { ServerConnection } from '@jupyterlab/services'; import { ShellManager } from '@jupyterlite/cockle'; import { Server as WebSocketServer } from 'mock-socket'; import { Shell } from './shell'; export class LiteTerminalAPIClient { constructor(options = {}) { var _a; this._externalCommands = []; this._shells = new Map(); this.serverSettings = (_a = options.serverSettings) !== null && _a !== void 0 ? _a : ServerConnection.makeSettings(); this._shellManager = new ShellManager(); } /** * Set identifier for communicating with service worker. */ set browsingContextId(browsingContextId) { console.log('LiteTerminalAPIClient browsingContextId', browsingContextId); this._browsingContextId = browsingContextId; } /** * Function that handles stdin requests received from service worker. */ async handleStdin(request) { return await this._shellManager.handleStdin(request); } get isAvailable() { const available = String(PageConfig.getOption('terminalsAvailable')); return available.toLowerCase() === 'true'; } async startNew(options) { var _a; // Create shell. const name = (_a = options === null || options === void 0 ? void 0 : options.name) !== null && _a !== void 0 ? _a : this._nextAvailableName(); const { baseUrl, wsUrl } = this.serverSettings; const shell = new Shell({ mountpoint: '/drive', baseUrl, wasmBaseUrl: URLExt.join(baseUrl, 'extensions/@jupyterlite/terminal/static/wasm/'), browsingContextId: this._browsingContextId, aliases: this._aliases, environment: this._environment, externalCommands: this._externalCommands, shellId: name, shellManager: this._shellManager, outputCallback: text => { var _a; const msg = JSON.stringify(['stdout', text]); (_a = shell.socket) === null || _a === void 0 ? void 0 : _a.send(msg); } }); this._shells.set(name, shell); // Hook to connect socket to shell. const hook = async (shell, socket) => { shell.socket = socket; socket.on('message', async (message) => { // Message from xtermjs to pass to shell. const data = JSON.parse(message); const message_type = data[0]; const content = data.slice(1); await shell.ready; if (message_type === 'stdin') { await shell.input(content[0]); } else if (message_type === 'set_size') { const rows = content[0]; const columns = content[1]; await shell.setSize(rows, columns); } }); // Return handshake. const res = JSON.stringify(['setup']); console.log('Terminal returning handshake via socket'); socket.send(res); shell.start(); }; const url = URLExt.join(wsUrl, 'terminals', 'websocket', name); const wsServer = new WebSocketServer(url); wsServer.on('connection', (socket) => { hook(shell, socket); }); shell.disposed.connect(() => { this.shutdown(name); wsServer.close(); }); return { name }; } async listRunning() { return this._models; } registerAlias(key, value) { if (this._aliases === undefined) { this._aliases = {}; } this._aliases[key] = value; } registerEnvironmentVariable(key, value) { if (this._environment === undefined) { this._environment = {}; } this._environment[key] = value; } registerExternalCommand(options) { this._externalCommands.push(options); } async shutdown(name) { var _a, _b; const shell = this._shells.get(name); if (shell !== undefined) { (_a = shell.socket) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(['disconnect'])); (_b = shell.socket) === null || _b === void 0 ? void 0 : _b.close(); this._shells.delete(name); shell.dispose(); } } themeChange(isDarkMode) { for (const shell of this._shells.values()) { // Can pass isDarkMode when cockle is released with PR #232. //shell.themeChange(isDarkMode); shell.themeChange(); } } get _models() { return Array.from(this._shells.keys(), name => { return { name }; }); } _nextAvailableName() { for (let i = 1;; ++i) { const name = `${i}`; if (!this._shells.has(name)) { return name; } } } }