@difizen/libro-kernel 0.0.2-alpha.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/LICENSE +21 -0
- package/README.md +1 -0
- package/es/basemanager.d.ts +94 -0
- package/es/basemanager.d.ts.map +1 -0
- package/es/basemanager.js +110 -0
- package/es/contents/contents-drive.d.ts +189 -0
- package/es/contents/contents-drive.d.ts.map +1 -0
- package/es/contents/contents-drive.js +792 -0
- package/es/contents/contents-manager.d.ts +229 -0
- package/es/contents/contents-manager.d.ts.map +1 -0
- package/es/contents/contents-manager.js +551 -0
- package/es/contents/contents-module.d.ts +3 -0
- package/es/contents/contents-module.d.ts.map +1 -0
- package/es/contents/contents-module.js +4 -0
- package/es/contents/contents-protocol.d.ts +487 -0
- package/es/contents/contents-protocol.d.ts.map +1 -0
- package/es/contents/contents-protocol.js +1 -0
- package/es/contents/index.d.ts +6 -0
- package/es/contents/index.d.ts.map +1 -0
- package/es/contents/index.js +5 -0
- package/es/contents/validate.d.ts +10 -0
- package/es/contents/validate.d.ts.map +1 -0
- package/es/contents/validate.js +22 -0
- package/es/index.d.ts +10 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +9 -0
- package/es/index.less +0 -0
- package/es/kernel/comm.d.ts +92 -0
- package/es/kernel/comm.d.ts.map +1 -0
- package/es/kernel/comm.js +216 -0
- package/es/kernel/future.d.ts +178 -0
- package/es/kernel/future.d.ts.map +1 -0
- package/es/kernel/future.js +587 -0
- package/es/kernel/index.d.ts +8 -0
- package/es/kernel/index.d.ts.map +1 -0
- package/es/kernel/index.js +8 -0
- package/es/kernel/kernel-connection.d.ts +550 -0
- package/es/kernel/kernel-connection.d.ts.map +1 -0
- package/es/kernel/kernel-connection.js +1957 -0
- package/es/kernel/kernel-module.d.ts +3 -0
- package/es/kernel/kernel-module.d.ts.map +1 -0
- package/es/kernel/kernel-module.js +32 -0
- package/es/kernel/libro-kernel-manager.d.ts +69 -0
- package/es/kernel/libro-kernel-manager.d.ts.map +1 -0
- package/es/kernel/libro-kernel-manager.js +349 -0
- package/es/kernel/libro-kernel-protocol.d.ts +675 -0
- package/es/kernel/libro-kernel-protocol.d.ts.map +1 -0
- package/es/kernel/libro-kernel-protocol.js +60 -0
- package/es/kernel/libro-kernel-utils.d.ts +95 -0
- package/es/kernel/libro-kernel-utils.d.ts.map +1 -0
- package/es/kernel/libro-kernel-utils.js +130 -0
- package/es/kernel/libro-kernel.d.ts +14 -0
- package/es/kernel/libro-kernel.d.ts.map +1 -0
- package/es/kernel/libro-kernel.js +54 -0
- package/es/kernel/messages.d.ts +845 -0
- package/es/kernel/messages.d.ts.map +1 -0
- package/es/kernel/messages.js +457 -0
- package/es/kernel/restapi.d.ts +78 -0
- package/es/kernel/restapi.d.ts.map +1 -0
- package/es/kernel/restapi.js +367 -0
- package/es/kernel/serialize.d.ts +10 -0
- package/es/kernel/serialize.d.ts.map +1 -0
- package/es/kernel/serialize.js +214 -0
- package/es/kernel/validate.d.ts +15 -0
- package/es/kernel/validate.d.ts.map +1 -0
- package/es/kernel/validate.js +125 -0
- package/es/kernelspec/index.d.ts +5 -0
- package/es/kernelspec/index.d.ts.map +1 -0
- package/es/kernelspec/index.js +4 -0
- package/es/kernelspec/kernelspec-module.d.ts +3 -0
- package/es/kernelspec/kernelspec-module.d.ts.map +1 -0
- package/es/kernelspec/kernelspec-module.js +4 -0
- package/es/kernelspec/kernelspec.d.ts +33 -0
- package/es/kernelspec/kernelspec.d.ts.map +1 -0
- package/es/kernelspec/kernelspec.js +1 -0
- package/es/kernelspec/manager.d.ts +81 -0
- package/es/kernelspec/manager.d.ts.map +1 -0
- package/es/kernelspec/manager.js +248 -0
- package/es/kernelspec/restapi.d.ts +71 -0
- package/es/kernelspec/restapi.d.ts.map +1 -0
- package/es/kernelspec/restapi.js +107 -0
- package/es/kernelspec/validate.d.ts +10 -0
- package/es/kernelspec/validate.d.ts.map +1 -0
- package/es/kernelspec/validate.js +69 -0
- package/es/libro-kernel-connection-manager.d.ts +19 -0
- package/es/libro-kernel-connection-manager.d.ts.map +1 -0
- package/es/libro-kernel-connection-manager.js +142 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +9 -0
- package/es/page-config.d.ts +36 -0
- package/es/page-config.d.ts.map +1 -0
- package/es/page-config.js +129 -0
- package/es/protocol.d.ts +13 -0
- package/es/protocol.d.ts.map +1 -0
- package/es/protocol.js +8 -0
- package/es/server/connection-error.d.ts +36 -0
- package/es/server/connection-error.d.ts.map +1 -0
- package/es/server/connection-error.js +109 -0
- package/es/server/index.d.ts +6 -0
- package/es/server/index.d.ts.map +1 -0
- package/es/server/index.js +5 -0
- package/es/server/server-connection-protocol.d.ts +49 -0
- package/es/server/server-connection-protocol.d.ts.map +1 -0
- package/es/server/server-connection-protocol.js +0 -0
- package/es/server/server-connection.d.ts +25 -0
- package/es/server/server-connection.d.ts.map +1 -0
- package/es/server/server-connection.js +159 -0
- package/es/server/server-manager.d.ts +22 -0
- package/es/server/server-manager.d.ts.map +1 -0
- package/es/server/server-manager.js +163 -0
- package/es/server/server-module.d.ts +3 -0
- package/es/server/server-module.d.ts.map +1 -0
- package/es/server/server-module.js +4 -0
- package/es/session/index.d.ts +5 -0
- package/es/session/index.d.ts.map +1 -0
- package/es/session/index.js +4 -0
- package/es/session/libro-session-manager.d.ts +71 -0
- package/es/session/libro-session-manager.d.ts.map +1 -0
- package/es/session/libro-session-manager.js +539 -0
- package/es/session/libro-session-protocol.d.ts +50 -0
- package/es/session/libro-session-protocol.d.ts.map +1 -0
- package/es/session/libro-session-protocol.js +21 -0
- package/es/session/libro-session.d.ts +12 -0
- package/es/session/libro-session.d.ts.map +1 -0
- package/es/session/libro-session.js +19 -0
- package/es/session/restapi.d.ts +28 -0
- package/es/session/restapi.d.ts.map +1 -0
- package/es/session/restapi.js +214 -0
- package/es/session/session-module.d.ts +3 -0
- package/es/session/session-module.d.ts.map +1 -0
- package/es/session/session-module.js +18 -0
- package/es/session/validate.d.ts +14 -0
- package/es/session/validate.d.ts.map +1 -0
- package/es/session/validate.js +37 -0
- package/es/utils.d.ts +4 -0
- package/es/utils.d.ts.map +1 -0
- package/es/utils.js +29 -0
- package/es/validate-property.d.ts +2 -0
- package/es/validate-property.d.ts.map +1 -0
- package/es/validate-property.js +35 -0
- package/package.json +62 -0
- package/src/basemanager.ts +133 -0
- package/src/contents/contents-drive.ts +495 -0
- package/src/contents/contents-manager.ts +465 -0
- package/src/contents/contents-module.ts +6 -0
- package/src/contents/contents-protocol.ts +604 -0
- package/src/contents/index.ts +5 -0
- package/src/contents/validate.ts +29 -0
- package/src/index.tsx +9 -0
- package/src/kernel/comm.ts +220 -0
- package/src/kernel/future.ts +474 -0
- package/src/kernel/index.ts +7 -0
- package/src/kernel/kernel-connection.ts +1770 -0
- package/src/kernel/kernel-module.ts +50 -0
- package/src/kernel/libro-kernel-manager.ts +199 -0
- package/src/kernel/libro-kernel-protocol.ts +858 -0
- package/src/kernel/libro-kernel-utils.ts +152 -0
- package/src/kernel/libro-kernel.ts +39 -0
- package/src/kernel/messages.ts +1104 -0
- package/src/kernel/restapi.ts +183 -0
- package/src/kernel/serialize.ts +262 -0
- package/src/kernel/validate.ts +101 -0
- package/src/kernelspec/index.ts +5 -0
- package/src/kernelspec/kernelspec-module.ts +9 -0
- package/src/kernelspec/kernelspec.ts +37 -0
- package/src/kernelspec/manager.ts +173 -0
- package/src/kernelspec/restapi.ts +104 -0
- package/src/kernelspec/validate.ts +80 -0
- package/src/libro-kernel-connection-manager.ts +73 -0
- package/src/module.ts +19 -0
- package/src/page-config.ts +106 -0
- package/src/protocol.ts +24 -0
- package/src/server/connection-error.ts +60 -0
- package/src/server/index.ts +5 -0
- package/src/server/server-connection-protocol.ts +57 -0
- package/src/server/server-connection.ts +144 -0
- package/src/server/server-manager.ts +76 -0
- package/src/server/server-module.ts +9 -0
- package/src/session/index.ts +4 -0
- package/src/session/libro-session-manager.ts +377 -0
- package/src/session/libro-session-protocol.ts +61 -0
- package/src/session/libro-session.ts +33 -0
- package/src/session/restapi.ts +126 -0
- package/src/session/session-module.ts +26 -0
- package/src/session/validate.ts +39 -0
- package/src/utils.ts +28 -0
- package/src/validate-property.ts +38 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Jupyter server settings object.
|
|
3
|
+
* Note that all of the settings are optional when passed to
|
|
4
|
+
* [[makeSettings]]. The default settings are given in [[defaultSettings]].
|
|
5
|
+
*/
|
|
6
|
+
export interface ISettings {
|
|
7
|
+
/**
|
|
8
|
+
* The base url of the server.
|
|
9
|
+
*/
|
|
10
|
+
readonly baseUrl: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The app url of the JupyterLab application.
|
|
14
|
+
*/
|
|
15
|
+
readonly appUrl: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The base ws url of the server.
|
|
19
|
+
*/
|
|
20
|
+
readonly wsUrl: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The default request init options.
|
|
24
|
+
*/
|
|
25
|
+
readonly init: RequestInit;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The authentication token for requests. Use an empty string to disable.
|
|
29
|
+
*/
|
|
30
|
+
readonly token: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether to append a token to a Websocket url. The default is `false` in the browser
|
|
34
|
+
* and `true` in node or jest.
|
|
35
|
+
*/
|
|
36
|
+
readonly appendToken: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The `fetch` method to use.
|
|
40
|
+
*/
|
|
41
|
+
readonly fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The `Request` object constructor.
|
|
45
|
+
*/
|
|
46
|
+
readonly Request: typeof Request;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The `Headers` object constructor.
|
|
50
|
+
*/
|
|
51
|
+
readonly Headers: typeof Headers;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The `WebSocket` object constructor.
|
|
55
|
+
*/
|
|
56
|
+
readonly WebSocket: typeof WebSocket;
|
|
57
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { URL } from '@difizen/libro-common';
|
|
2
|
+
import { isNative, isWeb, prop } from '@difizen/mana-app';
|
|
3
|
+
import { singleton } from '@difizen/mana-app';
|
|
4
|
+
|
|
5
|
+
import { PageConfig } from '../page-config.js';
|
|
6
|
+
|
|
7
|
+
import { NetworkError } from './connection-error.js';
|
|
8
|
+
import type { ISettings } from './server-connection-protocol.js';
|
|
9
|
+
|
|
10
|
+
let FETCH: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
11
|
+
let HEADERS: typeof Headers;
|
|
12
|
+
let REQUEST: typeof Request;
|
|
13
|
+
let WEBSOCKET: typeof WebSocket;
|
|
14
|
+
|
|
15
|
+
if (typeof window === 'undefined') {
|
|
16
|
+
// node environment
|
|
17
|
+
} else {
|
|
18
|
+
FETCH = fetch;
|
|
19
|
+
REQUEST = Request;
|
|
20
|
+
HEADERS = Headers;
|
|
21
|
+
WEBSOCKET = WebSocket;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@singleton()
|
|
25
|
+
export class ServerConnection {
|
|
26
|
+
@prop()
|
|
27
|
+
settings: ISettings;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.updateSettings({
|
|
31
|
+
baseUrl: 'http://localhost:8000/',
|
|
32
|
+
wsUrl: 'ws://localhost:8888/',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
updateSettings(options: Partial<ISettings> = {}): ISettings {
|
|
37
|
+
const pageBaseUrl = PageConfig.getBaseUrl();
|
|
38
|
+
const pageWsUrl = PageConfig.getWsUrl();
|
|
39
|
+
const baseUrl = URL.normalize(options.baseUrl) || pageBaseUrl;
|
|
40
|
+
let wsUrl = options.wsUrl;
|
|
41
|
+
// Prefer the default wsUrl if we are using the default baseUrl.
|
|
42
|
+
if (!wsUrl && baseUrl === pageBaseUrl) {
|
|
43
|
+
wsUrl = pageWsUrl;
|
|
44
|
+
}
|
|
45
|
+
// Otherwise convert the baseUrl to a wsUrl if possible.
|
|
46
|
+
if (!wsUrl && baseUrl.indexOf('http') === 0) {
|
|
47
|
+
wsUrl = 'ws' + baseUrl.slice(4);
|
|
48
|
+
}
|
|
49
|
+
// Otherwise fall back on the default wsUrl.
|
|
50
|
+
wsUrl = wsUrl ?? pageWsUrl;
|
|
51
|
+
|
|
52
|
+
this.settings = {
|
|
53
|
+
init: { cache: 'no-store', credentials: 'same-origin' },
|
|
54
|
+
fetch: FETCH,
|
|
55
|
+
Headers: HEADERS,
|
|
56
|
+
Request: REQUEST,
|
|
57
|
+
WebSocket: WEBSOCKET,
|
|
58
|
+
token: PageConfig.getToken(),
|
|
59
|
+
appUrl: PageConfig.getOption('appUrl'),
|
|
60
|
+
appendToken:
|
|
61
|
+
!isWeb ||
|
|
62
|
+
(isNative && process?.env?.['JEST_WORKER_ID'] !== undefined) ||
|
|
63
|
+
URL.getHostName(pageBaseUrl) !== URL.getHostName(wsUrl),
|
|
64
|
+
...options,
|
|
65
|
+
baseUrl,
|
|
66
|
+
wsUrl,
|
|
67
|
+
};
|
|
68
|
+
PageConfig.setOption('baseUrl', baseUrl);
|
|
69
|
+
return this.settings;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle a request.
|
|
74
|
+
*
|
|
75
|
+
* @param url - The url for the request.
|
|
76
|
+
*
|
|
77
|
+
* @param init - The overrides for the request init.
|
|
78
|
+
*
|
|
79
|
+
* @param settings - The settings object for the request.
|
|
80
|
+
*
|
|
81
|
+
* #### Notes
|
|
82
|
+
* The `url` must start with `settings.baseUrl`. The `init` settings
|
|
83
|
+
* take precedence over `settings.init`.
|
|
84
|
+
*/
|
|
85
|
+
makeRequest(
|
|
86
|
+
baseUrl: string,
|
|
87
|
+
init: RequestInit,
|
|
88
|
+
settings: ISettings = this.settings,
|
|
89
|
+
): Promise<Response> {
|
|
90
|
+
let url = baseUrl;
|
|
91
|
+
// Handle notebook server requests.
|
|
92
|
+
if (url.indexOf(settings.baseUrl) !== 0) {
|
|
93
|
+
throw new Error('Can only be used for notebook server requests');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use explicit cache buster when `no-store` is set since
|
|
97
|
+
// not all browsers use it properly.
|
|
98
|
+
const cache = init.cache ?? settings.init.cache;
|
|
99
|
+
if (cache === 'no-store') {
|
|
100
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
|
|
101
|
+
url += (/\?/.test(url) ? '&' : '?') + new Date().getTime();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const request = new settings.Request(url, { ...settings.init, ...init });
|
|
105
|
+
|
|
106
|
+
// Handle authentication. Authentication can be overdetermined by
|
|
107
|
+
// settings token and XSRF token.
|
|
108
|
+
let authenticated = false;
|
|
109
|
+
if (settings.token) {
|
|
110
|
+
authenticated = true;
|
|
111
|
+
request.headers.append('Authorization', `token ${settings.token}`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof document !== 'undefined' && document?.cookie) {
|
|
114
|
+
const xsrfToken = this.getCookie('_xsrf');
|
|
115
|
+
if (xsrfToken !== undefined) {
|
|
116
|
+
authenticated = true;
|
|
117
|
+
request.headers.append('X-XSRFToken', xsrfToken);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Set the content type if there is no given data and we are
|
|
122
|
+
// using an authenticated connection.
|
|
123
|
+
if (!request.headers.has('Content-Type') && authenticated) {
|
|
124
|
+
request.headers.set('Content-Type', 'application/json');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Use `call` to avoid a `TypeError` in the browser.
|
|
128
|
+
return settings.fetch.call(null, request).catch((e: TypeError) => {
|
|
129
|
+
// Convert the TypeError into a more specific error.
|
|
130
|
+
throw new NetworkError(e);
|
|
131
|
+
});
|
|
132
|
+
// TODO: *this* is probably where we need a system-wide connectionFailure
|
|
133
|
+
// signal we can hook into.
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get a cookie from the document.
|
|
138
|
+
*/
|
|
139
|
+
getCookie(name: string): string | undefined {
|
|
140
|
+
// From http://www.tornadoweb.org/en/stable/guide/security.html
|
|
141
|
+
const matches = document.cookie.match('\\b' + name + '=([^;]*)\\b');
|
|
142
|
+
return matches?.[1];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { prop } from '@difizen/mana-app';
|
|
2
|
+
import { inject, singleton } from '@difizen/mana-app';
|
|
3
|
+
import { timeout, Deferred } from '@difizen/mana-app';
|
|
4
|
+
|
|
5
|
+
import type { ISpecModels } from '../kernelspec/restapi.js';
|
|
6
|
+
import { KernelSpecRestAPI } from '../kernelspec/restapi.js';
|
|
7
|
+
|
|
8
|
+
@singleton()
|
|
9
|
+
export class ServerManager {
|
|
10
|
+
/**
|
|
11
|
+
* 加载中
|
|
12
|
+
*/
|
|
13
|
+
@prop()
|
|
14
|
+
launching?: boolean = true;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 加载成功
|
|
18
|
+
*/
|
|
19
|
+
@prop()
|
|
20
|
+
loaded?: boolean = false;
|
|
21
|
+
|
|
22
|
+
@prop()
|
|
23
|
+
kernelspec?: ISpecModels;
|
|
24
|
+
|
|
25
|
+
protected defer: Deferred<ISpecModels> = new Deferred<ISpecModels>();
|
|
26
|
+
|
|
27
|
+
get ready() {
|
|
28
|
+
return this.defer.promise;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected kernelSpecRestAPI: KernelSpecRestAPI;
|
|
32
|
+
|
|
33
|
+
constructor(@inject(KernelSpecRestAPI) kernelSpecRestAPI: KernelSpecRestAPI) {
|
|
34
|
+
this.kernelSpecRestAPI = kernelSpecRestAPI;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async launch() {
|
|
38
|
+
this.doLaunch()
|
|
39
|
+
.then((r) => {
|
|
40
|
+
this.defer.resolve(r);
|
|
41
|
+
return;
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {
|
|
44
|
+
//
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return this.defer.promise;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected async doLaunch(): Promise<ISpecModels> {
|
|
51
|
+
const kernelspec = await this.doGetServerStatus();
|
|
52
|
+
this.launching = true;
|
|
53
|
+
this.loaded = false;
|
|
54
|
+
if (!kernelspec) {
|
|
55
|
+
await timeout(1000);
|
|
56
|
+
return await this.doLaunch();
|
|
57
|
+
}
|
|
58
|
+
this.launching = false;
|
|
59
|
+
this.loaded = true;
|
|
60
|
+
this.kernelspec = kernelspec;
|
|
61
|
+
return kernelspec;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected async doGetServerStatus(): Promise<ISpecModels | undefined> {
|
|
65
|
+
try {
|
|
66
|
+
const r = await this.kernelSpecRestAPI.getSpecs();
|
|
67
|
+
if (r && r.kernelspecs && Object.keys(r.kernelspecs).length !== 0) {
|
|
68
|
+
return r;
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(error);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
2
|
+
|
|
3
|
+
import { ServerConnection } from './server-connection.js';
|
|
4
|
+
import { ServerManager } from './server-manager.js';
|
|
5
|
+
|
|
6
|
+
export const LibroServerModule = ManaModule.create().register(
|
|
7
|
+
ServerConnection,
|
|
8
|
+
ServerManager,
|
|
9
|
+
);
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { Poll } from '@difizen/libro-common';
|
|
2
|
+
import type { Event } from '@difizen/mana-app';
|
|
3
|
+
import { inject, singleton } from '@difizen/mana-app';
|
|
4
|
+
import { prop, getOrigin } from '@difizen/mana-app';
|
|
5
|
+
import { Emitter } from '@difizen/mana-app';
|
|
6
|
+
import { StorageService } from '@difizen/mana-app';
|
|
7
|
+
|
|
8
|
+
import type { IContentsModel } from '../contents/index.js';
|
|
9
|
+
import { LibroKernelManager } from '../kernel/libro-kernel-manager.js';
|
|
10
|
+
import { LibroKernelFactory } from '../kernel/libro-kernel-protocol.js';
|
|
11
|
+
import type {
|
|
12
|
+
KernelConnectionOptions,
|
|
13
|
+
IKernelModel,
|
|
14
|
+
IKernelConnection,
|
|
15
|
+
} from '../kernel/libro-kernel-protocol.js';
|
|
16
|
+
import type { ISettings } from '../server/index.js';
|
|
17
|
+
import { NetworkError } from '../server/index.js';
|
|
18
|
+
import { ServerManager } from '../server/server-manager.js';
|
|
19
|
+
|
|
20
|
+
import { LibroSessionFactory } from './libro-session-protocol.js';
|
|
21
|
+
import type {
|
|
22
|
+
SessionMeta,
|
|
23
|
+
ISessionOptions,
|
|
24
|
+
SessionIModel,
|
|
25
|
+
} from './libro-session-protocol.js';
|
|
26
|
+
import type { DeepPartial } from './restapi.js';
|
|
27
|
+
import { SessionRestAPI } from './restapi.js';
|
|
28
|
+
|
|
29
|
+
interface PersistSessionMessage {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
options: {
|
|
32
|
+
model: {
|
|
33
|
+
id: string; // kernel.id,
|
|
34
|
+
name: string; // kernel.name,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@singleton()
|
|
40
|
+
export class LibroSessionManager {
|
|
41
|
+
protected sessionFactory: LibroSessionFactory;
|
|
42
|
+
protected kernelFactory: LibroKernelFactory;
|
|
43
|
+
@inject(SessionRestAPI) sessionRestAPI: SessionRestAPI;
|
|
44
|
+
|
|
45
|
+
@prop()
|
|
46
|
+
sessionIdMap = new Map<string, string>();
|
|
47
|
+
|
|
48
|
+
readonly serverSettings: ISettings;
|
|
49
|
+
protected kernelManager: LibroKernelManager;
|
|
50
|
+
protected serverManager: ServerManager;
|
|
51
|
+
protected _pollModels: Poll;
|
|
52
|
+
protected _ready: Promise<void>;
|
|
53
|
+
protected _isReady = false;
|
|
54
|
+
protected storageService: StorageService;
|
|
55
|
+
protected _models: Map<string, SessionIModel>;
|
|
56
|
+
protected _sessionConnections: Map<string, IKernelConnection>; // sessionId -> kernelConnection
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
@inject(LibroKernelManager) kernelManager: LibroKernelManager,
|
|
60
|
+
@inject(ServerManager) serverManager: ServerManager,
|
|
61
|
+
@inject(LibroSessionFactory) sessionFactory: LibroSessionFactory,
|
|
62
|
+
@inject(LibroKernelFactory) kernelFactory: LibroKernelFactory,
|
|
63
|
+
@inject(StorageService) storageService: StorageService,
|
|
64
|
+
) {
|
|
65
|
+
this.kernelManager = kernelManager;
|
|
66
|
+
this.serverManager = serverManager;
|
|
67
|
+
this.sessionFactory = sessionFactory;
|
|
68
|
+
this.kernelFactory = kernelFactory;
|
|
69
|
+
this.storageService = storageService;
|
|
70
|
+
this._models = new Map();
|
|
71
|
+
this._sessionConnections = new Map();
|
|
72
|
+
|
|
73
|
+
// Start model polling with exponential backoff.
|
|
74
|
+
this._pollModels = new Poll({
|
|
75
|
+
auto: false,
|
|
76
|
+
factory: () => this.requestRunning(),
|
|
77
|
+
frequency: {
|
|
78
|
+
interval: 10 * 1000,
|
|
79
|
+
backoff: true,
|
|
80
|
+
max: 300 * 1000,
|
|
81
|
+
},
|
|
82
|
+
name: `@jupyterlab/services:SessionManager#models`,
|
|
83
|
+
// standby: options.standby ?? 'when-hidden',
|
|
84
|
+
standby: 'when-hidden',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Initialize internal data.
|
|
88
|
+
this._ready = (async () => {
|
|
89
|
+
await getOrigin(this._pollModels).start();
|
|
90
|
+
await getOrigin(this._pollModels).tick;
|
|
91
|
+
// if (this._kernelManager.isActive) {
|
|
92
|
+
// await this._kernelManager.ready;
|
|
93
|
+
// }
|
|
94
|
+
this._isReady = true;
|
|
95
|
+
})();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Execute a request to the server to poll running kernels and update state.
|
|
100
|
+
*/
|
|
101
|
+
protected async requestRunning(): Promise<void> {
|
|
102
|
+
let models: SessionMeta[];
|
|
103
|
+
try {
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
105
|
+
models = await this.sessionRestAPI.listRunning(this.serverSettings);
|
|
106
|
+
} catch (err: any) {
|
|
107
|
+
// Handle network errors, as well as cases where we are on a
|
|
108
|
+
// JupyterHub and the server is not running. JupyterHub returns a
|
|
109
|
+
// 503 (<2.0) or 424 (>2.0) in that case.
|
|
110
|
+
if (
|
|
111
|
+
err instanceof NetworkError ||
|
|
112
|
+
err.response?.status === 503 ||
|
|
113
|
+
err.response?.status === 424
|
|
114
|
+
) {
|
|
115
|
+
this._connectionFailure.fire(err);
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this._models = new Map(models.map((x) => [x.id, x]));
|
|
121
|
+
|
|
122
|
+
for (const [sessionId, kc] of this._sessionConnections) {
|
|
123
|
+
if (!this._models.has(sessionId)) {
|
|
124
|
+
let filePersistKey = '';
|
|
125
|
+
this.sessionIdMap.forEach((id, pk) => {
|
|
126
|
+
if (id === sessionId) {
|
|
127
|
+
filePersistKey = pk;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await this.storageService.setData(filePersistKey, undefined);
|
|
133
|
+
kc.dispose();
|
|
134
|
+
this.sessionIdMap.delete(filePersistKey);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected _connectionFailure = new Emitter<Error>();
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A signal emitted when there is a connection failure.
|
|
143
|
+
*/
|
|
144
|
+
get connectionFailure(): Event<Error> {
|
|
145
|
+
return this._connectionFailure.event;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@prop()
|
|
149
|
+
kernelConnection?: IKernelConnection;
|
|
150
|
+
|
|
151
|
+
protected persistKey(fileInfo: IContentsModel): string {
|
|
152
|
+
return `${fileInfo.path}/${fileInfo.name}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async shutdownKC(fileInfo: IContentsModel, kc: IKernelConnection) {
|
|
156
|
+
// 删除缓存
|
|
157
|
+
await this.storageService.setData(this.persistKey(fileInfo), undefined);
|
|
158
|
+
await kc.shutdown();
|
|
159
|
+
this.sessionIdMap.delete(this.persistKey(fileInfo));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async connectToKernel(
|
|
163
|
+
fileInfo: IContentsModel,
|
|
164
|
+
reuseKernelInfo?: { id: string; name: string },
|
|
165
|
+
): Promise<IKernelConnection | undefined> {
|
|
166
|
+
// 轮询获取的kernelspec,选取第一个kernelspec作为默认kernel
|
|
167
|
+
let firstKernelSpecName = undefined;
|
|
168
|
+
if (
|
|
169
|
+
this.serverManager.kernelspec &&
|
|
170
|
+
this.serverManager.kernelspec.kernelspecs &&
|
|
171
|
+
Object.keys(this.serverManager.kernelspec?.kernelspecs).length !== 0
|
|
172
|
+
) {
|
|
173
|
+
const kernelspec = Object.keys(this.serverManager.kernelspec.kernelspecs)[0];
|
|
174
|
+
firstKernelSpecName =
|
|
175
|
+
this.serverManager.kernelspec?.kernelspecs[kernelspec]?.name;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const newSession = await this.sessionRestAPI.startSession(
|
|
179
|
+
{
|
|
180
|
+
name: fileInfo.name,
|
|
181
|
+
kernel: {
|
|
182
|
+
kernelName: reuseKernelInfo
|
|
183
|
+
? reuseKernelInfo.name
|
|
184
|
+
: fileInfo.content.metadata.kernelspec?.name || firstKernelSpecName, // 使用ipynb文件原本的kernel name,或者使用kernel spec轮询得到的第一个kernel name
|
|
185
|
+
},
|
|
186
|
+
path: fileInfo.path,
|
|
187
|
+
type: fileInfo.type,
|
|
188
|
+
} as ISessionOptions,
|
|
189
|
+
this.serverSettings,
|
|
190
|
+
);
|
|
191
|
+
await this.refreshRunning();
|
|
192
|
+
|
|
193
|
+
if (!newSession || !newSession.kernel) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// // 根据session中的信息,新建kernel
|
|
198
|
+
// newSession.kernel = this.kernelFactory(newSession.kernel);
|
|
199
|
+
// newSession.kernel.sessionIds.add(newSession.id);
|
|
200
|
+
|
|
201
|
+
// 建立Kernel连接
|
|
202
|
+
const options = {
|
|
203
|
+
model: {
|
|
204
|
+
id: reuseKernelInfo ? reuseKernelInfo.id : newSession.kernel.id,
|
|
205
|
+
name: reuseKernelInfo ? reuseKernelInfo.name : newSession.kernel.name,
|
|
206
|
+
},
|
|
207
|
+
} as KernelConnectionOptions;
|
|
208
|
+
|
|
209
|
+
await this.storageService.setData(
|
|
210
|
+
this.persistKey(fileInfo),
|
|
211
|
+
JSON.stringify({
|
|
212
|
+
sessionId: newSession.id,
|
|
213
|
+
options: options,
|
|
214
|
+
} as PersistSessionMessage),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
this.sessionIdMap.set(this.persistKey(fileInfo), newSession.id);
|
|
218
|
+
|
|
219
|
+
const kernelConnection = await this.kernelManager.connectToKernel({
|
|
220
|
+
...options,
|
|
221
|
+
serverSettings: this.serverSettings,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this._sessionConnections.set(newSession.id, kernelConnection);
|
|
225
|
+
|
|
226
|
+
return kernelConnection;
|
|
227
|
+
// newSession.kernel.kernelConnection = kernelConnection;
|
|
228
|
+
|
|
229
|
+
// // 新建session
|
|
230
|
+
// const currLibroSession = this.sessionFactory(newSession as SessionMeta);
|
|
231
|
+
// // 保存session
|
|
232
|
+
// this.sessionMap.set(newSession.id, currLibroSession);
|
|
233
|
+
// // 保存kernel
|
|
234
|
+
// this.kernelManager.kernelMap.set(newSession.kernel.id, newSession.kernel);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async startNew(fileInfo: IContentsModel) {
|
|
238
|
+
// const fileContent = await sessionApi.queryFile(params.fileName, params.type);
|
|
239
|
+
|
|
240
|
+
// const { name, content, type, path } = fileInfo;
|
|
241
|
+
|
|
242
|
+
// await this.queryAndUpdateSessions();
|
|
243
|
+
|
|
244
|
+
const tryGetPersistSession: string | undefined = await this.storageService.getData(
|
|
245
|
+
this.persistKey(fileInfo),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
let kernelConnection = undefined;
|
|
249
|
+
|
|
250
|
+
if (!tryGetPersistSession) {
|
|
251
|
+
kernelConnection = await this.connectToKernel(fileInfo);
|
|
252
|
+
} else {
|
|
253
|
+
const { options, sessionId } = JSON.parse(
|
|
254
|
+
tryGetPersistSession,
|
|
255
|
+
) as PersistSessionMessage;
|
|
256
|
+
|
|
257
|
+
const kernelId = options.model.id;
|
|
258
|
+
const isAlive = await this.kernelManager.isKernelAlive(kernelId);
|
|
259
|
+
|
|
260
|
+
if (isAlive) {
|
|
261
|
+
// 尝试复用缓存中的kernel
|
|
262
|
+
kernelConnection = await this.kernelManager.connectToKernel({
|
|
263
|
+
...options,
|
|
264
|
+
serverSettings: this.serverSettings,
|
|
265
|
+
});
|
|
266
|
+
if (kernelConnection) {
|
|
267
|
+
this._sessionConnections.set(sessionId, kernelConnection);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
// TODO: 如果缓存中的kernel不能重用,则使用fileInfo建立新的kernel连接
|
|
271
|
+
await this.storageService.setData(this.persistKey(fileInfo), undefined);
|
|
272
|
+
kernelConnection = await this.connectToKernel(fileInfo);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.kernelConnection = kernelConnection;
|
|
277
|
+
return kernelConnection;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Force a refresh of the running sessions.
|
|
282
|
+
*
|
|
283
|
+
* @returns A promise that with the list of running sessions.
|
|
284
|
+
*
|
|
285
|
+
* #### Notes
|
|
286
|
+
* This is not typically meant to be called by the user, since the
|
|
287
|
+
* manager maintains its own internal state.
|
|
288
|
+
*/
|
|
289
|
+
async refreshRunning(): Promise<void> {
|
|
290
|
+
await getOrigin(this._pollModels).refresh();
|
|
291
|
+
await getOrigin(this._pollModels).tick;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Send a PATCH to the server, updating the session path or the kernel.
|
|
296
|
+
*/
|
|
297
|
+
private async _patch(
|
|
298
|
+
body: DeepPartial<SessionIModel>,
|
|
299
|
+
sessionId?: string,
|
|
300
|
+
): Promise<SessionIModel> {
|
|
301
|
+
// TODO: 复用session
|
|
302
|
+
const model = await this.sessionRestAPI.updateSession(
|
|
303
|
+
{ ...body, id: sessionId ?? '' },
|
|
304
|
+
this.serverSettings,
|
|
305
|
+
);
|
|
306
|
+
// this.update(model);
|
|
307
|
+
return model;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Change the kernel.
|
|
312
|
+
*
|
|
313
|
+
* @param options - The name or id of the new kernel.
|
|
314
|
+
*
|
|
315
|
+
* #### Notes
|
|
316
|
+
* This shuts down the existing kernel and creates a new kernel,
|
|
317
|
+
* keeping the existing session ID and session path.
|
|
318
|
+
*/
|
|
319
|
+
async changeKernel(
|
|
320
|
+
fileInfo: IContentsModel,
|
|
321
|
+
options: Partial<IKernelModel>,
|
|
322
|
+
): Promise<IKernelConnection | null> {
|
|
323
|
+
let reuseSessionId = this.sessionIdMap.get(this.persistKey(fileInfo));
|
|
324
|
+
|
|
325
|
+
// shutdown 过后
|
|
326
|
+
if (!reuseSessionId) {
|
|
327
|
+
const newSession = await this.sessionRestAPI.startSession(
|
|
328
|
+
{
|
|
329
|
+
name: fileInfo.name,
|
|
330
|
+
kernel: {
|
|
331
|
+
kernelName: options.name || fileInfo.content.metadata.kernelspec.name,
|
|
332
|
+
},
|
|
333
|
+
path: fileInfo.path,
|
|
334
|
+
type: fileInfo.type,
|
|
335
|
+
} as ISessionOptions,
|
|
336
|
+
this.serverSettings,
|
|
337
|
+
);
|
|
338
|
+
await this.refreshRunning();
|
|
339
|
+
|
|
340
|
+
if (!newSession || !newSession.kernel) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
reuseSessionId = newSession.id;
|
|
344
|
+
this.sessionIdMap.set(this.persistKey(fileInfo), reuseSessionId!);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const model = await this._patch({ kernel: options } as any, reuseSessionId);
|
|
348
|
+
|
|
349
|
+
if (!model || !model.kernel) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const optionsForConnectKernel = {
|
|
354
|
+
model: {
|
|
355
|
+
id: model.kernel.id,
|
|
356
|
+
name: model.kernel.name,
|
|
357
|
+
},
|
|
358
|
+
} as KernelConnectionOptions;
|
|
359
|
+
|
|
360
|
+
await this.storageService.setData(
|
|
361
|
+
this.persistKey(fileInfo),
|
|
362
|
+
JSON.stringify({
|
|
363
|
+
sessionId: reuseSessionId,
|
|
364
|
+
options: optionsForConnectKernel,
|
|
365
|
+
} as PersistSessionMessage),
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
const kernelConnection = await this.kernelManager.connectToKernel({
|
|
369
|
+
...optionsForConnectKernel,
|
|
370
|
+
serverSettings: this.serverSettings,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
this._sessionConnections.set(reuseSessionId!, kernelConnection);
|
|
374
|
+
|
|
375
|
+
return kernelConnection;
|
|
376
|
+
}
|
|
377
|
+
}
|