@kerebron/extension-yjs 0.6.7 → 0.7.1

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.
Files changed (103) hide show
  1. package/esm/ExtensionYjs.d.ts +3 -11
  2. package/esm/ExtensionYjs.d.ts.map +1 -1
  3. package/esm/ExtensionYjs.js +71 -45
  4. package/esm/ExtensionYjs.js.map +1 -1
  5. package/esm/WebsocketProvider.d.ts +70 -0
  6. package/esm/WebsocketProvider.d.ts.map +1 -0
  7. package/esm/WebsocketProvider.js +377 -0
  8. package/esm/WebsocketProvider.js.map +1 -0
  9. package/esm/YjsProvider.d.ts +48 -0
  10. package/esm/YjsProvider.d.ts.map +1 -0
  11. package/esm/YjsProvider.js +12 -0
  12. package/esm/YjsProvider.js.map +1 -0
  13. package/esm/_dnt.shims.d.ts +2 -0
  14. package/esm/_dnt.shims.d.ts.map +1 -0
  15. package/esm/_dnt.shims.js +58 -0
  16. package/esm/_dnt.shims.js.map +1 -0
  17. package/esm/binding/BindingMetadata.d.ts +6 -0
  18. package/esm/binding/BindingMetadata.d.ts.map +1 -0
  19. package/esm/binding/BindingMetadata.js +2 -0
  20. package/esm/binding/BindingMetadata.js.map +1 -0
  21. package/esm/binding/DiffViewer.d.ts +17 -0
  22. package/esm/binding/DiffViewer.d.ts.map +1 -0
  23. package/esm/binding/DiffViewer.js +96 -0
  24. package/esm/binding/DiffViewer.js.map +1 -0
  25. package/esm/binding/PmYjsBinding.d.ts +45 -0
  26. package/esm/binding/PmYjsBinding.d.ts.map +1 -0
  27. package/esm/binding/PmYjsBinding.js +230 -0
  28. package/esm/binding/PmYjsBinding.js.map +1 -0
  29. package/esm/binding/convertUtils.d.ts +48 -0
  30. package/esm/binding/convertUtils.d.ts.map +1 -0
  31. package/esm/binding/convertUtils.js +80 -0
  32. package/esm/binding/convertUtils.js.map +1 -0
  33. package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +2 -2
  34. package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
  35. package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
  36. package/esm/binding/createNodeFromYElement.js.map +1 -0
  37. package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
  38. package/esm/binding/updateYFragment.d.ts.map +1 -0
  39. package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
  40. package/esm/binding/updateYFragment.js.map +1 -0
  41. package/esm/debug.d.ts.map +1 -1
  42. package/esm/debug.js +11 -0
  43. package/esm/debug.js.map +1 -1
  44. package/esm/lib.d.ts +1 -7
  45. package/esm/lib.d.ts.map +1 -1
  46. package/esm/lib.js +1 -200
  47. package/esm/lib.js.map +1 -1
  48. package/esm/position.d.ts +8 -0
  49. package/esm/position.d.ts.map +1 -0
  50. package/esm/position.js +165 -0
  51. package/esm/position.js.map +1 -0
  52. package/esm/ui/selection.d.ts +29 -0
  53. package/esm/ui/selection.d.ts.map +1 -0
  54. package/esm/ui/selection.js +129 -0
  55. package/esm/ui/selection.js.map +1 -0
  56. package/esm/utils.d.ts +1 -1
  57. package/esm/utils.d.ts.map +1 -1
  58. package/esm/utils.js.map +1 -1
  59. package/esm/yPositionPlugin.d.ts +6 -1
  60. package/esm/yPositionPlugin.d.ts.map +1 -1
  61. package/esm/yPositionPlugin.js +91 -50
  62. package/esm/yPositionPlugin.js.map +1 -1
  63. package/esm/ySyncPlugin.d.ts +5 -22
  64. package/esm/ySyncPlugin.d.ts.map +1 -1
  65. package/esm/ySyncPlugin.js +70 -101
  66. package/esm/ySyncPlugin.js.map +1 -1
  67. package/esm/yUndoPlugin.d.ts +11 -10
  68. package/esm/yUndoPlugin.d.ts.map +1 -1
  69. package/esm/yUndoPlugin.js +90 -52
  70. package/esm/yUndoPlugin.js.map +1 -1
  71. package/package.json +9 -6
  72. package/src/ExtensionYjs.ts +98 -67
  73. package/src/WebsocketProvider.ts +528 -0
  74. package/src/YjsProvider.ts +75 -0
  75. package/src/_dnt.shims.ts +60 -0
  76. package/src/binding/BindingMetadata.ts +6 -0
  77. package/src/binding/DiffViewer.ts +138 -0
  78. package/src/binding/PmYjsBinding.ts +360 -0
  79. package/src/binding/convertUtils.ts +124 -0
  80. package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +4 -4
  81. package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
  82. package/src/debug.ts +21 -0
  83. package/src/lib.ts +4 -230
  84. package/src/position.ts +191 -0
  85. package/src/ui/selection.ts +218 -0
  86. package/src/utils.ts +1 -1
  87. package/src/yPositionPlugin.ts +122 -74
  88. package/src/ySyncPlugin.ts +111 -155
  89. package/src/yUndoPlugin.ts +113 -62
  90. package/esm/ProsemirrorBinding.d.ts +0 -60
  91. package/esm/ProsemirrorBinding.d.ts.map +0 -1
  92. package/esm/ProsemirrorBinding.js +0 -405
  93. package/esm/ProsemirrorBinding.js.map +0 -1
  94. package/esm/createNodeFromYElement.d.ts.map +0 -1
  95. package/esm/createNodeFromYElement.js.map +0 -1
  96. package/esm/updateYFragment.d.ts.map +0 -1
  97. package/esm/updateYFragment.js.map +0 -1
  98. package/esm/userColors.d.ts +0 -5
  99. package/esm/userColors.d.ts.map +0 -1
  100. package/esm/userColors.js +0 -11
  101. package/esm/userColors.js.map +0 -1
  102. package/src/ProsemirrorBinding.ts +0 -607
  103. package/src/userColors.ts +0 -10
@@ -0,0 +1,528 @@
1
+ import * as Y from 'yjs';
2
+ import * as time from 'lib0/time';
3
+ import * as encoding from 'lib0/encoding';
4
+ import * as decoding from 'lib0/decoding';
5
+ import * as syncProtocol from 'y-protocols/sync';
6
+ import * as authProtocol from 'y-protocols/auth';
7
+ import * as awarenessProtocol from 'y-protocols/awareness';
8
+ import * as math from 'lib0/math';
9
+ import * as url from 'lib0/url';
10
+
11
+ import {
12
+ messageAuth,
13
+ messageAwareness,
14
+ MessageHandler,
15
+ messageQueryAwareness,
16
+ messageSync,
17
+ MessageType,
18
+ YjsProvider,
19
+ } from './YjsProvider.js';
20
+
21
+ // @todo - this should depend on awareness.outdatedTime
22
+ const messageReconnectTimeout = 30000;
23
+
24
+ const permissionDeniedHandler = (provider: WebsocketProvider, reason: string) =>
25
+ console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
26
+
27
+ const readMessage = (
28
+ provider: WebsocketProvider,
29
+ buf: Uint8Array,
30
+ emitSynced: boolean,
31
+ ): encoding.Encoder => {
32
+ const decoder = decoding.createDecoder(buf);
33
+ const encoder = encoding.createEncoder();
34
+ const messageType = decoding.readVarUint(decoder) as MessageType;
35
+ const messageHandler = provider.messageHandlers[messageType];
36
+ if (messageHandler) {
37
+ messageHandler(encoder, decoder, provider, emitSynced, messageType);
38
+ } else {
39
+ console.error('Unable to compute message');
40
+ }
41
+ return encoder;
42
+ };
43
+
44
+ /**
45
+ * Outsource this function so that a new websocket connection is created immediately.
46
+ * I suspect that the `ws.onclose` event is not always fired if there are network issues.
47
+ */
48
+ const closeWebsocketConnection = (
49
+ provider: WebsocketProvider,
50
+ ws: WebSocket,
51
+ event: CloseEvent | null,
52
+ ) => {
53
+ if (ws === provider.ws) {
54
+ provider.dispatchEvent(
55
+ new CustomEvent('connection-close', { detail: { event, provider } }),
56
+ );
57
+ provider.ws = undefined;
58
+ ws.close();
59
+ provider.wsconnecting = false;
60
+ if (provider.wsconnected) {
61
+ provider.wsconnected = false;
62
+ provider.synced = false;
63
+ // update awareness (all users except local left)
64
+ awarenessProtocol.removeAwarenessStates(
65
+ provider.awareness,
66
+ Array.from(provider.awareness.getStates().keys()).filter((client) =>
67
+ client !== provider.doc.clientID
68
+ ),
69
+ provider,
70
+ );
71
+ provider.dispatchEvent(
72
+ new CustomEvent('status', { detail: { status: 'disconnected' } }),
73
+ );
74
+ } else {
75
+ provider.wsUnsuccessfulReconnects++;
76
+ }
77
+ // Start with no reconnect timeout and increase timeout by
78
+ // using exponential backoff starting with 100ms
79
+ if (!provider.destroyed) {
80
+ provider.setupWSTimeout = setTimeout(
81
+ setupWS,
82
+ math.min(
83
+ math.pow(2, provider.wsUnsuccessfulReconnects) * 100,
84
+ provider.maxBackoffTime,
85
+ ),
86
+ provider,
87
+ );
88
+ }
89
+ }
90
+ };
91
+
92
+ const setupWS = (provider: WebsocketProvider) => {
93
+ provider.setupWSTimeout = undefined;
94
+ if (provider.shouldConnect && !provider.ws) {
95
+ const websocket = new provider._WS(provider.url, provider.protocols);
96
+ websocket.binaryType = 'arraybuffer';
97
+ provider.ws = websocket;
98
+ provider.wsconnecting = true;
99
+ provider.wsconnected = false;
100
+ provider.synced = false;
101
+
102
+ websocket.onmessage = (event) => {
103
+ provider.wsLastMessageReceived = time.getUnixTime();
104
+ const encoder = readMessage(provider, new Uint8Array(event.data), true);
105
+ if (encoding.length(encoder) > 1) {
106
+ websocket.send(encoding.toUint8Array(encoder));
107
+ }
108
+ };
109
+ websocket.onerror = (event) => {
110
+ provider.dispatchEvent(
111
+ new CustomEvent('connection-error', { detail: { event, provider } }),
112
+ );
113
+ };
114
+ websocket.onclose = (event) => {
115
+ try {
116
+ closeWebsocketConnection(provider, websocket, event);
117
+ } catch (err) {
118
+ console.error(err);
119
+ }
120
+ };
121
+ websocket.onopen = () => {
122
+ provider.wsLastMessageReceived = time.getUnixTime();
123
+ provider.wsconnecting = false;
124
+ provider.wsconnected = true;
125
+ provider.wsUnsuccessfulReconnects = 0;
126
+ provider.dispatchEvent(
127
+ new CustomEvent('status', { detail: { status: 'connected' } }),
128
+ );
129
+ // always send sync step 1 when connected
130
+ const encoder = encoding.createEncoder();
131
+ encoding.writeVarUint(encoder, messageSync);
132
+ syncProtocol.writeSyncStep1(encoder, provider.doc);
133
+ websocket.send(encoding.toUint8Array(encoder));
134
+ // broadcast local awareness state
135
+ if (provider.awareness.getLocalState() !== null) {
136
+ const encoderAwarenessState = encoding.createEncoder();
137
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
138
+ encoding.writeVarUint8Array(
139
+ encoderAwarenessState,
140
+ awarenessProtocol.encodeAwarenessUpdate(provider.awareness, [
141
+ provider.doc.clientID,
142
+ ]),
143
+ );
144
+ websocket.send(encoding.toUint8Array(encoderAwarenessState));
145
+ }
146
+ };
147
+ provider.dispatchEvent(
148
+ new CustomEvent('status', { detail: { status: 'connecting' } }),
149
+ );
150
+ }
151
+ };
152
+
153
+ const broadcastMessage = (provider: WebsocketProvider, buf: Uint8Array) => {
154
+ const ws = provider.ws;
155
+ if (provider.wsconnected && ws && ws.readyState === ws.OPEN) {
156
+ ws.send(buf);
157
+ }
158
+ if (provider.bcconnected) {
159
+ try {
160
+ provider.bc.postMessage(buf);
161
+ } catch (err) {
162
+ console.trace(err);
163
+ }
164
+ }
165
+ };
166
+
167
+ interface WebsocketProviderOpts {
168
+ connect: boolean;
169
+ awareness: awarenessProtocol.Awareness;
170
+ params: Record<string, string>; // specify url parameters
171
+ protocols: Array<string>; // specify websocket protocols
172
+ WebSocketPolyfill: typeof WebSocket; // Optionall provide a WebSocket polyfill
173
+ resyncInterval: number; // Request server state every `resyncInterval` milliseconds
174
+ maxBackoffTime: number; // Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff)
175
+ disableBc: boolean; // Disable cross-tab BroadcastChannel communication
176
+ }
177
+
178
+ /**
179
+ * Websocket Provider for Yjs. Creates a websocket connection to sync the shared document.
180
+ * The document name is attached to the provided url. I.e. the following example
181
+ * creates a websocket connection to http://localhost:1234/my-document-name
182
+ *
183
+ * @example
184
+ * import * as Y from 'yjs'
185
+ * import { WebsocketProvider } from '@kerebron/extension-yjs/WebsocketProvider'
186
+ * const doc = new Y.Doc()
187
+ * const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc)
188
+ */
189
+ export class WebsocketProvider extends EventTarget implements YjsProvider {
190
+ roomname: string;
191
+ doc: Y.Doc;
192
+
193
+ _synced = false;
194
+ shouldConnect: boolean;
195
+ ws: WebSocket | undefined;
196
+ _resyncInterval = 0;
197
+ serverUrl: string;
198
+ bcChannel: string;
199
+ maxBackoffTime: number;
200
+ /**
201
+ * The specified url parameters. This can be safely updated. The changed parameters will be used
202
+ * when a new connection is established.
203
+ */
204
+ params: Record<string, string>;
205
+ protocols: string[];
206
+ _WS: typeof WebSocket;
207
+ awareness: awarenessProtocol.Awareness;
208
+ destroyed = false;
209
+ wsconnected = false;
210
+ wsconnecting = false;
211
+ bcconnected = false;
212
+ disableBc: boolean;
213
+ wsUnsuccessfulReconnects: number;
214
+ messageHandlers: MessageHandler<WebsocketProvider>[];
215
+ wsLastMessageReceived: number;
216
+ public readonly bc: BroadcastChannel;
217
+ private _bcSubscriber: (messageEvent: MessageEvent) => void;
218
+ private _updateHandler: (update: any, origin: any) => void;
219
+ private _awarenessUpdateHandler: (
220
+ { added, updated, removed }: { added: any; updated: any; removed: any },
221
+ _origin: any,
222
+ ) => void;
223
+ private _exitHandler: () => void;
224
+ private _checkInterval: number;
225
+ setupWSTimeout: number | undefined;
226
+
227
+ constructor(
228
+ serverUrl: string,
229
+ roomname: string,
230
+ doc: Y.Doc,
231
+ opts: WebsocketProviderOpts = {
232
+ connect: true,
233
+ awareness: new awarenessProtocol.Awareness(doc),
234
+ params: {},
235
+ protocols: [],
236
+ WebSocketPolyfill: WebSocket,
237
+ resyncInterval: -1,
238
+ maxBackoffTime: 2500,
239
+ disableBc: false,
240
+ },
241
+ ) {
242
+ super();
243
+ // ensure that serverUrl does not end with /
244
+ this.serverUrl = serverUrl;
245
+ this.roomname = roomname;
246
+ this.doc = doc;
247
+
248
+ while (this.serverUrl[this.serverUrl.length - 1] === '/') {
249
+ this.serverUrl = this.serverUrl.slice(0, this.serverUrl.length - 1);
250
+ }
251
+ this.bcChannel = this.serverUrl + '/' + roomname;
252
+ this.bc = new BroadcastChannel(this.bcChannel);
253
+ this.maxBackoffTime = opts.maxBackoffTime;
254
+ this.params = opts.params;
255
+ this.protocols = opts.protocols;
256
+ this._WS = opts.WebSocketPolyfill;
257
+ this.awareness = opts.awareness;
258
+ this.disableBc = opts.disableBc;
259
+ this.wsUnsuccessfulReconnects = 0;
260
+ this.messageHandlers = this.setupMessageHandlers();
261
+ this.wsLastMessageReceived = 0;
262
+ this.shouldConnect = opts.connect;
263
+
264
+ if (opts.resyncInterval > 0) {
265
+ this._resyncInterval = setInterval(() => {
266
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
267
+ // resend sync step 1
268
+ const encoder = encoding.createEncoder();
269
+ encoding.writeVarUint(encoder, messageSync);
270
+ syncProtocol.writeSyncStep1(encoder, doc);
271
+ this.ws.send(encoding.toUint8Array(encoder));
272
+ }
273
+ }, opts.resyncInterval);
274
+ }
275
+
276
+ this._bcSubscriber = (event: MessageEvent) => {
277
+ readMessage(this, new Uint8Array(event.data), false);
278
+ };
279
+ /**
280
+ * Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel)
281
+ */
282
+ this._updateHandler = (update, origin) => {
283
+ if (origin !== this) {
284
+ const encoder = encoding.createEncoder();
285
+ encoding.writeVarUint(encoder, messageSync);
286
+ syncProtocol.writeUpdate(encoder, update);
287
+ broadcastMessage(this, encoding.toUint8Array(encoder));
288
+ }
289
+ };
290
+ this.doc.on('update', this._updateHandler);
291
+ this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => {
292
+ const changedClients = added.concat(updated).concat(removed);
293
+ const encoder = encoding.createEncoder();
294
+ encoding.writeVarUint(encoder, messageAwareness);
295
+ encoding.writeVarUint8Array(
296
+ encoder,
297
+ awarenessProtocol.encodeAwarenessUpdate(opts.awareness, changedClients),
298
+ );
299
+ broadcastMessage(this, encoding.toUint8Array(encoder));
300
+ };
301
+ this._exitHandler = () => {
302
+ awarenessProtocol.removeAwarenessStates(
303
+ this.awareness,
304
+ [doc.clientID],
305
+ 'app closed',
306
+ );
307
+ };
308
+ // if (env.isNode && typeof process !== 'undefined') {
309
+ // process.on('exit', this._exitHandler);
310
+ // }
311
+ opts.awareness.on('update', this._awarenessUpdateHandler);
312
+ this._checkInterval = /** @type {any} */ (setInterval(() => {
313
+ try {
314
+ if (
315
+ this.wsconnected &&
316
+ messageReconnectTimeout <
317
+ time.getUnixTime() - this.wsLastMessageReceived
318
+ ) {
319
+ // no message received in a long time - not even your own awareness
320
+ // updates (which are updated every 15 seconds)
321
+
322
+ if (this.ws) {
323
+ closeWebsocketConnection(
324
+ this,
325
+ this.ws,
326
+ null,
327
+ );
328
+ }
329
+ }
330
+ } catch (err) {
331
+ console.error(err);
332
+ }
333
+ }, messageReconnectTimeout / 10));
334
+
335
+ if (opts.connect) {
336
+ this.connect();
337
+ }
338
+ }
339
+
340
+ get url() {
341
+ const encodedParams = url.encodeQueryParams(this.params);
342
+ return this.serverUrl + '/' + this.roomname +
343
+ (encodedParams.length === 0 ? '' : '?' + encodedParams);
344
+ }
345
+
346
+ get synced(): boolean {
347
+ return this._synced;
348
+ }
349
+
350
+ set synced(state: boolean) {
351
+ if (this._synced !== state) {
352
+ this._synced = state;
353
+ this.dispatchEvent(new CustomEvent('synced', { detail: { state } }));
354
+ this.dispatchEvent(new CustomEvent('sync', { detail: { state } }));
355
+ }
356
+ }
357
+
358
+ destroy() {
359
+ this.destroyed = true;
360
+
361
+ if (this._resyncInterval !== 0) {
362
+ clearInterval(this._resyncInterval);
363
+ }
364
+ if (this.setupWSTimeout) {
365
+ clearTimeout(this.setupWSTimeout);
366
+ }
367
+ clearInterval(this._checkInterval);
368
+ this.disconnect();
369
+ // if (env.isNode && typeof process !== 'undefined') {
370
+ // process.off('exit', this._exitHandler);
371
+ // }
372
+ this.awareness.off('update', this._awarenessUpdateHandler);
373
+ this.doc.off('update', this._updateHandler);
374
+ this.awareness.destroy();
375
+ }
376
+
377
+ connectBc() {
378
+ if (this.disableBc) {
379
+ return;
380
+ }
381
+ if (!this.bcconnected) {
382
+ this.bc.addEventListener('message', this._bcSubscriber);
383
+ this.bcconnected = true;
384
+ }
385
+
386
+ try {
387
+ // send sync step1 to bc
388
+ // write sync step 1
389
+ const encoderSync = encoding.createEncoder();
390
+ encoding.writeVarUint(encoderSync, messageSync);
391
+ syncProtocol.writeSyncStep1(encoderSync, this.doc);
392
+ this.bc.postMessage(encoding.toUint8Array(encoderSync));
393
+
394
+ // broadcast local state
395
+ const encoderState = encoding.createEncoder();
396
+ encoding.writeVarUint(encoderState, messageSync);
397
+ syncProtocol.writeSyncStep2(encoderState, this.doc);
398
+ this.bc.postMessage(encoding.toUint8Array(encoderState));
399
+ // write queryAwareness
400
+ const encoderAwarenessQuery = encoding.createEncoder();
401
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
402
+ this.bc.postMessage(encoding.toUint8Array(encoderAwarenessQuery));
403
+ // broadcast local awareness state
404
+ const encoderAwarenessState = encoding.createEncoder();
405
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
406
+ encoding.writeVarUint8Array(
407
+ encoderAwarenessState,
408
+ awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
409
+ this.doc.clientID,
410
+ ]),
411
+ );
412
+ this.bc.postMessage(encoding.toUint8Array(encoderAwarenessState));
413
+ } catch (err) {
414
+ console.trace(err);
415
+ }
416
+ }
417
+
418
+ disconnectBc() {
419
+ // broadcast message with local awareness state set to null (indicating disconnect)
420
+ const encoder = encoding.createEncoder();
421
+ encoding.writeVarUint(encoder, messageAwareness);
422
+ encoding.writeVarUint8Array(
423
+ encoder,
424
+ awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
425
+ this.doc.clientID,
426
+ ], new Map()),
427
+ );
428
+ broadcastMessage(this, encoding.toUint8Array(encoder));
429
+ if (this.bcconnected) {
430
+ setTimeout(() => { // Deno defers sending message. Ensure above broadcase was transmitted
431
+ try {
432
+ this.bc.close();
433
+ } catch (err) {
434
+ console.trace(err);
435
+ }
436
+ }, 0);
437
+ this.bcconnected = false;
438
+ }
439
+ }
440
+
441
+ disconnect() {
442
+ this.shouldConnect = false;
443
+ this.disconnectBc();
444
+ if (this.ws) {
445
+ closeWebsocketConnection(this, this.ws, null);
446
+ }
447
+ }
448
+
449
+ connect() {
450
+ this.shouldConnect = true;
451
+ if (!this.wsconnected && !this.ws) {
452
+ setupWS(this);
453
+ this.connectBc();
454
+ }
455
+ }
456
+
457
+ private setupMessageHandlers() {
458
+ const messageHandlers: MessageHandler<WebsocketProvider>[] = [];
459
+ messageHandlers[messageSync] = (
460
+ encoder: encoding.Encoder,
461
+ decoder: decoding.Decoder,
462
+ provider: WebsocketProvider,
463
+ emitSynced: boolean,
464
+ _messageType: MessageType,
465
+ ) => {
466
+ encoding.writeVarUint(encoder, messageSync);
467
+ const syncMessageType: MessageType = syncProtocol.readSyncMessage(
468
+ decoder,
469
+ encoder,
470
+ provider.doc,
471
+ provider,
472
+ );
473
+ if (
474
+ emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2 &&
475
+ !provider.synced
476
+ ) {
477
+ provider.synced = true;
478
+ }
479
+ };
480
+
481
+ messageHandlers[messageQueryAwareness] = (
482
+ encoder: encoding.Encoder,
483
+ _decoder: decoding.Decoder,
484
+ provider: WebsocketProvider,
485
+ _emitSynced: boolean,
486
+ _messageType: MessageType,
487
+ ) => {
488
+ encoding.writeVarUint(encoder, messageAwareness);
489
+ encoding.writeVarUint8Array(
490
+ encoder,
491
+ awarenessProtocol.encodeAwarenessUpdate(
492
+ provider.awareness,
493
+ Array.from(provider.awareness.getStates().keys()),
494
+ ),
495
+ );
496
+ };
497
+
498
+ messageHandlers[messageAwareness] = (
499
+ _encoder: encoding.Encoder,
500
+ decoder: decoding.Decoder,
501
+ provider: WebsocketProvider,
502
+ _emitSynced: boolean,
503
+ _messageType: MessageType,
504
+ ) => {
505
+ awarenessProtocol.applyAwarenessUpdate(
506
+ provider.awareness,
507
+ decoding.readVarUint8Array(decoder),
508
+ provider,
509
+ );
510
+ };
511
+
512
+ messageHandlers[messageAuth] = (
513
+ _encoder: encoding.Encoder,
514
+ decoder: decoding.Decoder,
515
+ provider: WebsocketProvider,
516
+ _emitSynced: boolean,
517
+ _messageType: MessageType,
518
+ ) => {
519
+ authProtocol.readAuthMessage(
520
+ decoder,
521
+ provider.doc,
522
+ (_ydoc, reason) => permissionDeniedHandler(provider, reason),
523
+ );
524
+ };
525
+
526
+ return messageHandlers;
527
+ }
528
+ }
@@ -0,0 +1,75 @@
1
+ // y-websocket is forked because we need better state connection handling
2
+
3
+ import * as Y from 'yjs';
4
+ import * as awarenessProtocol from 'y-protocols/awareness';
5
+ import * as encoding from 'lib0/encoding';
6
+ import * as decoding from 'lib0/decoding';
7
+
8
+ export const messageSync = 0;
9
+ export const messageQueryAwareness = 3;
10
+ export const messageAwareness = 1;
11
+ export const messageAuth = 2;
12
+
13
+ export type CreateYjsProvider = (roomId: string) => [YjsProvider, Y.Doc];
14
+
15
+ export const MessageType = {
16
+ Sync: 0,
17
+ Awareness: 1,
18
+ Auth: 2,
19
+ QueryAwareness: 3,
20
+ } as const;
21
+
22
+ export type MessageType = typeof MessageType[keyof typeof MessageType];
23
+
24
+ export type MessageHandler<T extends YjsProvider> = (
25
+ encoder: encoding.Encoder,
26
+ decoder: decoding.Decoder,
27
+ provider: T,
28
+ emitSynced: boolean,
29
+ messageType: MessageType,
30
+ ) => void;
31
+
32
+ export interface YjsProviderEventMap {
33
+ status: CustomEvent<{ status: 'connected' | 'disconnected' | 'connecting' }>;
34
+ sync: CustomEvent<{ state: boolean }>;
35
+ synced: CustomEvent<{ state: boolean }>;
36
+ 'connection-error': CustomEvent<{ event: Event; provider: YjsProvider }>;
37
+ 'connection-close': CustomEvent<
38
+ { event: CloseEvent | null; provider: YjsProvider }
39
+ >;
40
+ }
41
+
42
+ export interface YjsProvider extends EventTarget {
43
+ awareness: awarenessProtocol.Awareness;
44
+ doc: Y.Doc;
45
+ synced: boolean;
46
+
47
+ destroy();
48
+
49
+ addEventListener<K extends keyof YjsProviderEventMap>(
50
+ type: K,
51
+ listener: (event: YjsProviderEventMap[K]) => void,
52
+ options?: boolean | AddEventListenerOptions,
53
+ ): void;
54
+
55
+ removeEventListener<K extends keyof YjsProviderEventMap>(
56
+ type: K,
57
+ listener: (event: YjsProviderEventMap[K]) => void,
58
+ options?: boolean | EventListenerOptions,
59
+ ): void;
60
+
61
+ dispatchEvent(event: Event): boolean;
62
+
63
+ // fallback DOM signature (required)
64
+ addEventListener(
65
+ type: string,
66
+ listener: EventListenerOrEventListenerObject | null,
67
+ options?: boolean | AddEventListenerOptions,
68
+ ): void;
69
+
70
+ removeEventListener(
71
+ type: string,
72
+ listener: EventListenerOrEventListenerObject | null,
73
+ options?: boolean | EventListenerOptions,
74
+ ): void;
75
+ }
@@ -0,0 +1,60 @@
1
+ const dntGlobals = {
2
+ };
3
+ export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
4
+
5
+ function createMergeProxy<T extends object, U extends object>(
6
+ baseObj: T,
7
+ extObj: U,
8
+ ): Omit<T, keyof U> & U {
9
+ return new Proxy(baseObj, {
10
+ get(_target, prop, _receiver) {
11
+ if (prop in extObj) {
12
+ return (extObj as any)[prop];
13
+ } else {
14
+ return (baseObj as any)[prop];
15
+ }
16
+ },
17
+ set(_target, prop, value) {
18
+ if (prop in extObj) {
19
+ delete (extObj as any)[prop];
20
+ }
21
+ (baseObj as any)[prop] = value;
22
+ return true;
23
+ },
24
+ deleteProperty(_target, prop) {
25
+ let success = false;
26
+ if (prop in extObj) {
27
+ delete (extObj as any)[prop];
28
+ success = true;
29
+ }
30
+ if (prop in baseObj) {
31
+ delete (baseObj as any)[prop];
32
+ success = true;
33
+ }
34
+ return success;
35
+ },
36
+ ownKeys(_target) {
37
+ const baseKeys = Reflect.ownKeys(baseObj);
38
+ const extKeys = Reflect.ownKeys(extObj);
39
+ const extKeysSet = new Set(extKeys);
40
+ return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
41
+ },
42
+ defineProperty(_target, prop, desc) {
43
+ if (prop in extObj) {
44
+ delete (extObj as any)[prop];
45
+ }
46
+ Reflect.defineProperty(baseObj, prop, desc);
47
+ return true;
48
+ },
49
+ getOwnPropertyDescriptor(_target, prop) {
50
+ if (prop in extObj) {
51
+ return Reflect.getOwnPropertyDescriptor(extObj, prop);
52
+ } else {
53
+ return Reflect.getOwnPropertyDescriptor(baseObj, prop);
54
+ }
55
+ },
56
+ has(_target, prop) {
57
+ return prop in extObj || prop in baseObj;
58
+ },
59
+ }) as any;
60
+ }
@@ -0,0 +1,6 @@
1
+ import { ProsemirrorMapping } from '../lib.js';
2
+
3
+ export interface BindingMetadata {
4
+ mapping: ProsemirrorMapping;
5
+ isOverlappingMark: Map<string, boolean>;
6
+ }