@jupyter/docprovider 1.2.0 → 2.0.0-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.
@@ -1,14 +1,17 @@
1
1
  import { User } from '@jupyterlab/services';
2
2
  import { IDisposable } from '@lumino/disposable';
3
- import { ISignal } from '@lumino/signaling';
3
+ import { IStream } from '@lumino/signaling';
4
+ import { IAwareness } from '@jupyter/ydoc';
4
5
  import { WebsocketProvider } from 'y-websocket';
5
- import { IAwareness, IAwarenessProvider } from './tokens';
6
- export declare enum MessageType {
7
- CHAT = 125
6
+ import { IAwarenessProvider } from './tokens';
7
+ export interface IContent {
8
+ type: string;
9
+ body: string;
8
10
  }
9
11
  export interface IChatMessage {
10
- username: string;
11
- msg: string;
12
+ sender: string;
13
+ timestamp: number;
14
+ content: IContent;
12
15
  }
13
16
  /**
14
17
  * A class to provide Yjs synchronization over WebSocket.
@@ -27,7 +30,7 @@ export declare class WebSocketAwarenessProvider extends WebsocketProvider implem
27
30
  /**
28
31
  * A signal to subscribe for incoming messages.
29
32
  */
30
- get chatMessage(): ISignal<this, IChatMessage>;
33
+ get messageStream(): IStream<this, IChatMessage>;
31
34
  dispose(): void;
32
35
  /**
33
36
  * Send a message to every collaborator.
@@ -37,8 +40,9 @@ export declare class WebSocketAwarenessProvider extends WebsocketProvider implem
37
40
  sendMessage(msg: string): void;
38
41
  private _onUserChanged;
39
42
  private _isDisposed;
43
+ private _user;
40
44
  private _awareness;
41
- private _chatMessage;
45
+ private _messageStream;
42
46
  }
43
47
  /**
44
48
  * A namespace for WebSocketAwarenessProvider statics.
package/lib/awareness.js CHANGED
@@ -2,14 +2,11 @@
2
2
  | Copyright (c) Jupyter Development Team.
3
3
  | Distributed under the terms of the Modified BSD License.
4
4
  |----------------------------------------------------------------------------*/
5
- import { Signal } from '@lumino/signaling';
5
+ import { Stream } from '@lumino/signaling';
6
6
  import * as decoding from 'lib0/decoding';
7
7
  import * as encoding from 'lib0/encoding';
8
8
  import { WebsocketProvider } from 'y-websocket';
9
- export var MessageType;
10
- (function (MessageType) {
11
- MessageType[MessageType["CHAT"] = 125] = "CHAT";
12
- })(MessageType || (MessageType = {}));
9
+ import { MessageType } from './utils';
13
10
  /**
14
11
  * A class to provide Yjs synchronization over WebSocket.
15
12
  *
@@ -28,17 +25,16 @@ export class WebSocketAwarenessProvider extends WebsocketProvider {
28
25
  });
29
26
  this._isDisposed = false;
30
27
  this._awareness = options.awareness;
31
- const user = options.user;
32
- user.ready
33
- .then(() => this._onUserChanged(user))
28
+ this._user = options.user;
29
+ this._user.ready
30
+ .then(() => this._onUserChanged(this._user))
34
31
  .catch(e => console.error(e));
35
- user.userChanged.connect(this._onUserChanged, this);
36
- this._chatMessage = new Signal(this);
32
+ this._user.userChanged.connect(this._onUserChanged, this);
33
+ this._messageStream = new Stream(this);
37
34
  this.messageHandlers[MessageType.CHAT] = (encoder, decoder, provider, emitSynced, messageType) => {
38
35
  const content = decoding.readVarString(decoder);
39
36
  const data = JSON.parse(content);
40
- console.debug('Chat:', data);
41
- this._chatMessage.emit(data);
37
+ this._messageStream.emit(data);
42
38
  };
43
39
  }
44
40
  get isDisposed() {
@@ -47,15 +43,16 @@ export class WebSocketAwarenessProvider extends WebsocketProvider {
47
43
  /**
48
44
  * A signal to subscribe for incoming messages.
49
45
  */
50
- get chatMessage() {
51
- return this._chatMessage;
46
+ get messageStream() {
47
+ return this._messageStream;
52
48
  }
53
49
  dispose() {
54
50
  if (this._isDisposed) {
55
51
  return;
56
52
  }
57
- this.destroy();
53
+ this._user.userChanged.disconnect(this._onUserChanged, this);
58
54
  this._isDisposed = true;
55
+ this.destroy();
59
56
  }
60
57
  /**
61
58
  * Send a message to every collaborator.
@@ -63,10 +60,13 @@ export class WebSocketAwarenessProvider extends WebsocketProvider {
63
60
  * @param msg message
64
61
  */
65
62
  sendMessage(msg) {
66
- console.debug('Send message:', msg);
63
+ const data = {
64
+ type: 'text',
65
+ body: msg
66
+ };
67
67
  const encoder = encoding.createEncoder();
68
68
  encoding.writeVarUint(encoder, MessageType.CHAT);
69
- encoding.writeVarString(encoder, msg);
69
+ encoding.writeVarString(encoder, JSON.stringify(data));
70
70
  this.ws.send(encoding.toUint8Array(encoder));
71
71
  }
72
72
  _onUserChanged(user) {
package/lib/tokens.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { DocumentChange, YDocument } from '@jupyter/ydoc';
2
2
  import { Contents } from '@jupyterlab/services';
3
3
  import { Token } from '@lumino/coreutils';
4
- import { ISignal } from '@lumino/signaling';
5
- import type { Awareness } from 'y-protocols/awareness';
4
+ import { IStream } from '@lumino/signaling';
6
5
  import { IChatMessage } from './awareness';
7
6
  /**
8
7
  * The collaborative drive.
@@ -34,12 +33,6 @@ export interface ISharedModelFactory extends Contents.ISharedFactory {
34
33
  */
35
34
  registerDocumentFactory(type: Contents.ContentType, factory: SharedDocumentFactory): void;
36
35
  }
37
- /**
38
- * The awareness interface.
39
- *
40
- * TODO: Move to @jupyter/YDoc
41
- */
42
- export type IAwareness = Awareness;
43
36
  /**
44
37
  * A provider interface for global awareness features.
45
38
  */
@@ -47,7 +40,7 @@ export interface IAwarenessProvider {
47
40
  /**
48
41
  * A signal to subscribe for incoming messages.
49
42
  */
50
- get chatMessage(): ISignal<this, IChatMessage>;
43
+ readonly messageStream: IStream<this, IChatMessage>;
51
44
  /**
52
45
  * Send a message to every collaborator.
53
46
  *
package/lib/utils.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare enum MessageType {
2
+ ROOM = 124,
3
+ CHAT = 125
4
+ }
5
+ export declare enum RoomMessage {
6
+ RELOAD = 0,
7
+ OVERWRITE = 1,
8
+ FILE_CHANGED = 2,
9
+ FILE_OVERWRITTEN = 3,
10
+ DOC_OVERWRITTEN = 4
11
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,17 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+ export var MessageType;
6
+ (function (MessageType) {
7
+ MessageType[MessageType["ROOM"] = 124] = "ROOM";
8
+ MessageType[MessageType["CHAT"] = 125] = "CHAT";
9
+ })(MessageType || (MessageType = {}));
10
+ export var RoomMessage;
11
+ (function (RoomMessage) {
12
+ RoomMessage[RoomMessage["RELOAD"] = 0] = "RELOAD";
13
+ RoomMessage[RoomMessage["OVERWRITE"] = 1] = "OVERWRITE";
14
+ RoomMessage[RoomMessage["FILE_CHANGED"] = 2] = "FILE_CHANGED";
15
+ RoomMessage[RoomMessage["FILE_OVERWRITTEN"] = 3] = "FILE_OVERWRITTEN";
16
+ RoomMessage[RoomMessage["DOC_OVERWRITTEN"] = 4] = "DOC_OVERWRITTEN";
17
+ })(RoomMessage || (RoomMessage = {}));
package/lib/ydrive.js CHANGED
@@ -82,8 +82,14 @@ export class YDrive extends Drive {
82
82
  const key = `${options.format}:${options.type}:${localPath}`;
83
83
  const provider = this._providers.get(key);
84
84
  if (provider) {
85
- const model = super.get(localPath, { ...options, content: false });
86
- await provider.ready;
85
+ // If the document does't exist, `super.get` will reject with an
86
+ // error and the provider will never be resolved.
87
+ // Use `Promise.all` to reject as soon as possible. The Context will
88
+ // show a dialog to the user.
89
+ const [model] = await Promise.all([
90
+ super.get(localPath, { ...options, content: false }),
91
+ provider.ready
92
+ ]);
87
93
  return model;
88
94
  }
89
95
  }
@@ -40,6 +40,11 @@ export declare class WebSocketProvider implements IDocumentProvider {
40
40
  private _onUserChanged;
41
41
  private _onConnectionClosed;
42
42
  private _onSync;
43
+ private _handleRoomMessage;
44
+ private _handleFileChanged;
45
+ private _sendReloadMsg;
46
+ private _sendOverwriteMsg;
47
+ private _dialog;
43
48
  private _awareness;
44
49
  private _contentType;
45
50
  private _format;
package/lib/yprovider.js CHANGED
@@ -5,8 +5,11 @@
5
5
  import { showErrorMessage, Dialog } from '@jupyterlab/apputils';
6
6
  import { PromiseDelegate } from '@lumino/coreutils';
7
7
  import { Signal } from '@lumino/signaling';
8
+ import * as decoding from 'lib0/decoding';
9
+ import * as encoding from 'lib0/encoding';
8
10
  import { WebsocketProvider as YWebsocketProvider } from 'y-websocket';
9
11
  import { requestDocSession } from './requests';
12
+ import { MessageType, RoomMessage } from './utils';
10
13
  /**
11
14
  * A class to provide Yjs synchronization over WebSocket.
12
15
  *
@@ -38,6 +41,7 @@ export class WebSocketProvider {
38
41
  (_a = this._yWebsocketProvider) === null || _a === void 0 ? void 0 : _a.off('sync', this._onSync);
39
42
  }
40
43
  };
44
+ this._dialog = null;
41
45
  this._ready = new PromiseDelegate();
42
46
  this._isDisposed = false;
43
47
  this._path = options.path;
@@ -85,15 +89,70 @@ export class WebSocketProvider {
85
89
  }
86
90
  async _connect() {
87
91
  const session = await requestDocSession(this._format, this._contentType, this._path);
92
+ const params = session.sessionId !== null ? { sessionId: session.sessionId } : undefined;
88
93
  this._yWebsocketProvider = new YWebsocketProvider(this._serverUrl, `${session.format}:${session.type}:${session.fileId}`, this._sharedModel.ydoc, {
89
94
  disableBc: true,
90
- params: { sessionId: session.sessionId },
95
+ params,
91
96
  awareness: this._awareness
92
97
  });
93
98
  this._yWebsocketProvider.on('sync', this._onSync);
94
99
  this._yWebsocketProvider.on('connection-close', this._onConnectionClosed);
100
+ this._yWebsocketProvider.messageHandlers[MessageType.ROOM] = (encoder, decoder, provider, emitSynced, messageType) => {
101
+ const msgType = decoding.readVarUint(decoder);
102
+ const data = decoding.readVarString(decoder);
103
+ this._handleRoomMessage(msgType, data);
104
+ };
95
105
  }
96
106
  _onUserChanged(user) {
97
107
  this._awareness.setLocalStateField('user', user.identity);
98
108
  }
109
+ _handleRoomMessage(type, data) {
110
+ switch (type) {
111
+ case RoomMessage.FILE_CHANGED:
112
+ this._handleFileChanged(data);
113
+ break;
114
+ case RoomMessage.DOC_OVERWRITTEN:
115
+ case RoomMessage.FILE_OVERWRITTEN:
116
+ if (this._dialog) {
117
+ this._dialog.close();
118
+ this._dialog = null;
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ _handleFileChanged(data) {
124
+ this._dialog = new Dialog({
125
+ title: this._trans.__('File changed'),
126
+ body: this._trans.__('Do you want to overwrite the file or reload it?'),
127
+ buttons: [
128
+ Dialog.okButton({ label: 'Reload' }),
129
+ Dialog.warnButton({ label: 'Overwrite' })
130
+ ],
131
+ hasClose: false
132
+ });
133
+ this._dialog.launch().then(resp => {
134
+ if (resp.button.label === 'Reload') {
135
+ this._sendReloadMsg(data);
136
+ }
137
+ else if (resp.button.label === 'Overwrite') {
138
+ this._sendOverwriteMsg(data);
139
+ }
140
+ });
141
+ }
142
+ _sendReloadMsg(data) {
143
+ var _a;
144
+ const encoder = encoding.createEncoder();
145
+ encoding.writeVarUint(encoder, MessageType.ROOM);
146
+ encoding.writeVarUint(encoder, RoomMessage.RELOAD);
147
+ encoding.writeVarString(encoder, data);
148
+ (_a = this._yWebsocketProvider) === null || _a === void 0 ? void 0 : _a.ws.send(encoding.toUint8Array(encoder));
149
+ }
150
+ _sendOverwriteMsg(data) {
151
+ var _a;
152
+ const encoder = encoding.createEncoder();
153
+ encoding.writeVarUint(encoder, MessageType.ROOM);
154
+ encoding.writeVarUint(encoder, RoomMessage.OVERWRITE);
155
+ encoding.writeVarString(encoder, data);
156
+ (_a = this._yWebsocketProvider) === null || _a === void 0 ? void 0 : _a.ws.send(encoding.toUint8Array(encoder));
157
+ }
99
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyter/docprovider",
3
- "version": "1.2.0",
3
+ "version": "2.0.0-alpha.0",
4
4
  "description": "JupyterLab - Document Provider",
5
5
  "homepage": "https://github.com/jupyterlab/jupyter_collaboration",
6
6
  "bugs": {
@@ -41,9 +41,9 @@
41
41
  "watch": "tsc -b --watch"
42
42
  },
43
43
  "dependencies": {
44
- "@jupyter/ydoc": "^1.0.2",
45
- "@jupyterlab/coreutils": "^6.0.0",
46
- "@jupyterlab/services": "^7.0.0",
44
+ "@jupyter/ydoc": "^1.1.0-a0",
45
+ "@jupyterlab/coreutils": "^6.0.5",
46
+ "@jupyterlab/services": "^7.0.5",
47
47
  "@lumino/coreutils": "^2.1.0",
48
48
  "@lumino/disposable": "^2.1.0",
49
49
  "@lumino/signaling": "^2.1.0",
@@ -52,7 +52,7 @@
52
52
  "yjs": "^13.5.40"
53
53
  },
54
54
  "devDependencies": {
55
- "@jupyterlab/testing": "^4.0.0",
55
+ "@jupyterlab/testing": "^4.0.5",
56
56
  "@types/jest": "^29.2.0",
57
57
  "jest": "^29.5.0",
58
58
  "rimraf": "^4.1.2",