@hocuspocus/provider 1.1.1 → 2.0.0-alpha.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 (40) hide show
  1. package/dist/hocuspocus-provider.cjs +370 -217
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +370 -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 +11 -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/OutgoingMessages/CloseMessage.d.ts +8 -0
  12. package/dist/packages/provider/src/index.d.ts +1 -0
  13. package/dist/packages/provider/src/types.d.ts +3 -1
  14. package/dist/packages/server/src/Connection.d.ts +6 -12
  15. package/dist/packages/server/src/Hocuspocus.d.ts +3 -8
  16. package/dist/packages/server/src/IncomingMessage.d.ts +1 -0
  17. package/dist/packages/server/src/MessageReceiver.d.ts +1 -2
  18. package/dist/packages/server/src/OutgoingMessage.d.ts +1 -1
  19. package/dist/packages/server/src/types.d.ts +2 -1
  20. package/dist/tests/utils/index.d.ts +1 -0
  21. package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
  22. package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +3 -0
  23. package/package.json +4 -3
  24. package/src/HocuspocusCloudProvider.ts +8 -1
  25. package/src/HocuspocusProvider.ts +108 -361
  26. package/src/HocuspocusProviderWebsocket.ts +475 -0
  27. package/src/IncomingMessage.ts +10 -0
  28. package/src/MessageReceiver.ts +4 -2
  29. package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
  30. package/src/OutgoingMessages/AwarenessMessage.ts +1 -0
  31. package/src/OutgoingMessages/CloseMessage.ts +16 -0
  32. package/src/OutgoingMessages/QueryAwarenessMessage.ts +5 -0
  33. package/src/OutgoingMessages/StatelessMessage.ts +1 -0
  34. package/src/OutgoingMessages/SyncStepOneMessage.ts +1 -0
  35. package/src/OutgoingMessages/SyncStepTwoMessage.ts +1 -1
  36. package/src/OutgoingMessages/UpdateMessage.ts +3 -1
  37. package/src/index.ts +1 -0
  38. package/src/types.ts +2 -0
  39. package/dist/tests/server/getDocumentName.d.ts +0 -1
  40. /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,27 @@ 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 { CloseMessage } from './OutgoingMessages/CloseMessage'
32
+ import { onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.'
27
33
 
28
34
  export type HocuspocusProviderConfiguration =
29
- Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>>
35
+ Required<Pick<CompleteHocuspocusProviderConfiguration, 'name' | 'websocketProvider'>>
30
36
  & Partial<CompleteHocuspocusProviderConfiguration>
31
37
 
32
38
  export interface CompleteHocuspocusProviderConfiguration {
33
- /**
34
- * URL of your @hocuspocus/server instance
35
- */
36
- url: string,
37
39
  /**
38
40
  * The identifier/name of your document
39
41
  */
@@ -42,10 +44,7 @@ export interface CompleteHocuspocusProviderConfiguration {
42
44
  * The actual Y.js document
43
45
  */
44
46
  document: Y.Doc,
45
- /**
46
- * Pass `false` to start the connection manually.
47
- */
48
- connect: boolean,
47
+
49
48
  /**
50
49
  * Pass false to disable broadcasting between browser tabs.
51
50
  */
@@ -63,49 +62,14 @@ export interface CompleteHocuspocusProviderConfiguration {
63
62
  */
64
63
  parameters: { [key: string]: any },
65
64
  /**
66
- * An optional WebSocket polyfill, for example for Node.js
65
+ * Hocuspocus websocket provider
67
66
  */
68
- WebSocketPolyfill: any,
67
+ websocketProvider: HocuspocusProviderWebsocket,
69
68
  /**
70
69
  * Force syncing the document in the defined interval.
71
70
  */
72
71
  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,
72
+
109
73
  onAuthenticated: () => void,
110
74
  onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void,
111
75
  onOpen: (data: onOpenParameters) => void,
@@ -130,35 +94,14 @@ export interface CompleteHocuspocusProviderConfiguration {
130
94
  export class HocuspocusProvider extends EventEmitter {
131
95
  public configuration: CompleteHocuspocusProviderConfiguration = {
132
96
  name: '',
133
- url: '',
134
97
  // @ts-ignore
135
98
  document: undefined,
136
99
  // @ts-ignore
137
100
  awareness: undefined,
138
- WebSocketPolyfill: undefined,
139
101
  token: null,
140
102
  parameters: {},
141
- connect: true,
142
103
  broadcast: true,
143
104
  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
105
  onAuthenticated: () => null,
163
106
  onAuthenticationFailed: () => null,
164
107
  onOpen: () => null,
@@ -178,55 +121,59 @@ export class HocuspocusProvider extends EventEmitter {
178
121
 
179
122
  subscribedToBroadcastChannel = false
180
123
 
181
- webSocket: WebSocket | null = null
182
-
183
- shouldConnect = true
184
-
185
- status = WebSocketStatus.Disconnected
186
-
187
124
  isSynced = false
188
125
 
189
126
  unsyncedChanges = 0
190
127
 
191
- isAuthenticated = false
128
+ status = WebSocketStatus.Disconnected
192
129
 
193
- lastMessageReceived = 0
130
+ isAuthenticated = false
194
131
 
195
132
  mux = mutex.createMutex()
196
133
 
197
134
  intervals: any = {
198
135
  forceSync: null,
199
- connectionChecker: null,
200
136
  }
201
137
 
202
- connectionAttempt: {
203
- resolve: (value?: any) => void
204
- reject: (reason?: any) => void
205
- } | null = null
206
-
207
138
  constructor(configuration: HocuspocusProviderConfiguration) {
208
139
  super()
209
140
  this.setConfiguration(configuration)
210
141
 
211
142
  this.configuration.document = configuration.document ? configuration.document : new Y.Doc()
212
143
  this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document)
213
- this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket
214
144
 
215
145
  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
146
  this.on('message', this.configuration.onMessage)
220
147
  this.on('outgoingMessage', this.configuration.onOutgoingMessage)
221
148
  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
149
  this.on('destroy', this.configuration.onDestroy)
226
150
  this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
227
151
  this.on('awarenessChange', this.configuration.onAwarenessChange)
228
152
  this.on('stateless', this.configuration.onStateless)
229
153
 
154
+ this.on('authenticated', this.configuration.onAuthenticated)
155
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed)
156
+
157
+ this.configuration.websocketProvider.on('connect', this.configuration.onConnect)
158
+ this.configuration.websocketProvider.on('connect', (e: Event) => this.emit('connect', e))
159
+
160
+ this.configuration.websocketProvider.on('open', this.onOpen.bind(this))
161
+ this.configuration.websocketProvider.on('open', (e: Event) => this.emit('open', e))
162
+
163
+ this.configuration.websocketProvider.on('message', this.onMessage.bind(this))
164
+
165
+ this.configuration.websocketProvider.on('close', this.onClose.bind(this))
166
+ this.configuration.websocketProvider.on('close', this.configuration.onClose)
167
+ this.configuration.websocketProvider.on('close', (e: Event) => this.emit('close', e))
168
+
169
+ this.configuration.websocketProvider.on('status', this.onStatus.bind(this))
170
+
171
+ this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect)
172
+ this.configuration.websocketProvider.on('disconnect', (e: Event) => this.emit('disconnect', e))
173
+
174
+ this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy)
175
+ this.configuration.websocketProvider.on('destroy', (e: Event) => this.emit('destroy', e))
176
+
230
177
  this.awareness.on('update', () => {
231
178
  this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) })
232
179
  })
@@ -239,11 +186,6 @@ export class HocuspocusProvider extends EventEmitter {
239
186
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
240
187
  this.registerEventListeners()
241
188
 
242
- this.intervals.connectionChecker = setInterval(
243
- this.checkConnection.bind(this),
244
- this.configuration.messageReconnectTimeout / 10,
245
- )
246
-
247
189
  if (this.configuration.forceSyncInterval) {
248
190
  this.intervals.forceSync = setInterval(
249
191
  this.forceSync.bind(this),
@@ -251,128 +193,20 @@ export class HocuspocusProvider extends EventEmitter {
251
193
  )
252
194
  }
253
195
 
254
- if (typeof configuration.connect !== 'undefined') {
255
- this.shouldConnect = configuration.connect
256
- }
196
+ this.configuration.websocketProvider.attach(this)
197
+ }
257
198
 
258
- if (!this.shouldConnect) {
259
- return
260
- }
199
+ public onStatus({ status } : onStatusParameters) {
200
+ this.status = status
261
201
 
262
- this.connect()
202
+ this.configuration.onStatus({ status })
203
+ this.emit('status', { status })
263
204
  }
264
205
 
265
206
  public setConfiguration(configuration: Partial<HocuspocusProviderConfiguration> = {}): void {
266
207
  this.configuration = { ...this.configuration, ...configuration }
267
208
  }
268
209
 
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
210
  get document() {
377
211
  return this.configuration.document
378
212
  }
@@ -385,33 +219,8 @@ export class HocuspocusProvider extends EventEmitter {
385
219
  return this.unsyncedChanges > 0
386
220
  }
387
221
 
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
222
  forceSync() {
410
- if (!this.webSocket) {
411
- return
412
- }
413
-
414
- this.send(SyncStepOneMessage, { document: this.document })
223
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
415
224
  }
416
225
 
417
226
  boundBeforeUnload = this.beforeUnload.bind(this)
@@ -425,12 +234,11 @@ export class HocuspocusProvider extends EventEmitter {
425
234
  return
426
235
  }
427
236
 
428
- window.addEventListener('online', this.boundConnect)
429
237
  window.addEventListener('beforeunload', this.boundBeforeUnload)
430
238
  }
431
239
 
432
240
  sendStateless(payload: string) {
433
- this.send(StatelessMessage, { payload })
241
+ this.send(StatelessMessage, { documentName: this.configuration.name, payload })
434
242
  }
435
243
 
436
244
  documentUpdateHandler(update: Uint8Array, origin: any) {
@@ -439,7 +247,7 @@ export class HocuspocusProvider extends EventEmitter {
439
247
  }
440
248
 
441
249
  this.unsyncedChanges += 1
442
- this.send(UpdateMessage, { update }, true)
250
+ this.send(UpdateMessage, { update, documentName: this.configuration.name }, true)
443
251
  }
444
252
 
445
253
  awarenessUpdateHandler({ added, updated, removed }: any, origin: any) {
@@ -448,37 +256,10 @@ export class HocuspocusProvider extends EventEmitter {
448
256
  this.send(AwarenessMessage, {
449
257
  awareness: this.awareness,
450
258
  clients: changedClients,
259
+ documentName: this.configuration.name,
451
260
  }, true)
452
261
  }
453
262
 
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
263
  get synced(): boolean {
483
264
  return this.isSynced
484
265
  }
@@ -501,19 +282,14 @@ export class HocuspocusProvider extends EventEmitter {
501
282
  return !!this.configuration.token && !this.isAuthenticated
502
283
  }
503
284
 
285
+ // not needed, but provides backward compatibility with e.g. lexicla/yjs
286
+ async connect() {
287
+ return this.configuration.websocketProvider.connect()
288
+ }
289
+
504
290
  disconnect() {
505
- this.shouldConnect = false
506
291
  this.disconnectBroadcastChannel()
507
-
508
- if (this.webSocket === null) {
509
- return
510
- }
511
-
512
- try {
513
- this.webSocket.close()
514
- } catch {
515
- //
516
- }
292
+ this.configuration.websocketProvider.detach(this)
517
293
  }
518
294
 
519
295
  async onOpen(event: Event) {
@@ -522,8 +298,8 @@ export class HocuspocusProvider extends EventEmitter {
522
298
  if (this.isAuthenticationRequired) {
523
299
  this.send(AuthenticationMessage, {
524
300
  token: await this.getToken(),
301
+ documentName: this.configuration.name,
525
302
  })
526
- return
527
303
  }
528
304
 
529
305
  this.startSync()
@@ -539,97 +315,54 @@ export class HocuspocusProvider extends EventEmitter {
539
315
  }
540
316
 
541
317
  startSync() {
542
- this.send(SyncStepOneMessage, { document: this.document })
318
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
543
319
 
544
320
  if (this.awareness.getLocalState() !== null) {
545
321
  this.send(AwarenessMessage, {
546
322
  awareness: this.awareness,
547
323
  clients: [this.document.clientID],
324
+ documentName: this.configuration.name,
548
325
  })
549
326
  }
550
327
  }
551
328
 
552
- send(Message: ConstructableOutgoingMessage, args: any, broadcast = false) {
329
+ send(message: ConstructableOutgoingMessage, args: any, broadcast = false) {
553
330
  if (broadcast) {
554
- this.mux(() => { this.broadcast(Message, args) })
331
+ this.mux(() => { this.broadcast(message, args) })
555
332
  }
556
333
 
557
- if (this.webSocket?.readyState === WsReadyStates.Open) {
558
- const messageSender = new MessageSender(Message, args)
334
+ const messageSender = new MessageSender(message, args)
559
335
 
560
- this.emit('outgoingMessage', { message: messageSender.message })
561
- messageSender.send(this.webSocket)
562
- }
336
+ this.emit('outgoingMessage', { message: messageSender.message })
337
+ messageSender.send(this.configuration.websocketProvider)
563
338
  }
564
339
 
565
340
  onMessage(event: MessageEvent) {
566
- this.resolveConnectionAttempt()
341
+ const message = new IncomingMessage(event.data)
567
342
 
568
- this.lastMessageReceived = time.getUnixTime()
343
+ const documentName = message.readVarString()
569
344
 
570
- const message = new IncomingMessage(event.data)
345
+ if (documentName !== this.configuration.name) {
346
+ return // message is meant for another provider
347
+ }
348
+
349
+ message.writeVarString(documentName)
571
350
 
572
- this.emit('message', { event, message })
351
+ this.emit('message', { event, message: new IncomingMessage(event.data) })
573
352
 
574
353
  new MessageReceiver(message).apply(this)
575
354
  }
576
355
 
577
356
  onClose(event: CloseEvent) {
578
- this.emit('close', { event })
579
-
580
- this.webSocket = null
581
357
  this.isAuthenticated = false
582
358
  this.synced = false
583
359
 
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 })
360
+ // update awareness (all users except local left)
361
+ removeAwarenessStates(
362
+ this.awareness,
363
+ Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID),
364
+ this,
365
+ )
633
366
  }
634
367
 
635
368
  destroy() {
@@ -639,15 +372,8 @@ export class HocuspocusProvider extends EventEmitter {
639
372
  clearInterval(this.intervals.forceSync)
640
373
  }
641
374
 
642
- clearInterval(this.intervals.connectionChecker)
643
-
644
375
  removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
645
376
 
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
377
  this.disconnect()
652
378
 
653
379
  this.awareness.off('update', this.awarenessUpdateHandler)
@@ -655,16 +381,31 @@ export class HocuspocusProvider extends EventEmitter {
655
381
 
656
382
  this.removeAllListeners()
657
383
 
384
+ this.send(CloseMessage, { documentName: this.configuration.name })
385
+
658
386
  if (typeof window === 'undefined') {
659
387
  return
660
388
  }
661
389
 
662
- window.removeEventListener('online', this.boundConnect)
663
390
  window.removeEventListener('beforeunload', this.boundBeforeUnload)
664
391
  }
665
392
 
393
+ permissionDeniedHandler(reason: string) {
394
+ this.emit('authenticationFailed', { reason })
395
+ this.isAuthenticated = false
396
+ this.disconnect()
397
+ this.status = WebSocketStatus.Disconnected
398
+ }
399
+
400
+ authenticatedHandler() {
401
+ this.isAuthenticated = true
402
+
403
+ this.emit('authenticated')
404
+ this.startSync()
405
+ }
406
+
666
407
  get broadcastChannel() {
667
- return `${this.serverUrl}/${this.configuration.name}`
408
+ return `${this.configuration.name}`
668
409
  }
669
410
 
670
411
  boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
@@ -672,6 +413,11 @@ export class HocuspocusProvider extends EventEmitter {
672
413
  broadcastChannelSubscriber(data: ArrayBuffer) {
673
414
  this.mux(() => {
674
415
  const message = new IncomingMessage(data)
416
+
417
+ const documentName = message.readVarString()
418
+
419
+ message.writeVarString(documentName)
420
+
675
421
  new MessageReceiver(message)
676
422
  .setBroadcasted(true)
677
423
  .apply(this, false)
@@ -687,8 +433,8 @@ export class HocuspocusProvider extends EventEmitter {
687
433
  this.mux(() => {
688
434
  this.broadcast(SyncStepOneMessage, { document: this.document })
689
435
  this.broadcast(SyncStepTwoMessage, { document: this.document })
690
- this.broadcast(QueryAwarenessMessage)
691
- this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID] })
436
+ this.broadcast(QueryAwarenessMessage, { document: this.document })
437
+ this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], document: this.document })
692
438
  })
693
439
  }
694
440
 
@@ -698,6 +444,7 @@ export class HocuspocusProvider extends EventEmitter {
698
444
  awareness: this.awareness,
699
445
  clients: [this.document.clientID],
700
446
  states: new Map(),
447
+ documentName: this.configuration.name,
701
448
  }, true)
702
449
 
703
450
  if (this.subscribedToBroadcastChannel) {