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