@hocuspocus/provider 1.0.2 → 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.
Files changed (45) hide show
  1. package/dist/hocuspocus-provider.cjs +370 -209
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +370 -210
  4. package/dist/hocuspocus-provider.esm.js.map +1 -1
  5. package/dist/packages/extension-monitor/src/Dashboard.d.ts +1 -1
  6. package/dist/packages/extension-monitor/src/index.d.ts +1 -1
  7. package/dist/packages/extension-redis/src/Redis.d.ts +19 -5
  8. package/dist/packages/provider/src/HocuspocusCloudProvider.d.ts +2 -1
  9. package/dist/packages/provider/src/HocuspocusProvider.d.ts +14 -70
  10. package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +115 -0
  11. package/dist/packages/provider/src/IncomingMessage.d.ts +2 -0
  12. package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +7 -0
  13. package/dist/packages/provider/src/index.d.ts +1 -0
  14. package/dist/packages/provider/src/types.d.ts +7 -1
  15. package/dist/packages/server/src/Connection.d.ts +10 -11
  16. package/dist/packages/server/src/Document.d.ts +9 -0
  17. package/dist/packages/server/src/Hocuspocus.d.ts +3 -8
  18. package/dist/packages/server/src/IncomingMessage.d.ts +2 -0
  19. package/dist/packages/server/src/MessageReceiver.d.ts +1 -2
  20. package/dist/packages/server/src/OutgoingMessage.d.ts +3 -1
  21. package/dist/packages/server/src/types.d.ts +19 -3
  22. package/dist/packages/transformer/src/Prosemirror.d.ts +1 -1
  23. package/dist/tests/providerwebsocket/configuration.d.ts +1 -0
  24. package/dist/tests/server/beforeBroadcastStateless.d.ts +1 -0
  25. package/dist/tests/server/onStateless.d.ts +1 -0
  26. package/dist/tests/utils/index.d.ts +1 -0
  27. package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
  28. package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +3 -0
  29. package/package.json +3 -3
  30. package/src/HocuspocusCloudProvider.ts +8 -1
  31. package/src/HocuspocusProvider.ts +111 -358
  32. package/src/HocuspocusProviderWebsocket.ts +475 -0
  33. package/src/IncomingMessage.ts +10 -0
  34. package/src/MessageReceiver.ts +9 -2
  35. package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
  36. package/src/OutgoingMessages/AwarenessMessage.ts +1 -0
  37. package/src/OutgoingMessages/QueryAwarenessMessage.ts +5 -0
  38. package/src/OutgoingMessages/StatelessMessage.ts +17 -0
  39. package/src/OutgoingMessages/SyncStepOneMessage.ts +1 -0
  40. package/src/OutgoingMessages/SyncStepTwoMessage.ts +1 -1
  41. package/src/OutgoingMessages/UpdateMessage.ts +3 -1
  42. package/src/index.ts +1 -0
  43. package/src/types.ts +7 -0
  44. /package/dist/tests/{provider/configuration.d.ts → extension-redis/onStateless.d.ts} +0 -0
  45. /package/dist/tests/{server/getDocumentName.d.ts → provider/onStateless.d.ts} +0 -0
@@ -1,14 +1,9 @@
1
1
  import * as Y from 'yjs'
2
2
  import * as bc from 'lib0/broadcastchannel'
3
- import * as time from 'lib0/time'
4
3
  import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
5
4
  import * as mutex from 'lib0/mutex'
6
- import * as url from 'lib0/url'
7
- import type { Event, CloseEvent, MessageEvent } from 'ws'
8
- import { retry } from '@lifeomic/attempt'
9
- import {
10
- awarenessStatesToArray, Forbidden, Unauthorized, WsReadyStates,
11
- } from '@hocuspocus/common'
5
+ import type { CloseEvent, Event, MessageEvent } from 'ws'
6
+ import { awarenessStatesToArray } from '@hocuspocus/common'
12
7
  import EventEmitter from './EventEmitter'
13
8
  import { IncomingMessage } from './IncomingMessage'
14
9
  import { MessageReceiver } from './MessageReceiver'
@@ -20,19 +15,26 @@ import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage'
20
15
  import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage'
21
16
  import { UpdateMessage } from './OutgoingMessages/UpdateMessage'
22
17
  import {
23
- ConstructableOutgoingMessage, onAuthenticationFailedParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters, onSyncedParameters, WebSocketStatus,
18
+ ConstructableOutgoingMessage,
19
+ onAuthenticationFailedParameters,
20
+ onCloseParameters,
21
+ onDisconnectParameters,
22
+ onMessageParameters,
23
+ onOpenParameters,
24
+ onOutgoingMessageParameters, onStatelessParameters,
25
+ onStatusParameters,
26
+ onSyncedParameters,
27
+ WebSocketStatus,
24
28
  } from './types'
29
+ import { HocuspocusProviderWebsocket } from './HocuspocusProviderWebsocket'
30
+ import { StatelessMessage } from './OutgoingMessages/StatelessMessage'
25
31
  import { onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.'
26
32
 
27
33
  export type HocuspocusProviderConfiguration =
28
- Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>>
34
+ Required<Pick<CompleteHocuspocusProviderConfiguration, 'name' | 'websocketProvider'>>
29
35
  & Partial<CompleteHocuspocusProviderConfiguration>
30
36
 
31
37
  export interface CompleteHocuspocusProviderConfiguration {
32
- /**
33
- * URL of your @hocuspocus/server instance
34
- */
35
- url: string,
36
38
  /**
37
39
  * The identifier/name of your document
38
40
  */
@@ -41,10 +43,7 @@ export interface CompleteHocuspocusProviderConfiguration {
41
43
  * The actual Y.js document
42
44
  */
43
45
  document: Y.Doc,
44
- /**
45
- * Pass `false` to start the connection manually.
46
- */
47
- connect: boolean,
46
+
48
47
  /**
49
48
  * Pass false to disable broadcasting between browser tabs.
50
49
  */
@@ -62,49 +61,14 @@ export interface CompleteHocuspocusProviderConfiguration {
62
61
  */
63
62
  parameters: { [key: string]: any },
64
63
  /**
65
- * An optional WebSocket polyfill, for example for Node.js
64
+ * Hocuspocus websocket provider
66
65
  */
67
- WebSocketPolyfill: any,
66
+ websocketProvider: HocuspocusProviderWebsocket,
68
67
  /**
69
68
  * Force syncing the document in the defined interval.
70
69
  */
71
70
  forceSyncInterval: false | number,
72
- /**
73
- * Disconnect when no message is received for the defined amount of milliseconds.
74
- */
75
- messageReconnectTimeout: number,
76
- /**
77
- * The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.
78
- */
79
- delay: number,
80
- /**
81
- * The intialDelay is the amount of time to wait before making the first attempt. This option should typically be 0 since you typically want the first attempt to happen immediately.
82
- */
83
- initialDelay: number,
84
- /**
85
- * The factor option is used to grow the delay exponentially.
86
- */
87
- factor: number,
88
- /**
89
- * The maximum number of attempts or 0 if there is no limit on number of attempts.
90
- */
91
- maxAttempts: number,
92
- /**
93
- * minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.
94
- */
95
- minDelay: number,
96
- /**
97
- * The maxDelay option is used to set an upper bound for the delay when factor is enabled. A value of 0 can be provided if there should be no upper bound when calculating delay.
98
- */
99
- maxDelay: number,
100
- /**
101
- * If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.
102
- */
103
- jitter: boolean,
104
- /**
105
- * A timeout in milliseconds. If timeout is non-zero then a timer is set using setTimeout. If the timeout is triggered then future attempts will be aborted.
106
- */
107
- timeout: number,
71
+
108
72
  onAuthenticated: () => void,
109
73
  onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void,
110
74
  onOpen: (data: onOpenParameters) => void,
@@ -118,6 +82,8 @@ export interface CompleteHocuspocusProviderConfiguration {
118
82
  onDestroy: () => void,
119
83
  onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void,
120
84
  onAwarenessChange: (data: onAwarenessChangeParameters) => void,
85
+ onStateless: (data: onStatelessParameters) => void
86
+
121
87
  /**
122
88
  * Don’t output any warnings.
123
89
  */
@@ -127,35 +93,14 @@ export interface CompleteHocuspocusProviderConfiguration {
127
93
  export class HocuspocusProvider extends EventEmitter {
128
94
  public configuration: CompleteHocuspocusProviderConfiguration = {
129
95
  name: '',
130
- url: '',
131
96
  // @ts-ignore
132
97
  document: undefined,
133
98
  // @ts-ignore
134
99
  awareness: undefined,
135
- WebSocketPolyfill: undefined,
136
100
  token: null,
137
101
  parameters: {},
138
- connect: true,
139
102
  broadcast: true,
140
103
  forceSyncInterval: false,
141
- // TODO: this should depend on awareness.outdatedTime
142
- messageReconnectTimeout: 30000,
143
- // 1 second
144
- delay: 1000,
145
- // instant
146
- initialDelay: 0,
147
- // double the delay each time
148
- factor: 2,
149
- // unlimited retries
150
- maxAttempts: 0,
151
- // wait at least 1 second
152
- minDelay: 1000,
153
- // at least every 30 seconds
154
- maxDelay: 30000,
155
- // randomize
156
- jitter: true,
157
- // retry forever
158
- timeout: 0,
159
104
  onAuthenticated: () => null,
160
105
  onAuthenticationFailed: () => null,
161
106
  onOpen: () => null,
@@ -169,58 +114,64 @@ export class HocuspocusProvider extends EventEmitter {
169
114
  onDestroy: () => null,
170
115
  onAwarenessUpdate: () => null,
171
116
  onAwarenessChange: () => null,
117
+ onStateless: () => null,
172
118
  quiet: false,
173
119
  }
174
120
 
175
121
  subscribedToBroadcastChannel = false
176
122
 
177
- webSocket: WebSocket | null = null
178
-
179
- shouldConnect = true
180
-
181
- status = WebSocketStatus.Disconnected
182
-
183
123
  isSynced = false
184
124
 
185
125
  unsyncedChanges = 0
186
126
 
187
- isAuthenticated = false
127
+ status = WebSocketStatus.Disconnected
188
128
 
189
- lastMessageReceived = 0
129
+ isAuthenticated = false
190
130
 
191
131
  mux = mutex.createMutex()
192
132
 
193
133
  intervals: any = {
194
134
  forceSync: null,
195
- connectionChecker: null,
196
135
  }
197
136
 
198
- connectionAttempt: {
199
- resolve: (value?: any) => void
200
- reject: (reason?: any) => void
201
- } | null = null
202
-
203
137
  constructor(configuration: HocuspocusProviderConfiguration) {
204
138
  super()
205
139
  this.setConfiguration(configuration)
206
140
 
207
141
  this.configuration.document = configuration.document ? configuration.document : new Y.Doc()
208
142
  this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document)
209
- this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket
210
143
 
211
144
  this.on('open', this.configuration.onOpen)
212
- this.on('authenticated', this.configuration.onAuthenticated)
213
- this.on('authenticationFailed', this.configuration.onAuthenticationFailed)
214
- this.on('connect', this.configuration.onConnect)
215
145
  this.on('message', this.configuration.onMessage)
216
146
  this.on('outgoingMessage', this.configuration.onOutgoingMessage)
217
147
  this.on('synced', this.configuration.onSynced)
218
- this.on('status', this.configuration.onStatus)
219
- this.on('disconnect', this.configuration.onDisconnect)
220
- this.on('close', this.configuration.onClose)
221
148
  this.on('destroy', this.configuration.onDestroy)
222
149
  this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
223
150
  this.on('awarenessChange', this.configuration.onAwarenessChange)
151
+ this.on('stateless', this.configuration.onStateless)
152
+
153
+ this.on('authenticated', this.configuration.onAuthenticated)
154
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed)
155
+
156
+ this.configuration.websocketProvider.on('connect', this.configuration.onConnect)
157
+ this.configuration.websocketProvider.on('connect', (e: Event) => this.emit('connect', e))
158
+
159
+ this.configuration.websocketProvider.on('open', this.onOpen.bind(this))
160
+ this.configuration.websocketProvider.on('open', (e: Event) => this.emit('open', e))
161
+
162
+ this.configuration.websocketProvider.on('message', this.onMessage.bind(this))
163
+
164
+ this.configuration.websocketProvider.on('close', this.onClose.bind(this))
165
+ this.configuration.websocketProvider.on('close', this.configuration.onClose)
166
+ this.configuration.websocketProvider.on('close', (e: Event) => this.emit('close', e))
167
+
168
+ this.configuration.websocketProvider.on('status', this.onStatus.bind(this))
169
+
170
+ this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect)
171
+ this.configuration.websocketProvider.on('disconnect', (e: Event) => this.emit('disconnect', e))
172
+
173
+ this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy)
174
+ this.configuration.websocketProvider.on('destroy', (e: Event) => this.emit('destroy', e))
224
175
 
225
176
  this.awareness.on('update', () => {
226
177
  this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) })
@@ -234,11 +185,6 @@ export class HocuspocusProvider extends EventEmitter {
234
185
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
235
186
  this.registerEventListeners()
236
187
 
237
- this.intervals.connectionChecker = setInterval(
238
- this.checkConnection.bind(this),
239
- this.configuration.messageReconnectTimeout / 10,
240
- )
241
-
242
188
  if (this.configuration.forceSyncInterval) {
243
189
  this.intervals.forceSync = setInterval(
244
190
  this.forceSync.bind(this),
@@ -246,128 +192,20 @@ export class HocuspocusProvider extends EventEmitter {
246
192
  )
247
193
  }
248
194
 
249
- if (typeof configuration.connect !== 'undefined') {
250
- this.shouldConnect = configuration.connect
251
- }
195
+ this.configuration.websocketProvider.attach(this)
196
+ }
252
197
 
253
- if (!this.shouldConnect) {
254
- return
255
- }
198
+ public onStatus({ status } : onStatusParameters) {
199
+ this.status = status
256
200
 
257
- this.connect()
201
+ this.configuration.onStatus({ status })
202
+ this.emit('status', { status })
258
203
  }
259
204
 
260
205
  public setConfiguration(configuration: Partial<HocuspocusProviderConfiguration> = {}): void {
261
206
  this.configuration = { ...this.configuration, ...configuration }
262
207
  }
263
208
 
264
- boundConnect = this.connect.bind(this)
265
-
266
- cancelWebsocketRetry?: () => void
267
-
268
- async connect() {
269
- if (this.status === WebSocketStatus.Connected) {
270
- return
271
- }
272
-
273
- // Always cancel any previously initiated connection retryer instances
274
- if (this.cancelWebsocketRetry) {
275
- this.cancelWebsocketRetry()
276
- this.cancelWebsocketRetry = undefined
277
- }
278
-
279
- this.unsyncedChanges = 0 // set to 0 in case we got reconnected
280
- this.shouldConnect = true
281
- this.subscribeToBroadcastChannel()
282
-
283
- const abortableRetry = () => {
284
- let cancelAttempt = false
285
-
286
- const retryPromise = retry(this.createWebSocketConnection.bind(this), {
287
- delay: this.configuration.delay,
288
- initialDelay: this.configuration.initialDelay,
289
- factor: this.configuration.factor,
290
- maxAttempts: this.configuration.maxAttempts,
291
- minDelay: this.configuration.minDelay,
292
- maxDelay: this.configuration.maxDelay,
293
- jitter: this.configuration.jitter,
294
- timeout: this.configuration.timeout,
295
- beforeAttempt: context => {
296
- if (!this.shouldConnect || cancelAttempt) {
297
- context.abort()
298
- }
299
- },
300
- }).catch((error: any) => {
301
- // If we aborted the connection attempt then don’t throw an error
302
- // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
303
- if (error && error.code !== 'ATTEMPT_ABORTED') {
304
- throw error
305
- }
306
- })
307
-
308
- return {
309
- retryPromise,
310
- cancelFunc: () => {
311
- cancelAttempt = true
312
- },
313
- }
314
- }
315
-
316
- const { retryPromise, cancelFunc } = abortableRetry()
317
- this.cancelWebsocketRetry = cancelFunc
318
-
319
- return retryPromise
320
- }
321
-
322
- createWebSocketConnection() {
323
- return new Promise((resolve, reject) => {
324
- if (this.webSocket) {
325
- this.webSocket.close()
326
- this.webSocket = null
327
- }
328
-
329
- // Init the WebSocket connection
330
- const ws = new this.configuration.WebSocketPolyfill(this.url)
331
- ws.binaryType = 'arraybuffer'
332
- ws.onmessage = this.onMessage.bind(this)
333
- ws.onclose = this.onClose.bind(this)
334
- ws.onopen = this.onOpen.bind(this)
335
- ws.onerror = (err: any) => {
336
- reject(err)
337
- }
338
- this.webSocket = ws
339
-
340
- // Reset the status
341
- this.synced = false
342
- this.status = WebSocketStatus.Connecting
343
- this.emit('status', { status: WebSocketStatus.Connecting })
344
-
345
- // Store resolve/reject for later use
346
- this.connectionAttempt = {
347
- resolve,
348
- reject,
349
- }
350
- })
351
- }
352
-
353
- resolveConnectionAttempt() {
354
- this.connectionAttempt?.resolve()
355
- this.connectionAttempt = null
356
-
357
- this.status = WebSocketStatus.Connected
358
- this.emit('status', { status: WebSocketStatus.Connected })
359
- this.emit('connect')
360
- }
361
-
362
- stopConnectionAttempt() {
363
- this.connectionAttempt = null
364
- }
365
-
366
- rejectConnectionAttempt() {
367
- this.connectionAttempt?.reject()
368
- this.connectionAttempt = null
369
- }
370
-
371
209
  get document() {
372
210
  return this.configuration.document
373
211
  }
@@ -380,33 +218,8 @@ export class HocuspocusProvider extends EventEmitter {
380
218
  return this.unsyncedChanges > 0
381
219
  }
382
220
 
383
- checkConnection() {
384
- // Don’t check the connection when it’s not even established
385
- if (this.status !== WebSocketStatus.Connected) {
386
- return
387
- }
388
-
389
- // Don’t close then connection while waiting for the first message
390
- if (!this.lastMessageReceived) {
391
- return
392
- }
393
-
394
- // Don’t close the connection when a message was received recently
395
- if (this.configuration.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
396
- return
397
- }
398
-
399
- // No message received in a long time, not even your own
400
- // Awareness updates, which are updated every 15 seconds.
401
- this.webSocket?.close()
402
- }
403
-
404
221
  forceSync() {
405
- if (!this.webSocket) {
406
- return
407
- }
408
-
409
- this.send(SyncStepOneMessage, { document: this.document })
222
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
410
223
  }
411
224
 
412
225
  boundBeforeUnload = this.beforeUnload.bind(this)
@@ -420,17 +233,20 @@ export class HocuspocusProvider extends EventEmitter {
420
233
  return
421
234
  }
422
235
 
423
- window.addEventListener('online', this.boundConnect)
424
236
  window.addEventListener('beforeunload', this.boundBeforeUnload)
425
237
  }
426
238
 
239
+ sendStateless(payload: string) {
240
+ this.send(StatelessMessage, { documentName: this.configuration.name, payload })
241
+ }
242
+
427
243
  documentUpdateHandler(update: Uint8Array, origin: any) {
428
244
  if (origin === this) {
429
245
  return
430
246
  }
431
247
 
432
248
  this.unsyncedChanges += 1
433
- this.send(UpdateMessage, { update }, true)
249
+ this.send(UpdateMessage, { update, documentName: this.configuration.name }, true)
434
250
  }
435
251
 
436
252
  awarenessUpdateHandler({ added, updated, removed }: any, origin: any) {
@@ -439,37 +255,10 @@ export class HocuspocusProvider extends EventEmitter {
439
255
  this.send(AwarenessMessage, {
440
256
  awareness: this.awareness,
441
257
  clients: changedClients,
258
+ documentName: this.configuration.name,
442
259
  }, true)
443
260
  }
444
261
 
445
- permissionDeniedHandler(reason: string) {
446
- this.emit('authenticationFailed', { reason })
447
- this.isAuthenticated = false
448
- this.shouldConnect = false
449
- }
450
-
451
- authenticatedHandler() {
452
- this.isAuthenticated = true
453
-
454
- this.emit('authenticated')
455
- this.startSync()
456
- }
457
-
458
- // Ensure that the URL always ends with /
459
- get serverUrl() {
460
- while (this.configuration.url[this.configuration.url.length - 1] === '/') {
461
- return this.configuration.url.slice(0, this.configuration.url.length - 1)
462
- }
463
-
464
- return this.configuration.url
465
- }
466
-
467
- get url() {
468
- const encodedParams = url.encodeQueryParams(this.configuration.parameters)
469
-
470
- return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
471
- }
472
-
473
262
  get synced(): boolean {
474
263
  return this.isSynced
475
264
  }
@@ -484,23 +273,17 @@ export class HocuspocusProvider extends EventEmitter {
484
273
  this.emit('sync', { state })
485
274
  }
486
275
 
276
+ receiveStateless(payload: string) {
277
+ this.emit('stateless', { payload })
278
+ }
279
+
487
280
  get isAuthenticationRequired(): boolean {
488
281
  return !!this.configuration.token && !this.isAuthenticated
489
282
  }
490
283
 
491
284
  disconnect() {
492
- this.shouldConnect = false
493
285
  this.disconnectBroadcastChannel()
494
-
495
- if (this.webSocket === null) {
496
- return
497
- }
498
-
499
- try {
500
- this.webSocket.close()
501
- } catch {
502
- //
503
- }
286
+ this.configuration.websocketProvider.detach(this)
504
287
  }
505
288
 
506
289
  async onOpen(event: Event) {
@@ -509,6 +292,7 @@ export class HocuspocusProvider extends EventEmitter {
509
292
  if (this.isAuthenticationRequired) {
510
293
  this.send(AuthenticationMessage, {
511
294
  token: await this.getToken(),
295
+ documentName: this.configuration.name,
512
296
  })
513
297
  return
514
298
  }
@@ -526,97 +310,54 @@ export class HocuspocusProvider extends EventEmitter {
526
310
  }
527
311
 
528
312
  startSync() {
529
- this.send(SyncStepOneMessage, { document: this.document })
313
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
530
314
 
531
315
  if (this.awareness.getLocalState() !== null) {
532
316
  this.send(AwarenessMessage, {
533
317
  awareness: this.awareness,
534
318
  clients: [this.document.clientID],
319
+ documentName: this.configuration.name,
535
320
  })
536
321
  }
537
322
  }
538
323
 
539
- send(Message: ConstructableOutgoingMessage, args: any, broadcast = false) {
324
+ send(message: ConstructableOutgoingMessage, args: any, broadcast = false) {
540
325
  if (broadcast) {
541
- this.mux(() => { this.broadcast(Message, args) })
326
+ this.mux(() => { this.broadcast(message, args) })
542
327
  }
543
328
 
544
- if (this.webSocket?.readyState === WsReadyStates.Open) {
545
- const messageSender = new MessageSender(Message, args)
329
+ const messageSender = new MessageSender(message, args)
546
330
 
547
- this.emit('outgoingMessage', { message: messageSender.message })
548
- messageSender.send(this.webSocket)
549
- }
331
+ this.emit('outgoingMessage', { message: messageSender.message })
332
+ messageSender.send(this.configuration.websocketProvider)
550
333
  }
551
334
 
552
335
  onMessage(event: MessageEvent) {
553
- this.resolveConnectionAttempt()
336
+ const message = new IncomingMessage(event.data)
554
337
 
555
- this.lastMessageReceived = time.getUnixTime()
338
+ const documentName = message.readVarString()
556
339
 
557
- const message = new IncomingMessage(event.data)
340
+ if (documentName !== this.configuration.name) {
341
+ return // message is meant for another provider
342
+ }
343
+
344
+ message.writeVarString(documentName)
558
345
 
559
- this.emit('message', { event, message })
346
+ this.emit('message', { event, message: new IncomingMessage(event.data) })
560
347
 
561
348
  new MessageReceiver(message).apply(this)
562
349
  }
563
350
 
564
351
  onClose(event: CloseEvent) {
565
- this.emit('close', { event })
566
-
567
- this.webSocket = null
568
352
  this.isAuthenticated = false
569
353
  this.synced = false
570
354
 
571
- if (this.status === WebSocketStatus.Connected) {
572
- // update awareness (all users except local left)
573
- removeAwarenessStates(
574
- this.awareness,
575
- Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID),
576
- this,
577
- )
578
-
579
- this.status = WebSocketStatus.Disconnected
580
- this.emit('status', { status: WebSocketStatus.Disconnected })
581
- this.emit('disconnect', { event })
582
- }
583
-
584
- if (event.code === Unauthorized.code) {
585
- if (!this.configuration.quiet) {
586
- console.warn('[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.')
587
- }
588
-
589
- this.shouldConnect = false
590
- }
591
-
592
- if (event.code === Forbidden.code) {
593
- if (!this.configuration.quiet) {
594
- console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.')
595
- }
596
- }
597
-
598
- if (this.connectionAttempt) {
599
- // That connection attempt failed.
600
- this.rejectConnectionAttempt()
601
- } else if (this.shouldConnect) {
602
- // The connection was closed by the server. Let’s just try again.
603
- this.connect()
604
- }
605
-
606
- // If we’ll reconnect, we’re done for now.
607
- if (this.shouldConnect) {
608
- return
609
- }
610
-
611
- // The status is set correctly already.
612
- if (this.status === WebSocketStatus.Disconnected) {
613
- return
614
- }
615
-
616
- // Let’s update the connection status.
617
- this.status = WebSocketStatus.Disconnected
618
- this.emit('status', { status: WebSocketStatus.Disconnected })
619
- this.emit('disconnect', { event })
355
+ // update awareness (all users except local left)
356
+ removeAwarenessStates(
357
+ this.awareness,
358
+ Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID),
359
+ this,
360
+ )
620
361
  }
621
362
 
622
363
  destroy() {
@@ -626,15 +367,8 @@ export class HocuspocusProvider extends EventEmitter {
626
367
  clearInterval(this.intervals.forceSync)
627
368
  }
628
369
 
629
- clearInterval(this.intervals.connectionChecker)
630
-
631
370
  removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
632
371
 
633
- // If there is still a connection attempt outstanding then we should stop
634
- // it before calling disconnect, otherwise it will be rejected in the onClose
635
- // handler and trigger a retry
636
- this.stopConnectionAttempt()
637
-
638
372
  this.disconnect()
639
373
 
640
374
  this.awareness.off('update', this.awarenessUpdateHandler)
@@ -646,12 +380,25 @@ export class HocuspocusProvider extends EventEmitter {
646
380
  return
647
381
  }
648
382
 
649
- window.removeEventListener('online', this.boundConnect)
650
383
  window.removeEventListener('beforeunload', this.boundBeforeUnload)
651
384
  }
652
385
 
386
+ permissionDeniedHandler(reason: string) {
387
+ this.emit('authenticationFailed', { reason })
388
+ this.isAuthenticated = false
389
+ this.disconnect()
390
+ this.status = WebSocketStatus.Disconnected
391
+ }
392
+
393
+ authenticatedHandler() {
394
+ this.isAuthenticated = true
395
+
396
+ this.emit('authenticated')
397
+ this.startSync()
398
+ }
399
+
653
400
  get broadcastChannel() {
654
- return `${this.serverUrl}/${this.configuration.name}`
401
+ return `${this.configuration.name}`
655
402
  }
656
403
 
657
404
  boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
@@ -659,6 +406,11 @@ export class HocuspocusProvider extends EventEmitter {
659
406
  broadcastChannelSubscriber(data: ArrayBuffer) {
660
407
  this.mux(() => {
661
408
  const message = new IncomingMessage(data)
409
+
410
+ const documentName = message.readVarString()
411
+
412
+ message.writeVarString(documentName)
413
+
662
414
  new MessageReceiver(message)
663
415
  .setBroadcasted(true)
664
416
  .apply(this, false)
@@ -674,8 +426,8 @@ export class HocuspocusProvider extends EventEmitter {
674
426
  this.mux(() => {
675
427
  this.broadcast(SyncStepOneMessage, { document: this.document })
676
428
  this.broadcast(SyncStepTwoMessage, { document: this.document })
677
- this.broadcast(QueryAwarenessMessage)
678
- this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID] })
429
+ this.broadcast(QueryAwarenessMessage, { document: this.document })
430
+ this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], document: this.document })
679
431
  })
680
432
  }
681
433
 
@@ -685,6 +437,7 @@ export class HocuspocusProvider extends EventEmitter {
685
437
  awareness: this.awareness,
686
438
  clients: [this.document.clientID],
687
439
  states: new Map(),
440
+ documentName: this.configuration.name,
688
441
  }, true)
689
442
 
690
443
  if (this.subscribedToBroadcastChannel) {