@jupyterlite/terminal 0.1.6 → 0.2.0-a0
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/lib/index.d.ts +3 -3
- package/lib/index.js +20 -42
- package/lib/manager.d.ts +79 -18
- package/lib/manager.js +139 -39
- package/lib/terminal.d.ts +76 -12
- package/lib/terminal.js +114 -68
- package/lib/worker.js +24 -22
- package/package.json +10 -13
- package/src/index.ts +34 -60
- package/src/manager.ts +167 -42
- package/src/terminal.ts +152 -88
- package/src/worker.ts +32 -38
- package/lib/tokens.d.ts +0 -52
- package/lib/tokens.js +0 -7
- package/src/tokens.ts +0 -66
package/lib/terminal.js
CHANGED
|
@@ -1,101 +1,147 @@
|
|
|
1
|
-
// Copyright (c) Jupyter Development Team.
|
|
2
|
-
// Distributed under the terms of the Modified BSD License.
|
|
3
1
|
import { Signal } from '@lumino/signaling';
|
|
4
|
-
import { Server as WebSocketServer } from 'mock-socket';
|
|
5
2
|
import { Shell } from './shell';
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* An implementation of a terminal interface.
|
|
5
|
+
*/
|
|
6
|
+
export class LiteTerminalConnection {
|
|
7
7
|
/**
|
|
8
|
-
* Construct a new
|
|
8
|
+
* Construct a new terminal session.
|
|
9
9
|
*/
|
|
10
10
|
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
this._disposed = new Signal(this);
|
|
13
11
|
this._isDisposed = false;
|
|
14
|
-
this.
|
|
12
|
+
this._disposed = new Signal(this);
|
|
13
|
+
this._connectionStatus = 'connecting';
|
|
14
|
+
this._connectionStatusChanged = new Signal(this);
|
|
15
|
+
this._messageReceived = new Signal(this);
|
|
16
|
+
this._name = options.model.name;
|
|
17
|
+
this._serverSettings = options.serverSettings;
|
|
18
|
+
const { baseUrl } = this._serverSettings;
|
|
19
|
+
const { browsingContextId } = options;
|
|
15
20
|
this._shell = new Shell({
|
|
16
21
|
mountpoint: '/drive',
|
|
17
|
-
driveFsBaseUrl:
|
|
18
|
-
wasmBaseUrl:
|
|
19
|
-
outputCallback: this._outputCallback.bind(this)
|
|
22
|
+
driveFsBaseUrl: baseUrl,
|
|
23
|
+
wasmBaseUrl: baseUrl + 'extensions/@jupyterlite/terminal/static/wasm/',
|
|
24
|
+
outputCallback: this._outputCallback.bind(this),
|
|
25
|
+
browsingContextId
|
|
20
26
|
});
|
|
21
27
|
this._shell.disposed.connect(() => this.dispose());
|
|
28
|
+
this._shell.start().then(() => this._updateConnectionStatus('connected'));
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The current connection status of the terminal connection.
|
|
32
|
+
*/
|
|
33
|
+
get connectionStatus() {
|
|
34
|
+
return this._connectionStatus;
|
|
28
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A signal emitted when the terminal connection status changes.
|
|
38
|
+
*/
|
|
39
|
+
get connectionStatusChanged() {
|
|
40
|
+
return this._connectionStatusChanged;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Dispose of the resources held by the session.
|
|
44
|
+
*/
|
|
29
45
|
dispose() {
|
|
30
46
|
if (this._isDisposed) {
|
|
31
47
|
return;
|
|
32
48
|
}
|
|
33
|
-
console.log('Terminal.dispose');
|
|
34
49
|
this._isDisposed = true;
|
|
35
|
-
if (this._socket !== undefined) {
|
|
36
|
-
// Disconnect from frontend.
|
|
37
|
-
this._socket.send(JSON.stringify(['disconnect']));
|
|
38
|
-
this._socket.close();
|
|
39
|
-
this._socket = undefined;
|
|
40
|
-
}
|
|
41
|
-
if (this._server !== undefined) {
|
|
42
|
-
this._server.close();
|
|
43
|
-
this._server = undefined;
|
|
44
|
-
}
|
|
45
50
|
this._shell.dispose();
|
|
46
51
|
this._disposed.emit();
|
|
52
|
+
this._updateConnectionStatus('disconnected');
|
|
53
|
+
Signal.clearData(this);
|
|
47
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* A signal emitted when the session is disposed.
|
|
57
|
+
*/
|
|
48
58
|
get disposed() {
|
|
49
59
|
return this._disposed;
|
|
50
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Test whether the session is disposed.
|
|
63
|
+
*/
|
|
51
64
|
get isDisposed() {
|
|
52
65
|
return this._isDisposed;
|
|
53
66
|
}
|
|
54
67
|
/**
|
|
55
|
-
*
|
|
68
|
+
* A signal emitted when a message is received from the server.
|
|
69
|
+
*/
|
|
70
|
+
get messageReceived() {
|
|
71
|
+
return this._messageReceived;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the model for the terminal session.
|
|
75
|
+
*/
|
|
76
|
+
get model() {
|
|
77
|
+
return { name: this._name };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the name of the terminal session.
|
|
56
81
|
*/
|
|
57
82
|
get name() {
|
|
58
|
-
return this.
|
|
83
|
+
return this._name;
|
|
59
84
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Return handshake.
|
|
92
|
-
const res = JSON.stringify(['setup']);
|
|
93
|
-
console.log('Terminal returning handshake via socket');
|
|
94
|
-
socket.send(res);
|
|
95
|
-
if (!this._running) {
|
|
96
|
-
this._running = true;
|
|
97
|
-
await this._shell.start();
|
|
85
|
+
/**
|
|
86
|
+
* Reconnect to a terminal.
|
|
87
|
+
*
|
|
88
|
+
* #### Notes
|
|
89
|
+
* This may try multiple times to reconnect to a terminal, and will sever
|
|
90
|
+
* any existing connection.
|
|
91
|
+
*/
|
|
92
|
+
async reconnect() {
|
|
93
|
+
console.log('==> LiteTerminalConnection.reconnect not implemented');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Send a message to the terminal session.
|
|
97
|
+
*
|
|
98
|
+
* #### Notes
|
|
99
|
+
* If the connection is down, the message will be queued for sending when
|
|
100
|
+
* the connection comes back up.
|
|
101
|
+
*/
|
|
102
|
+
send(message) {
|
|
103
|
+
const { content } = message;
|
|
104
|
+
if (content === undefined) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
switch (message.type) {
|
|
108
|
+
case 'stdin':
|
|
109
|
+
this._shell.input(content[0]); // async
|
|
110
|
+
break;
|
|
111
|
+
case 'set_size': {
|
|
112
|
+
const rows = content[0];
|
|
113
|
+
const columns = content[1];
|
|
114
|
+
this._shell.setSize(rows, columns); // async
|
|
115
|
+
break;
|
|
98
116
|
}
|
|
99
|
-
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The server settings for the session.
|
|
121
|
+
*/
|
|
122
|
+
get serverSettings() {
|
|
123
|
+
return this._serverSettings;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Shut down the terminal session.
|
|
127
|
+
*/
|
|
128
|
+
async shutdown() {
|
|
129
|
+
this.dispose();
|
|
130
|
+
}
|
|
131
|
+
_outputCallback(text) {
|
|
132
|
+
// 'stdout' or 'disconnect' as MessageType.
|
|
133
|
+
// Cockle is not yet using the 'disconnect'.
|
|
134
|
+
this._messageReceived.emit({ type: 'stdout', content: [text] });
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Handle connection status changes.
|
|
138
|
+
*/
|
|
139
|
+
_updateConnectionStatus(connectionStatus) {
|
|
140
|
+
if (this._connectionStatus === connectionStatus) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this._connectionStatus = connectionStatus;
|
|
144
|
+
// Notify others that the connection status changed.
|
|
145
|
+
this._connectionStatusChanged.emit(connectionStatus);
|
|
100
146
|
}
|
|
101
147
|
}
|
package/lib/worker.js
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
import { expose } from 'comlink';
|
|
2
2
|
import { BaseShellWorker } from '@jupyterlite/cockle';
|
|
3
|
-
import { DriveFS
|
|
4
|
-
/**
|
|
5
|
-
* Custom DriveFS implementation using the service worker.
|
|
6
|
-
*/
|
|
7
|
-
class MyDriveFS extends DriveFS {
|
|
8
|
-
createAPI(options) {
|
|
9
|
-
return new ServiceWorkerContentsAPI(options.baseUrl, options.driveName, options.mountpoint, options.FS, options.ERRNO_CODES);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
3
|
+
import { DriveFS } from '@jupyterlite/contents';
|
|
12
4
|
/**
|
|
13
5
|
* Shell web worker that uses DriveFS via service worker.
|
|
14
6
|
* Note that this is not exported as it is accessed from Shell via the filename.
|
|
15
7
|
*/
|
|
16
8
|
class ShellWorker extends BaseShellWorker {
|
|
17
9
|
/**
|
|
18
|
-
* Initialize the DriveFS to mount an external file system.
|
|
10
|
+
* Initialize the DriveFS to mount an external file system, if available.
|
|
19
11
|
*/
|
|
20
|
-
initDriveFS(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ERRNO_CODES,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
initDriveFS(options) {
|
|
13
|
+
const { browsingContextId, driveFsBaseUrl, fileSystem, mountpoint } = options;
|
|
14
|
+
console.log('Terminal initDriveFS', driveFsBaseUrl, mountpoint, browsingContextId);
|
|
15
|
+
if (mountpoint !== '' &&
|
|
16
|
+
driveFsBaseUrl !== undefined &&
|
|
17
|
+
browsingContextId !== undefined) {
|
|
18
|
+
const { FS, ERRNO_CODES, PATH } = fileSystem;
|
|
19
|
+
const driveFS = new DriveFS({
|
|
20
|
+
FS,
|
|
21
|
+
PATH,
|
|
22
|
+
ERRNO_CODES,
|
|
23
|
+
baseUrl: driveFsBaseUrl,
|
|
24
|
+
driveName: '',
|
|
25
|
+
mountpoint,
|
|
26
|
+
browsingContextId
|
|
27
|
+
});
|
|
28
|
+
FS.mount(driveFS, {}, mountpoint);
|
|
29
|
+
console.log('Terminal connected to shared drive');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.warn('Terminal not connected to shared drive');
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
const worker = new ShellWorker();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/terminal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-a0",
|
|
4
4
|
"description": "A terminal for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -59,19 +59,19 @@
|
|
|
59
59
|
"watch:labextension": "jupyter labextension watch ."
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@jupyterlab/coreutils": "^6.
|
|
63
|
-
"@jupyterlab/services": "^7.
|
|
64
|
-
"@jupyterlab/terminal": "^4.
|
|
65
|
-
"@jupyterlab/terminal-extension": "^4.
|
|
66
|
-
"@jupyterlite/cockle": "^0.0.
|
|
67
|
-
"@jupyterlite/contents": "
|
|
68
|
-
"@jupyterlite/server": "
|
|
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",
|
|
69
69
|
"@lumino/coreutils": "^2.2.0",
|
|
70
70
|
"mock-socket": "^9.3.1"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@jupyterlab/builder": "^4.
|
|
74
|
-
"@jupyterlab/testutils": "^4.
|
|
73
|
+
"@jupyterlab/builder": "^4.4.2",
|
|
74
|
+
"@jupyterlab/testutils": "^4.4.2",
|
|
75
75
|
"@types/jest": "^29.2.0",
|
|
76
76
|
"@types/json-schema": "^7.0.11",
|
|
77
77
|
"@types/react": "^18.0.26",
|
|
@@ -111,9 +111,6 @@
|
|
|
111
111
|
"extension": true,
|
|
112
112
|
"outputDir": "jupyterlite_terminal/labextension"
|
|
113
113
|
},
|
|
114
|
-
"jupyterlite": {
|
|
115
|
-
"liteExtension": true
|
|
116
|
-
},
|
|
117
114
|
"eslintIgnore": [
|
|
118
115
|
"node_modules",
|
|
119
116
|
"dist",
|
package/src/index.ts
CHANGED
|
@@ -2,84 +2,58 @@
|
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
JupyterFrontEnd,
|
|
6
|
+
JupyterFrontEndPlugin
|
|
7
|
+
} from '@jupyterlab/application';
|
|
8
|
+
import {
|
|
9
|
+
ITerminalManager,
|
|
10
|
+
ServiceManagerPlugin,
|
|
11
|
+
Terminal
|
|
12
|
+
} from '@jupyterlab/services';
|
|
13
|
+
import { IServiceWorkerManager } from '@jupyterlite/server';
|
|
9
14
|
|
|
10
|
-
import {
|
|
11
|
-
import { ITerminalManager } from './tokens';
|
|
15
|
+
import { isILiteTerminalManager, LiteTerminalManager } from './manager';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
|
-
* The
|
|
18
|
+
* The terminal manager plugin, replacing the JupyterLab terminal manager.
|
|
15
19
|
*/
|
|
16
|
-
const
|
|
20
|
+
const terminalManagerPlugin: ServiceManagerPlugin<Terminal.IManager> = {
|
|
17
21
|
id: '@jupyterlite/terminal:plugin',
|
|
18
|
-
description: 'A terminal
|
|
22
|
+
description: 'A JupyterLite extension providing a custom terminal manager',
|
|
19
23
|
autoStart: true,
|
|
20
24
|
provides: ITerminalManager,
|
|
21
|
-
activate:
|
|
25
|
+
activate: (_: null): Terminal.IManager => {
|
|
22
26
|
console.log(
|
|
23
27
|
'JupyterLite extension @jupyterlite/terminal:plugin is activated!'
|
|
24
28
|
);
|
|
25
|
-
|
|
26
|
-
const { serviceManager } = app;
|
|
27
|
-
const { serverSettings, terminals } = serviceManager;
|
|
28
|
-
console.log('terminals available:', terminals.isAvailable());
|
|
29
|
-
console.log('terminals ready:', terminals.isReady); // Not ready
|
|
30
|
-
console.log('terminals active:', terminals.isActive);
|
|
31
|
-
|
|
32
|
-
// Not sure this is necessary?
|
|
33
|
-
await terminals.ready;
|
|
34
|
-
console.log('terminals ready after await:', terminals.isReady); // Ready
|
|
35
|
-
|
|
36
|
-
return new TerminalManager(serverSettings.wsUrl);
|
|
29
|
+
return new LiteTerminalManager();
|
|
37
30
|
}
|
|
38
31
|
};
|
|
39
32
|
|
|
40
33
|
/**
|
|
41
|
-
* A plugin
|
|
34
|
+
* A plugin that sets the browsingContextId of the terminal manager.
|
|
42
35
|
*/
|
|
43
|
-
const
|
|
44
|
-
id: '@jupyterlite/terminal:
|
|
36
|
+
const browsingContextIdSetter: JupyterFrontEndPlugin<void> = {
|
|
37
|
+
id: '@jupyterlite/terminal:browsing-context-id',
|
|
45
38
|
autoStart: true,
|
|
39
|
+
optional: [IServiceWorkerManager],
|
|
46
40
|
requires: [ITerminalManager],
|
|
47
|
-
activate: (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// POST /api/terminals - Start a terminal
|
|
61
|
-
app.router.post('/api/terminals', async (req: Router.IRequest) => {
|
|
62
|
-
const res = await terminalManager.startNew();
|
|
63
|
-
// Should return last_activity too.
|
|
64
|
-
return new Response(JSON.stringify(res));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// DELETE /api/terminals/{terminal name} - Delete a terminal
|
|
68
|
-
app.router.delete(
|
|
69
|
-
'/api/terminals/(.+)',
|
|
70
|
-
async (req: Router.IRequest, name: string) => {
|
|
71
|
-
const exists = terminalManager.has(name);
|
|
72
|
-
if (exists) {
|
|
73
|
-
await terminalManager.shutdownTerminal(name);
|
|
74
|
-
} else {
|
|
75
|
-
const msg = `The terminal session "${name}"" does not exist`;
|
|
76
|
-
console.warn(msg);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return new Response(null, { status: exists ? 204 : 404 });
|
|
41
|
+
activate: (
|
|
42
|
+
_: JupyterFrontEnd,
|
|
43
|
+
terminalManager: Terminal.IManager,
|
|
44
|
+
serviceWorkerManager?: IServiceWorkerManager
|
|
45
|
+
): void => {
|
|
46
|
+
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
|
+
);
|
|
80
54
|
}
|
|
81
|
-
|
|
55
|
+
}
|
|
82
56
|
}
|
|
83
57
|
};
|
|
84
58
|
|
|
85
|
-
export default [
|
|
59
|
+
export default [terminalManagerPlugin, browsingContextIdSetter];
|