@jupyterlite/terminal 0.2.0-a0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ A terminal for JupyterLite.
11
11
 
12
12
  ## Requirements
13
13
 
14
- - JupyterLite >= 0.4.0
14
+ - JupyterLite >= 0.6.0
15
15
 
16
16
  ## Install
17
17
 
@@ -46,18 +46,6 @@ Then build a new JupyterLite site:
46
46
  jupyter lite build
47
47
  ```
48
48
 
49
- ## Deployment
50
-
51
- If you would like to deploy a JupyterLite site with the terminal extension, you will need to configure your server to add the `Cross-Origin-Embedder-Policy` and `Cross-Origin-Opener-Policy` headers.
52
-
53
- As an example, this repository deploys the JupyterLite terminal to [Vercel](https://vercel.com), using the following files:
54
-
55
- - `vercel.json`: configure the COOP / COEP server headers
56
- - `deploy/requirements-deploy.txt`: dependencies for the JupyterLite deployment
57
- - `deploy/deploy.sh`: script to deploy to Vercel, using micromamba to have full control on the Python versions and isolate the build in a virtual environment
58
-
59
- For more information, have a look at the JupyterLite documentation: https://jupyterlite.readthedocs.io/
60
-
61
49
  ## Contributing
62
50
 
63
51
  ### Development install
@@ -97,6 +85,18 @@ jupyter lite build --contents contents
97
85
 
98
86
  And serve it either using:
99
87
 
88
+ ```bash
89
+ npx static-handler _output/
90
+ ```
91
+
92
+ or:
93
+
94
+ ```bash
95
+ jupyter lite serve
96
+ ```
97
+
98
+ To enable use of SharedArrayBuffer rather than ServiceWorker for `stdin` you will have to configure your server to add the `Cross-Origin-Embedder-Policy` and `Cross-Origin-Opener-Policy` headers. Do this using either:
99
+
100
100
  ```bash
101
101
  npx static-handler --cors --coop --coep --corp _output/
102
102
  ```
@@ -107,8 +107,6 @@ or:
107
107
  jupyter lite serve --LiteBuildConfig.extra_http_headers=Cross-Origin-Embedder-Policy=require-corp --LiteBuildConfig.extra_http_headers=Cross-Origin-Opener-Policy=same-origin
108
108
  ```
109
109
 
110
- The extra HTTP headers are require to ensure that `SharedArrayBuffer` is available.
111
-
112
110
  ### Packaging the extension
113
111
 
114
112
  See [RELEASE](RELEASE.md)
@@ -0,0 +1,28 @@
1
+ import { ServerConnection, Terminal } from '@jupyterlab/services';
2
+ import { IExternalCommand, IStdinReply, IStdinRequest } from '@jupyterlite/cockle';
3
+ import { ILiteTerminalAPIClient } from './tokens';
4
+ export declare class LiteTerminalAPIClient implements ILiteTerminalAPIClient {
5
+ constructor(options?: {
6
+ serverSettings?: ServerConnection.ISettings;
7
+ });
8
+ /**
9
+ * Set identifier for communicating with service worker.
10
+ */
11
+ set browsingContextId(browsingContextId: string);
12
+ /**
13
+ * Function that handles stdin requests received from service worker.
14
+ */
15
+ handleStdin(request: IStdinRequest): Promise<IStdinReply>;
16
+ get isAvailable(): boolean;
17
+ readonly serverSettings: ServerConnection.ISettings;
18
+ startNew(options?: Terminal.ITerminal.IOptions): Promise<Terminal.IModel>;
19
+ listRunning(): Promise<Terminal.IModel[]>;
20
+ registerExternalCommand(options: IExternalCommand.IOptions): void;
21
+ shutdown(name: string): Promise<void>;
22
+ private get _models();
23
+ private _nextAvailableName;
24
+ private _browsingContextId?;
25
+ private _externalCommands;
26
+ private _shellManager;
27
+ private _shells;
28
+ }
package/lib/client.js ADDED
@@ -0,0 +1,118 @@
1
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
+ import { ServerConnection } from '@jupyterlab/services';
3
+ import { ShellManager } from '@jupyterlite/cockle';
4
+ import { Server as WebSocketServer } from 'mock-socket';
5
+ import { Shell } from './shell';
6
+ export class LiteTerminalAPIClient {
7
+ constructor(options = {}) {
8
+ var _a;
9
+ this._externalCommands = [];
10
+ this._shells = new Map();
11
+ this.serverSettings =
12
+ (_a = options.serverSettings) !== null && _a !== void 0 ? _a : ServerConnection.makeSettings();
13
+ this._shellManager = new ShellManager();
14
+ }
15
+ /**
16
+ * Set identifier for communicating with service worker.
17
+ */
18
+ set browsingContextId(browsingContextId) {
19
+ console.log('LiteTerminalAPIClient browsingContextId', browsingContextId);
20
+ this._browsingContextId = browsingContextId;
21
+ }
22
+ /**
23
+ * Function that handles stdin requests received from service worker.
24
+ */
25
+ async handleStdin(request) {
26
+ return await this._shellManager.handleStdin(request);
27
+ }
28
+ get isAvailable() {
29
+ const available = String(PageConfig.getOption('terminalsAvailable'));
30
+ return available.toLowerCase() === 'true';
31
+ }
32
+ async startNew(options) {
33
+ var _a;
34
+ // Create shell.
35
+ const name = (_a = options === null || options === void 0 ? void 0 : options.name) !== null && _a !== void 0 ? _a : this._nextAvailableName();
36
+ const { baseUrl, wsUrl } = this.serverSettings;
37
+ const shell = new Shell({
38
+ mountpoint: '/drive',
39
+ baseUrl,
40
+ wasmBaseUrl: URLExt.join(baseUrl, 'extensions/@jupyterlite/terminal/static/wasm/'),
41
+ browsingContextId: this._browsingContextId,
42
+ shellId: name,
43
+ shellManager: this._shellManager,
44
+ outputCallback: text => {
45
+ var _a;
46
+ const msg = JSON.stringify(['stdout', text]);
47
+ (_a = shell.socket) === null || _a === void 0 ? void 0 : _a.send(msg);
48
+ }
49
+ });
50
+ this._shells.set(name, shell);
51
+ for (const externalCommand of this._externalCommands) {
52
+ shell.registerExternalCommand(externalCommand);
53
+ }
54
+ // Hook to connect socket to shell.
55
+ const hook = async (shell, socket) => {
56
+ shell.socket = socket;
57
+ socket.on('message', async (message) => {
58
+ // Message from xtermjs to pass to shell.
59
+ const data = JSON.parse(message);
60
+ const message_type = data[0];
61
+ const content = data.slice(1);
62
+ await shell.ready;
63
+ if (message_type === 'stdin') {
64
+ await shell.input(content[0]);
65
+ }
66
+ else if (message_type === 'set_size') {
67
+ const rows = content[0];
68
+ const columns = content[1];
69
+ await shell.setSize(rows, columns);
70
+ }
71
+ });
72
+ // Return handshake.
73
+ const res = JSON.stringify(['setup']);
74
+ console.log('Terminal returning handshake via socket');
75
+ socket.send(res);
76
+ shell.start();
77
+ };
78
+ const url = URLExt.join(wsUrl, 'terminals', 'websocket', name);
79
+ const wsServer = new WebSocketServer(url);
80
+ wsServer.on('connection', (socket) => {
81
+ hook(shell, socket);
82
+ });
83
+ shell.disposed.connect(() => {
84
+ this.shutdown(name);
85
+ wsServer.close();
86
+ });
87
+ return { name };
88
+ }
89
+ async listRunning() {
90
+ return this._models;
91
+ }
92
+ registerExternalCommand(options) {
93
+ this._externalCommands.push(options);
94
+ }
95
+ async shutdown(name) {
96
+ var _a, _b;
97
+ const shell = this._shells.get(name);
98
+ if (shell !== undefined) {
99
+ (_a = shell.socket) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(['disconnect']));
100
+ (_b = shell.socket) === null || _b === void 0 ? void 0 : _b.close();
101
+ this._shells.delete(name);
102
+ shell.dispose();
103
+ }
104
+ }
105
+ get _models() {
106
+ return Array.from(this._shells.keys(), name => {
107
+ return { name };
108
+ });
109
+ }
110
+ _nextAvailableName() {
111
+ for (let i = 1;; ++i) {
112
+ const name = `${i}`;
113
+ if (!this._shells.has(name)) {
114
+ return name;
115
+ }
116
+ }
117
+ }
118
+ }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { ServiceManagerPlugin, Terminal } from '@jupyterlab/services';
3
- declare const _default: (ServiceManagerPlugin<Terminal.IManager> | JupyterFrontEndPlugin<void>)[];
3
+ declare const _default: (ServiceManagerPlugin<Terminal.ITerminalAPIClient> | ServiceManagerPlugin<Terminal.IManager> | JupyterFrontEndPlugin<void>)[];
4
4
  export default _default;
package/lib/index.js CHANGED
@@ -1,39 +1,67 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
- import { ITerminalManager } from '@jupyterlab/services';
3
+ import { ITerminalManager, ServerConnection, IServerSettings, TerminalManager } from '@jupyterlab/services';
4
4
  import { IServiceWorkerManager } from '@jupyterlite/server';
5
- import { isILiteTerminalManager, LiteTerminalManager } from './manager';
5
+ import { WebSocket } from 'mock-socket';
6
+ import { LiteTerminalAPIClient } from './client';
7
+ import { ILiteTerminalAPIClient } from './tokens';
6
8
  /**
7
- * The terminal manager plugin, replacing the JupyterLab terminal manager.
9
+ * Plugin containing client for in-browser terminals.
10
+ */
11
+ const terminalClientPlugin = {
12
+ id: '@jupyterlite/terminal:client',
13
+ description: 'The client for Lite terminals',
14
+ autoStart: true,
15
+ provides: ILiteTerminalAPIClient,
16
+ optional: [IServerSettings],
17
+ activate: (_, serverSettings) => {
18
+ return new LiteTerminalAPIClient({
19
+ serverSettings: {
20
+ ...ServerConnection.makeSettings(),
21
+ ...serverSettings,
22
+ WebSocket
23
+ }
24
+ });
25
+ }
26
+ };
27
+ /**
28
+ * Plugin containing manager for in-browser terminals.
8
29
  */
9
30
  const terminalManagerPlugin = {
10
- id: '@jupyterlite/terminal:plugin',
31
+ id: '@jupyterlite/terminal:manager',
11
32
  description: 'A JupyterLite extension providing a custom terminal manager',
12
33
  autoStart: true,
13
34
  provides: ITerminalManager,
14
- activate: (_) => {
15
- console.log('JupyterLite extension @jupyterlite/terminal:plugin is activated!');
16
- return new LiteTerminalManager();
35
+ requires: [ILiteTerminalAPIClient],
36
+ activate: (_, terminalAPIClient) => {
37
+ console.log('JupyterLite extension @jupyterlite/terminal:manager activated');
38
+ return new TerminalManager({
39
+ terminalAPIClient,
40
+ serverSettings: terminalAPIClient.serverSettings
41
+ });
17
42
  }
18
43
  };
19
44
  /**
20
- * A plugin that sets the browsingContextId of the terminal manager.
45
+ * Plugin that connects in-browser terminals and service worker.
21
46
  */
22
- const browsingContextIdSetter = {
23
- id: '@jupyterlite/terminal:browsing-context-id',
47
+ const terminalServiceWorkerPlugin = {
48
+ id: '@jupyterlite/terminal:service-worker',
24
49
  autoStart: true,
50
+ requires: [ILiteTerminalAPIClient],
25
51
  optional: [IServiceWorkerManager],
26
- requires: [ITerminalManager],
27
- activate: (_, terminalManager, serviceWorkerManager) => {
52
+ activate: (_, liteTerminalAPIClient, serviceWorkerManager) => {
28
53
  if (serviceWorkerManager !== undefined) {
29
- if (isILiteTerminalManager(terminalManager)) {
30
- const { browsingContextId } = serviceWorkerManager;
31
- terminalManager.browsingContextId = browsingContextId;
32
- }
33
- else {
34
- console.warn('Terminal manager does not support setting browsingContextId');
35
- }
54
+ liteTerminalAPIClient.browsingContextId =
55
+ serviceWorkerManager.browsingContextId;
56
+ serviceWorkerManager.registerStdinHandler('terminal', liteTerminalAPIClient.handleStdin.bind(liteTerminalAPIClient));
57
+ }
58
+ else {
59
+ console.warn('Service worker is not available for terminals');
36
60
  }
37
61
  }
38
62
  };
39
- export default [terminalManagerPlugin, browsingContextIdSetter];
63
+ export default [
64
+ terminalClientPlugin,
65
+ terminalManagerPlugin,
66
+ terminalServiceWorkerPlugin
67
+ ];
package/lib/shell.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BaseShell, IShell } from '@jupyterlite/cockle';
2
+ import { Client as WebSocketClient } from 'mock-socket';
2
3
  /**
3
4
  * Shell class that uses web worker that plugs into a DriveFS via the service worker.
4
5
  */
@@ -13,4 +14,5 @@ export declare class Shell extends BaseShell {
13
14
  * Load the web worker.
14
15
  */
15
16
  protected initWorker(options: IShell.IOptions): Worker;
17
+ socket?: WebSocketClient;
16
18
  }
@@ -0,0 +1,18 @@
1
+ import { Terminal } from '@jupyterlab/services';
2
+ import { IExternalCommand, IStdinReply, IStdinRequest } from '@jupyterlite/cockle';
3
+ import { Token } from '@lumino/coreutils';
4
+ export declare const ILiteTerminalAPIClient: Token<ILiteTerminalAPIClient>;
5
+ export interface ILiteTerminalAPIClient extends Terminal.ITerminalAPIClient {
6
+ /**
7
+ * Identifier for communicating with service worker.
8
+ */
9
+ browsingContextId: string;
10
+ /**
11
+ * Function that handles stdin requests received from service worker.
12
+ */
13
+ handleStdin(request: IStdinRequest): Promise<IStdinReply>;
14
+ /**
15
+ * Register an external command that will be available in all terminals.
16
+ */
17
+ registerExternalCommand(options: IExternalCommand.IOptions): void;
18
+ }
package/lib/tokens.js ADDED
@@ -0,0 +1,2 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ export const ILiteTerminalAPIClient = new Token('@jupyterlite/terminal:client');
package/lib/worker.js CHANGED
@@ -10,17 +10,17 @@ class ShellWorker extends BaseShellWorker {
10
10
  * Initialize the DriveFS to mount an external file system, if available.
11
11
  */
12
12
  initDriveFS(options) {
13
- const { browsingContextId, driveFsBaseUrl, fileSystem, mountpoint } = options;
14
- console.log('Terminal initDriveFS', driveFsBaseUrl, mountpoint, browsingContextId);
13
+ const { baseUrl, browsingContextId, fileSystem, mountpoint } = options;
14
+ console.log('Terminal initDriveFS', baseUrl, mountpoint, browsingContextId);
15
15
  if (mountpoint !== '' &&
16
- driveFsBaseUrl !== undefined &&
16
+ baseUrl !== undefined &&
17
17
  browsingContextId !== undefined) {
18
18
  const { FS, ERRNO_CODES, PATH } = fileSystem;
19
19
  const driveFS = new DriveFS({
20
20
  FS,
21
21
  PATH,
22
22
  ERRNO_CODES,
23
- baseUrl: driveFsBaseUrl,
23
+ baseUrl,
24
24
  driveName: '',
25
25
  mountpoint,
26
26
  browsingContextId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlite/terminal",
3
- "version": "0.2.0-a0",
3
+ "version": "0.2.0",
4
4
  "description": "A terminal for JupyterLite",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -59,19 +59,17 @@
59
59
  "watch:labextension": "jupyter labextension watch ."
60
60
  },
61
61
  "dependencies": {
62
- "@jupyterlab/coreutils": "^6.4.2",
63
- "@jupyterlab/services": "^7.4.2",
64
- "@jupyterlab/terminal": "^4.4.2",
65
- "@jupyterlab/terminal-extension": "^4.4.2",
66
- "@jupyterlite/cockle": "^0.0.19",
67
- "@jupyterlite/contents": "0.6.0-beta.0",
68
- "@jupyterlite/server": "0.6.0-beta.0",
62
+ "@jupyterlab/coreutils": "^6.4.3",
63
+ "@jupyterlab/services": "^7.4.3",
64
+ "@jupyterlite/cockle": "^0.1.0",
65
+ "@jupyterlite/contents": "0.6.0",
66
+ "@jupyterlite/server": "0.6.0",
69
67
  "@lumino/coreutils": "^2.2.0",
70
68
  "mock-socket": "^9.3.1"
71
69
  },
72
70
  "devDependencies": {
73
- "@jupyterlab/builder": "^4.4.2",
74
- "@jupyterlab/testutils": "^4.4.2",
71
+ "@jupyterlab/builder": "^4.4.3",
72
+ "@jupyterlab/testutils": "^4.4.3",
75
73
  "@types/jest": "^29.2.0",
76
74
  "@types/json-schema": "^7.0.11",
77
75
  "@types/react": "^18.0.26",
@@ -99,6 +97,9 @@
99
97
  "webpack-cli": "^5.1.4",
100
98
  "yjs": "^13.5.0"
101
99
  },
100
+ "resolutions": {
101
+ "parse5": "7.2.1"
102
+ },
102
103
  "sideEffects": [
103
104
  "style/*.css",
104
105
  "style/index.js"
package/src/client.ts ADDED
@@ -0,0 +1,157 @@
1
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
+ import { ServerConnection, Terminal } from '@jupyterlab/services';
3
+ import {
4
+ IExternalCommand,
5
+ IShellManager,
6
+ IStdinReply,
7
+ IStdinRequest,
8
+ ShellManager
9
+ } from '@jupyterlite/cockle';
10
+ import { JSONPrimitive } from '@lumino/coreutils';
11
+
12
+ import {
13
+ Server as WebSocketServer,
14
+ Client as WebSocketClient
15
+ } from 'mock-socket';
16
+
17
+ import { Shell } from './shell';
18
+ import { ILiteTerminalAPIClient } from './tokens';
19
+
20
+ export class LiteTerminalAPIClient implements ILiteTerminalAPIClient {
21
+ constructor(options: { serverSettings?: ServerConnection.ISettings } = {}) {
22
+ this.serverSettings =
23
+ options.serverSettings ?? ServerConnection.makeSettings();
24
+ this._shellManager = new ShellManager();
25
+ }
26
+
27
+ /**
28
+ * Set identifier for communicating with service worker.
29
+ */
30
+ set browsingContextId(browsingContextId: string) {
31
+ console.log('LiteTerminalAPIClient browsingContextId', browsingContextId);
32
+ this._browsingContextId = browsingContextId;
33
+ }
34
+
35
+ /**
36
+ * Function that handles stdin requests received from service worker.
37
+ */
38
+ async handleStdin(request: IStdinRequest): Promise<IStdinReply> {
39
+ return await this._shellManager.handleStdin(request);
40
+ }
41
+
42
+ get isAvailable(): boolean {
43
+ const available = String(PageConfig.getOption('terminalsAvailable'));
44
+ return available.toLowerCase() === 'true';
45
+ }
46
+
47
+ readonly serverSettings: ServerConnection.ISettings;
48
+
49
+ async startNew(
50
+ options?: Terminal.ITerminal.IOptions
51
+ ): Promise<Terminal.IModel> {
52
+ // Create shell.
53
+ const name = options?.name ?? this._nextAvailableName();
54
+ const { baseUrl, wsUrl } = this.serverSettings;
55
+ const shell = new Shell({
56
+ mountpoint: '/drive',
57
+ baseUrl,
58
+ wasmBaseUrl: URLExt.join(
59
+ baseUrl,
60
+ 'extensions/@jupyterlite/terminal/static/wasm/'
61
+ ),
62
+ browsingContextId: this._browsingContextId,
63
+ shellId: name,
64
+ shellManager: this._shellManager,
65
+ outputCallback: text => {
66
+ const msg = JSON.stringify(['stdout', text]);
67
+ shell.socket?.send(msg);
68
+ }
69
+ });
70
+ this._shells.set(name, shell);
71
+
72
+ for (const externalCommand of this._externalCommands) {
73
+ shell.registerExternalCommand(externalCommand);
74
+ }
75
+
76
+ // Hook to connect socket to shell.
77
+ const hook = async (
78
+ shell: Shell,
79
+ socket: WebSocketClient
80
+ ): Promise<void> => {
81
+ shell.socket = socket;
82
+
83
+ socket.on('message', async (message: any) => {
84
+ // Message from xtermjs to pass to shell.
85
+ const data = JSON.parse(message) as JSONPrimitive[];
86
+ const message_type = data[0];
87
+ const content = data.slice(1);
88
+ await shell.ready;
89
+ if (message_type === 'stdin') {
90
+ await shell.input(content[0] as string);
91
+ } else if (message_type === 'set_size') {
92
+ const rows = content[0] as number;
93
+ const columns = content[1] as number;
94
+ await shell.setSize(rows, columns);
95
+ }
96
+ });
97
+
98
+ // Return handshake.
99
+ const res = JSON.stringify(['setup']);
100
+ console.log('Terminal returning handshake via socket');
101
+ socket.send(res);
102
+
103
+ shell.start();
104
+ };
105
+
106
+ const url = URLExt.join(wsUrl, 'terminals', 'websocket', name);
107
+ const wsServer = new WebSocketServer(url);
108
+ wsServer.on('connection', (socket: WebSocketClient): void => {
109
+ hook(shell, socket);
110
+ });
111
+
112
+ shell.disposed.connect(() => {
113
+ this.shutdown(name);
114
+ wsServer.close();
115
+ });
116
+
117
+ return { name };
118
+ }
119
+
120
+ async listRunning(): Promise<Terminal.IModel[]> {
121
+ return this._models;
122
+ }
123
+
124
+ registerExternalCommand(options: IExternalCommand.IOptions): void {
125
+ this._externalCommands.push(options);
126
+ }
127
+
128
+ async shutdown(name: string): Promise<void> {
129
+ const shell = this._shells.get(name);
130
+ if (shell !== undefined) {
131
+ shell.socket?.send(JSON.stringify(['disconnect']));
132
+ shell.socket?.close();
133
+ this._shells.delete(name);
134
+ shell.dispose();
135
+ }
136
+ }
137
+
138
+ private get _models(): Terminal.IModel[] {
139
+ return Array.from(this._shells.keys(), name => {
140
+ return { name };
141
+ });
142
+ }
143
+
144
+ private _nextAvailableName(): string {
145
+ for (let i = 1; ; ++i) {
146
+ const name = `${i}`;
147
+ if (!this._shells.has(name)) {
148
+ return name;
149
+ }
150
+ }
151
+ }
152
+
153
+ private _browsingContextId?: string;
154
+ private _externalCommands: IExternalCommand.IOptions[] = [];
155
+ private _shellManager: IShellManager;
156
+ private _shells = new Map<string, Shell>();
157
+ }
package/src/index.ts CHANGED
@@ -8,52 +8,94 @@ import {
8
8
  import {
9
9
  ITerminalManager,
10
10
  ServiceManagerPlugin,
11
- Terminal
11
+ Terminal,
12
+ ServerConnection,
13
+ IServerSettings,
14
+ TerminalManager
12
15
  } from '@jupyterlab/services';
13
16
  import { IServiceWorkerManager } from '@jupyterlite/server';
14
17
 
15
- import { isILiteTerminalManager, LiteTerminalManager } from './manager';
18
+ import { WebSocket } from 'mock-socket';
19
+
20
+ import { LiteTerminalAPIClient } from './client';
21
+ import { ILiteTerminalAPIClient } from './tokens';
16
22
 
17
23
  /**
18
- * The terminal manager plugin, replacing the JupyterLab terminal manager.
24
+ * Plugin containing client for in-browser terminals.
25
+ */
26
+ const terminalClientPlugin: ServiceManagerPlugin<Terminal.ITerminalAPIClient> =
27
+ {
28
+ id: '@jupyterlite/terminal:client',
29
+ description: 'The client for Lite terminals',
30
+ autoStart: true,
31
+ provides: ILiteTerminalAPIClient,
32
+ optional: [IServerSettings],
33
+ activate: (
34
+ _: null,
35
+ serverSettings?: ServerConnection.ISettings
36
+ ): ILiteTerminalAPIClient => {
37
+ return new LiteTerminalAPIClient({
38
+ serverSettings: {
39
+ ...ServerConnection.makeSettings(),
40
+ ...serverSettings,
41
+ WebSocket
42
+ }
43
+ });
44
+ }
45
+ };
46
+
47
+ /**
48
+ * Plugin containing manager for in-browser terminals.
19
49
  */
20
50
  const terminalManagerPlugin: ServiceManagerPlugin<Terminal.IManager> = {
21
- id: '@jupyterlite/terminal:plugin',
51
+ id: '@jupyterlite/terminal:manager',
22
52
  description: 'A JupyterLite extension providing a custom terminal manager',
23
53
  autoStart: true,
24
54
  provides: ITerminalManager,
25
- activate: (_: null): Terminal.IManager => {
55
+ requires: [ILiteTerminalAPIClient],
56
+ activate: (
57
+ _: null,
58
+ terminalAPIClient: Terminal.ITerminalAPIClient
59
+ ): Terminal.IManager => {
26
60
  console.log(
27
- 'JupyterLite extension @jupyterlite/terminal:plugin is activated!'
61
+ 'JupyterLite extension @jupyterlite/terminal:manager activated'
28
62
  );
29
- return new LiteTerminalManager();
63
+ return new TerminalManager({
64
+ terminalAPIClient,
65
+ serverSettings: terminalAPIClient.serverSettings
66
+ });
30
67
  }
31
68
  };
32
69
 
33
70
  /**
34
- * A plugin that sets the browsingContextId of the terminal manager.
71
+ * Plugin that connects in-browser terminals and service worker.
35
72
  */
36
- const browsingContextIdSetter: JupyterFrontEndPlugin<void> = {
37
- id: '@jupyterlite/terminal:browsing-context-id',
73
+ const terminalServiceWorkerPlugin: JupyterFrontEndPlugin<void> = {
74
+ id: '@jupyterlite/terminal:service-worker',
38
75
  autoStart: true,
76
+ requires: [ILiteTerminalAPIClient],
39
77
  optional: [IServiceWorkerManager],
40
- requires: [ITerminalManager],
41
78
  activate: (
42
79
  _: JupyterFrontEnd,
43
- terminalManager: Terminal.IManager,
80
+ liteTerminalAPIClient: ILiteTerminalAPIClient,
44
81
  serviceWorkerManager?: IServiceWorkerManager
45
82
  ): void => {
46
83
  if (serviceWorkerManager !== undefined) {
47
- if (isILiteTerminalManager(terminalManager)) {
48
- const { browsingContextId } = serviceWorkerManager;
49
- terminalManager.browsingContextId = browsingContextId;
50
- } else {
51
- console.warn(
52
- 'Terminal manager does not support setting browsingContextId'
53
- );
54
- }
84
+ liteTerminalAPIClient.browsingContextId =
85
+ serviceWorkerManager.browsingContextId;
86
+
87
+ serviceWorkerManager.registerStdinHandler(
88
+ 'terminal',
89
+ liteTerminalAPIClient.handleStdin.bind(liteTerminalAPIClient)
90
+ );
91
+ } else {
92
+ console.warn('Service worker is not available for terminals');
55
93
  }
56
94
  }
57
95
  };
58
96
 
59
- export default [terminalManagerPlugin, browsingContextIdSetter];
97
+ export default [
98
+ terminalClientPlugin,
99
+ terminalManagerPlugin,
100
+ terminalServiceWorkerPlugin
101
+ ];