@jupyter/docprovider 1.0.0-alpha.1 → 1.0.0-alpha.3
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/requests.d.ts +8 -0
- package/lib/requests.js +25 -0
- package/lib/ydrive.d.ts +25 -2
- package/lib/ydrive.js +16 -7
- package/lib/yprovider.d.ts +10 -2
- package/lib/yprovider.js +36 -30
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Contents } from '@jupyterlab/services';
|
|
2
|
+
export interface ISessionModel {
|
|
3
|
+
format: Contents.FileFormat;
|
|
4
|
+
type: Contents.ContentType;
|
|
5
|
+
fileId: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function requestDocSession(format: string, type: string, path: string): Promise<ISessionModel>;
|
package/lib/requests.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* -----------------------------------------------------------------------------
|
|
2
|
+
| Copyright (c) Jupyter Development Team.
|
|
3
|
+
| Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|----------------------------------------------------------------------------*/
|
|
5
|
+
import { URLExt } from '@jupyterlab/coreutils';
|
|
6
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
7
|
+
/**
|
|
8
|
+
* Document session endpoint provided by `jupyter_collaboration`
|
|
9
|
+
* See https://github.com/jupyterlab/jupyter_collaboration
|
|
10
|
+
*/
|
|
11
|
+
const DOC_SESSION_URL = 'api/collaboration/session';
|
|
12
|
+
export async function requestDocSession(format, type, path) {
|
|
13
|
+
const { makeSettings, makeRequest, ResponseError } = ServerConnection;
|
|
14
|
+
const settings = makeSettings();
|
|
15
|
+
const url = URLExt.join(settings.baseUrl, DOC_SESSION_URL, encodeURIComponent(path));
|
|
16
|
+
const data = {
|
|
17
|
+
method: 'PUT',
|
|
18
|
+
body: JSON.stringify({ format, type })
|
|
19
|
+
};
|
|
20
|
+
const response = await makeRequest(url, data, settings);
|
|
21
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
22
|
+
throw new ResponseError(response);
|
|
23
|
+
}
|
|
24
|
+
return response.json();
|
|
25
|
+
}
|
package/lib/ydrive.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DocumentChange, ISharedDocument, YDocument } from '@jupyter/ydoc';
|
|
2
|
+
import { TranslationBundle } from '@jupyterlab/translation';
|
|
1
3
|
import { Contents, Drive, User } from '@jupyterlab/services';
|
|
2
4
|
/**
|
|
3
5
|
* A Collaborative implementation for an `IDrive`, talking to the
|
|
@@ -9,11 +11,11 @@ export declare class YDrive extends Drive {
|
|
|
9
11
|
*
|
|
10
12
|
* @param user - The user manager to add the identity to the awareness of documents.
|
|
11
13
|
*/
|
|
12
|
-
constructor(user: User.IManager);
|
|
14
|
+
constructor(user: User.IManager, translator: TranslationBundle);
|
|
13
15
|
/**
|
|
14
16
|
* SharedModel factory for the YDrive.
|
|
15
17
|
*/
|
|
16
|
-
readonly sharedModelFactory:
|
|
18
|
+
readonly sharedModelFactory: SharedModelFactory;
|
|
17
19
|
/**
|
|
18
20
|
* Delete a file.
|
|
19
21
|
*
|
|
@@ -73,5 +75,26 @@ export declare class YDrive extends Drive {
|
|
|
73
75
|
save(localPath: string, options?: Partial<Contents.IModel>): Promise<Contents.IModel>;
|
|
74
76
|
private _onCreate;
|
|
75
77
|
private _user;
|
|
78
|
+
private _trans;
|
|
76
79
|
private _providers;
|
|
77
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Yjs sharedModel factory for real-time collaboration.
|
|
83
|
+
*/
|
|
84
|
+
declare class SharedModelFactory implements Contents.ISharedFactory {
|
|
85
|
+
private _onCreate;
|
|
86
|
+
private _documentOptions;
|
|
87
|
+
constructor(_onCreate: (options: Contents.ISharedFactoryOptions, sharedModel: YDocument<DocumentChange>) => void);
|
|
88
|
+
/**
|
|
89
|
+
* Whether the IDrive supports real-time collaboration or not.
|
|
90
|
+
*/
|
|
91
|
+
readonly collaborative = true;
|
|
92
|
+
setDocumentOptions(type: Contents.ContentType, value: Record<string, any>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Create a new `ISharedDocument` instance.
|
|
95
|
+
*
|
|
96
|
+
* It should return `undefined` if the factory is not able to create a `ISharedDocument`.
|
|
97
|
+
*/
|
|
98
|
+
createNew(options: Contents.ISharedFactoryOptions): ISharedDocument | undefined;
|
|
99
|
+
}
|
|
100
|
+
export {};
|
package/lib/ydrive.js
CHANGED
|
@@ -7,7 +7,7 @@ import { WebSocketProvider } from './yprovider';
|
|
|
7
7
|
/**
|
|
8
8
|
* The url for the default drive service.
|
|
9
9
|
*/
|
|
10
|
-
const
|
|
10
|
+
const DOCUMENT_PROVIDER_URL = 'api/collaboration/room';
|
|
11
11
|
/**
|
|
12
12
|
* A Collaborative implementation for an `IDrive`, talking to the
|
|
13
13
|
* server using the Jupyter REST API and a WebSocket connection.
|
|
@@ -18,7 +18,7 @@ export class YDrive extends Drive {
|
|
|
18
18
|
*
|
|
19
19
|
* @param user - The user manager to add the identity to the awareness of documents.
|
|
20
20
|
*/
|
|
21
|
-
constructor(user) {
|
|
21
|
+
constructor(user, translator) {
|
|
22
22
|
super({ name: 'YDrive' });
|
|
23
23
|
this._onCreate = (options, sharedModel) => {
|
|
24
24
|
if (typeof options.format !== 'string') {
|
|
@@ -26,14 +26,15 @@ export class YDrive extends Drive {
|
|
|
26
26
|
}
|
|
27
27
|
try {
|
|
28
28
|
const provider = new WebSocketProvider({
|
|
29
|
-
url: URLExt.join(this.serverSettings.wsUrl,
|
|
29
|
+
url: URLExt.join(this.serverSettings.wsUrl, DOCUMENT_PROVIDER_URL),
|
|
30
30
|
path: options.path,
|
|
31
31
|
format: options.format,
|
|
32
32
|
contentType: options.contentType,
|
|
33
33
|
model: sharedModel,
|
|
34
|
-
user: this._user
|
|
34
|
+
user: this._user,
|
|
35
|
+
translator: this._trans
|
|
35
36
|
});
|
|
36
|
-
const key = `${options.
|
|
37
|
+
const key = `${options.format}:${options.contentType}:${options.path}`;
|
|
37
38
|
this._providers.set(key, provider);
|
|
38
39
|
sharedModel.disposed.connect(() => {
|
|
39
40
|
const provider = this._providers.get(key);
|
|
@@ -50,6 +51,7 @@ export class YDrive extends Drive {
|
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
this._user = user;
|
|
54
|
+
this._trans = translator;
|
|
53
55
|
this._providers = new Map();
|
|
54
56
|
this.sharedModelFactory = new SharedModelFactory(this._onCreate);
|
|
55
57
|
}
|
|
@@ -93,7 +95,7 @@ export class YDrive extends Drive {
|
|
|
93
95
|
*/
|
|
94
96
|
async get(localPath, options) {
|
|
95
97
|
if (options && options.format && options.type) {
|
|
96
|
-
const key = `${options.
|
|
98
|
+
const key = `${options.format}:${options.type}:${localPath}`;
|
|
97
99
|
const provider = this._providers.get(key);
|
|
98
100
|
if (provider) {
|
|
99
101
|
const model = super.get(localPath, { ...options, content: false });
|
|
@@ -149,6 +151,13 @@ class SharedModelFactory {
|
|
|
149
151
|
* Whether the IDrive supports real-time collaboration or not.
|
|
150
152
|
*/
|
|
151
153
|
this.collaborative = true;
|
|
154
|
+
this._documentOptions = new Map();
|
|
155
|
+
this._documentOptions.set('notebook', {
|
|
156
|
+
disableDocumentWideUndoRedo: true
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
setDocumentOptions(type, value) {
|
|
160
|
+
this._documentOptions.set(type, value);
|
|
152
161
|
}
|
|
153
162
|
/**
|
|
154
163
|
* Create a new `ISharedDocument` instance.
|
|
@@ -169,7 +178,7 @@ class SharedModelFactory {
|
|
|
169
178
|
sharedModel = new YFile();
|
|
170
179
|
break;
|
|
171
180
|
case 'notebook':
|
|
172
|
-
sharedModel = new YNotebook();
|
|
181
|
+
sharedModel = new YNotebook(this._documentOptions.get('notebook'));
|
|
173
182
|
break;
|
|
174
183
|
//default:
|
|
175
184
|
// FIXME we should request a registry for the proper sharedModel
|
package/lib/yprovider.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { User } from '@jupyterlab/services';
|
|
2
|
-
import {
|
|
2
|
+
import { TranslationBundle } from '@jupyterlab/translation';
|
|
3
3
|
import { IDisposable } from '@lumino/disposable';
|
|
4
|
+
import { DocumentChange, YDocument } from '@jupyter/ydoc';
|
|
4
5
|
/**
|
|
5
6
|
* An interface for a document provider.
|
|
6
7
|
*/
|
|
@@ -35,7 +36,9 @@ export declare class WebSocketProvider implements IDocumentProvider {
|
|
|
35
36
|
* Dispose of the resources held by the object.
|
|
36
37
|
*/
|
|
37
38
|
dispose(): void;
|
|
39
|
+
private _connect;
|
|
38
40
|
private _onUserChanged;
|
|
41
|
+
private _onConnectionClosed;
|
|
39
42
|
private _awareness;
|
|
40
43
|
private _contentType;
|
|
41
44
|
private _format;
|
|
@@ -43,8 +46,9 @@ export declare class WebSocketProvider implements IDocumentProvider {
|
|
|
43
46
|
private _path;
|
|
44
47
|
private _ready;
|
|
45
48
|
private _serverUrl;
|
|
46
|
-
private
|
|
49
|
+
private _sharedModel;
|
|
47
50
|
private _yWebsocketProvider;
|
|
51
|
+
private _trans;
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* A namespace for WebSocketProvider statics.
|
|
@@ -78,5 +82,9 @@ export declare namespace WebSocketProvider {
|
|
|
78
82
|
* The user data
|
|
79
83
|
*/
|
|
80
84
|
user: User.IManager;
|
|
85
|
+
/**
|
|
86
|
+
* The jupyterlab translator
|
|
87
|
+
*/
|
|
88
|
+
translator: TranslationBundle;
|
|
81
89
|
}
|
|
82
90
|
}
|
package/lib/yprovider.js
CHANGED
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
| Copyright (c) Jupyter Development Team.
|
|
3
3
|
| Distributed under the terms of the Modified BSD License.
|
|
4
4
|
|----------------------------------------------------------------------------*/
|
|
5
|
-
import {
|
|
6
|
-
import { ServerConnection } from '@jupyterlab/services';
|
|
5
|
+
import { showErrorMessage, Dialog } from '@jupyterlab/apputils';
|
|
7
6
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
8
7
|
import { Signal } from '@lumino/signaling';
|
|
9
8
|
import { WebsocketProvider as YWebsocketProvider } from 'y-websocket';
|
|
10
|
-
|
|
11
|
-
* Room Id endpoint provided by `jupyter_collaboration`
|
|
12
|
-
* See https://github.com/jupyterlab/jupyter_collaboration
|
|
13
|
-
*/
|
|
14
|
-
const FILE_PATH_TO_ROOM_ID_URL = 'api/yjs/roomid';
|
|
9
|
+
import { requestDocSession } from './requests';
|
|
15
10
|
/**
|
|
16
11
|
* A class to provide Yjs synchronization over WebSocket.
|
|
17
12
|
*
|
|
@@ -25,15 +20,31 @@ export class WebSocketProvider {
|
|
|
25
20
|
* @param options The instantiation options for a WebSocketProvider
|
|
26
21
|
*/
|
|
27
22
|
constructor(options) {
|
|
23
|
+
this._onConnectionClosed = (event) => {
|
|
24
|
+
if (event.code === 1003) {
|
|
25
|
+
console.error('Document provider closed:', event.reason);
|
|
26
|
+
showErrorMessage(this._trans.__('Session expired'), this._trans.__('The document session expired. You need to reload this browser tab.'), [Dialog.okButton({ label: this._trans.__('Reload') })])
|
|
27
|
+
.then((r) => {
|
|
28
|
+
if (r.button.accept) {
|
|
29
|
+
window.location.reload();
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.catch(e => window.location.reload());
|
|
33
|
+
// Dispose shared model immediately. Better break the document model,
|
|
34
|
+
// than overriding data on disk.
|
|
35
|
+
this._sharedModel.dispose();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
28
38
|
this._ready = new PromiseDelegate();
|
|
29
39
|
this._isDisposed = false;
|
|
30
40
|
this._path = options.path;
|
|
31
41
|
this._contentType = options.contentType;
|
|
32
42
|
this._format = options.format;
|
|
33
43
|
this._serverUrl = options.url;
|
|
34
|
-
this.
|
|
44
|
+
this._sharedModel = options.model;
|
|
35
45
|
this._awareness = options.model.awareness;
|
|
36
46
|
this._yWebsocketProvider = null;
|
|
47
|
+
this._trans = options.translator;
|
|
37
48
|
const user = options.user;
|
|
38
49
|
user.ready
|
|
39
50
|
.then(() => {
|
|
@@ -41,26 +52,7 @@ export class WebSocketProvider {
|
|
|
41
52
|
})
|
|
42
53
|
.catch(e => console.error(e));
|
|
43
54
|
user.userChanged.connect(this._onUserChanged, this);
|
|
44
|
-
|
|
45
|
-
const url = URLExt.join(serverSettings.baseUrl, FILE_PATH_TO_ROOM_ID_URL, encodeURIComponent(this._path));
|
|
46
|
-
const data = {
|
|
47
|
-
method: 'PUT',
|
|
48
|
-
body: JSON.stringify({ format: this._format, type: this._contentType })
|
|
49
|
-
};
|
|
50
|
-
ServerConnection.makeRequest(url, data, serverSettings)
|
|
51
|
-
.then(response => {
|
|
52
|
-
if (response.status !== 200 && response.status !== 201) {
|
|
53
|
-
throw new ServerConnection.ResponseError(response);
|
|
54
|
-
}
|
|
55
|
-
return response.text();
|
|
56
|
-
})
|
|
57
|
-
.then(roomid => {
|
|
58
|
-
this._yWebsocketProvider = new YWebsocketProvider(this._serverUrl, roomid, this._ydoc, {
|
|
59
|
-
awareness: this._awareness
|
|
60
|
-
});
|
|
61
|
-
})
|
|
62
|
-
.then(() => this._ready.resolve())
|
|
63
|
-
.catch(reason => console.warn(reason));
|
|
55
|
+
this._connect();
|
|
64
56
|
}
|
|
65
57
|
/**
|
|
66
58
|
* Test whether the object has been disposed.
|
|
@@ -78,14 +70,28 @@ export class WebSocketProvider {
|
|
|
78
70
|
* Dispose of the resources held by the object.
|
|
79
71
|
*/
|
|
80
72
|
dispose() {
|
|
81
|
-
var _a;
|
|
73
|
+
var _a, _b;
|
|
82
74
|
if (this.isDisposed) {
|
|
83
75
|
return;
|
|
84
76
|
}
|
|
85
77
|
this._isDisposed = true;
|
|
86
|
-
(_a = this._yWebsocketProvider) === null || _a === void 0 ? void 0 : _a.
|
|
78
|
+
(_a = this._yWebsocketProvider) === null || _a === void 0 ? void 0 : _a.off('connection-close', this._onConnectionClosed);
|
|
79
|
+
(_b = this._yWebsocketProvider) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
87
80
|
Signal.clearData(this);
|
|
88
81
|
}
|
|
82
|
+
_connect() {
|
|
83
|
+
requestDocSession(this._format, this._contentType, this._path)
|
|
84
|
+
.then((session) => {
|
|
85
|
+
this._yWebsocketProvider = new YWebsocketProvider(this._serverUrl, `${session.format}:${session.type}:${session.fileId}`, this._sharedModel.ydoc, {
|
|
86
|
+
disableBc: true,
|
|
87
|
+
params: { sessionId: session.sessionId },
|
|
88
|
+
awareness: this._awareness
|
|
89
|
+
});
|
|
90
|
+
this._yWebsocketProvider.on('connection-close', this._onConnectionClosed);
|
|
91
|
+
})
|
|
92
|
+
.then(r => this._ready.resolve())
|
|
93
|
+
.catch(e => console.warn(e));
|
|
94
|
+
}
|
|
89
95
|
_onUserChanged(user) {
|
|
90
96
|
this._awareness.setLocalStateField('user', user.identity);
|
|
91
97
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyter/docprovider",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"description": "JupyterLab - Document Provider",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyter_collaboration",
|
|
6
6
|
"bugs": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"watch": "tsc -b --watch"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@jupyter/ydoc": "^0.3.
|
|
44
|
+
"@jupyter/ydoc": "^0.3.4",
|
|
45
45
|
"@jupyterlab/coreutils": "^6.0.0-alpha.19",
|
|
46
46
|
"@jupyterlab/services": "^7.0.0-alpha.19",
|
|
47
47
|
"@lumino/coreutils": "^2.0.0-beta.0",
|