@hocuspocus/provider 1.0.0-alpha.12 → 1.0.0-alpha.16

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.
@@ -10,4 +10,5 @@ export declare class IncomingMessage {
10
10
  readVarUint8Array(): Uint8Array;
11
11
  writeVarUint(type: MessageType): void;
12
12
  writeVarUint8Array(data: Uint8Array): void;
13
+ length(): number;
13
14
  }
@@ -2,7 +2,9 @@ import { HocuspocusProvider } from './HocuspocusProvider';
2
2
  import { IncomingMessage } from './IncomingMessage';
3
3
  export declare class MessageReceiver {
4
4
  message: IncomingMessage;
5
+ broadcasted: boolean;
5
6
  constructor(message: IncomingMessage);
7
+ setBroadcasted(value: boolean): this;
6
8
  apply(provider: HocuspocusProvider, emitSynced?: boolean): void;
7
9
  private applySyncMessage;
8
10
  private applyAwarenessMessage;
@@ -1,15 +1,9 @@
1
1
  import { Encoder } from 'lib0/encoding';
2
- import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage';
3
- import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage';
4
- import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage';
5
- import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage';
6
- import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage';
7
- import { UpdateMessage } from './OutgoingMessages/UpdateMessage';
8
- import { Constructable } from './types';
2
+ import { ConstructableOutgoingMessage } from './types';
9
3
  export declare class MessageSender {
10
4
  encoder: Encoder;
11
5
  message: any;
12
- constructor(Message: Constructable<AuthenticationMessage> | Constructable<AwarenessMessage> | Constructable<QueryAwarenessMessage> | Constructable<SyncStepOneMessage> | Constructable<SyncStepTwoMessage> | Constructable<UpdateMessage>, args?: any);
6
+ constructor(Message: ConstructableOutgoingMessage, args?: any);
13
7
  create(): Uint8Array;
14
8
  send(webSocket: any): void;
15
9
  broadcast(channel: string): void;
@@ -1,8 +1,9 @@
1
1
  import { Encoder } from 'lib0/encoding';
2
- import { MessageType, OutgoingMessageInterface } from './types';
2
+ import { MessageType, OutgoingMessageArguments, OutgoingMessageInterface } from './types';
3
3
  export declare class OutgoingMessage implements OutgoingMessageInterface {
4
4
  encoder: Encoder;
5
5
  type?: MessageType;
6
6
  constructor();
7
+ get(args: Partial<OutgoingMessageArguments>): Encoder | undefined;
7
8
  toUint8Array(): Uint8Array;
8
9
  }
@@ -1,6 +1,12 @@
1
1
  import { Awareness } from 'y-protocols/awareness';
2
2
  import * as Y from 'yjs';
3
- import * as encoding from 'lib0/encoding';
3
+ import { Encoder } from 'lib0/encoding';
4
+ import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage';
5
+ import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage';
6
+ import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage';
7
+ import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage';
8
+ import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage';
9
+ import { UpdateMessage } from './OutgoingMessages/UpdateMessage';
4
10
  export declare enum MessageType {
5
11
  Sync = 0,
6
12
  Awareness = 1,
@@ -8,7 +14,7 @@ export declare enum MessageType {
8
14
  QueryAwareness = 3
9
15
  }
10
16
  export interface OutgoingMessageInterface {
11
- encoder: encoding.Encoder;
17
+ encoder: Encoder;
12
18
  type?: MessageType;
13
19
  }
14
20
  export interface OutgoingMessageArguments {
@@ -20,7 +26,9 @@ export interface OutgoingMessageArguments {
20
26
  [key: string]: any;
21
27
  }>;
22
28
  update: any;
29
+ encoder: Encoder;
23
30
  }
24
31
  export interface Constructable<T> {
25
32
  new (...args: any): T;
26
33
  }
34
+ export declare type ConstructableOutgoingMessage = Constructable<AuthenticationMessage> | Constructable<AwarenessMessage> | Constructable<QueryAwarenessMessage> | Constructable<SyncStepOneMessage> | Constructable<SyncStepTwoMessage> | Constructable<UpdateMessage>;
@@ -40,10 +40,10 @@ declare class Connection {
40
40
  */
41
41
  private check;
42
42
  /**
43
- * Send first sync step
43
+ * Send the current document awareness to the client, if any
44
44
  * @private
45
45
  */
46
- private sendFirstSyncStep;
46
+ private sendCurrentAwareness;
47
47
  /**
48
48
  * Handle an incoming message
49
49
  * @private
@@ -43,9 +43,9 @@ declare class Document extends Doc {
43
43
  */
44
44
  removeConnection(connection: Connection): Document;
45
45
  /**
46
- * Get the number of active connections
46
+ * Get the number of active connections for this document
47
47
  */
48
- connectionsCount(): number;
48
+ getConnectionsCount(): number;
49
49
  /**
50
50
  * Get an array of registered connections
51
51
  */
@@ -8,7 +8,7 @@ export declare const defaultConfiguration: {
8
8
  timeout: number;
9
9
  };
10
10
  /**
11
- * Hocuspocus yjs websocket server
11
+ * Hocuspocus server
12
12
  */
13
13
  export declare class Hocuspocus {
14
14
  configuration: Configuration;
@@ -26,7 +26,15 @@ export declare class Hocuspocus {
26
26
  */
27
27
  listen(): Promise<void>;
28
28
  /**
29
- * Force closes one or more connections
29
+ * Get the total number of active documents
30
+ */
31
+ getDocumentsCount(): number;
32
+ /**
33
+ * Get the total number of active connections
34
+ */
35
+ getConnectionsCount(): number;
36
+ /**
37
+ * Force close one or more connections
30
38
  */
31
39
  closeConnections(documentName?: string): void;
32
40
  /**
@@ -34,7 +42,7 @@ export declare class Hocuspocus {
34
42
  */
35
43
  destroy(): Promise<any>;
36
44
  /**
37
- * Handle the incoming websocket connection
45
+ * Handle the incoming WebSocket connection
38
46
  */
39
47
  handleConnection(incoming: WebSocket, request: IncomingMessage, documentName: string, context?: any): void;
40
48
  /**
@@ -69,7 +77,7 @@ export declare class Hocuspocus {
69
77
  */
70
78
  private static getDocumentName;
71
79
  enableDebugging(): void;
72
- enableLogging(): void;
80
+ enableMessageLogging(): void;
73
81
  disableLogging(): void;
74
82
  disableDebugging(): void;
75
83
  flushMessageLogs(): this;
@@ -77,6 +77,7 @@ export interface onCreateDocumentPayload {
77
77
  context: any;
78
78
  document: Document;
79
79
  documentName: string;
80
+ instance: Hocuspocus;
80
81
  requestHeaders: IncomingHttpHeaders;
81
82
  requestParameters: URLSearchParams;
82
83
  socketId: string;
@@ -87,6 +88,7 @@ export interface onChangePayload {
87
88
  context: any;
88
89
  document: Document;
89
90
  documentName: string;
91
+ instance: Hocuspocus;
90
92
  requestHeaders: IncomingHttpHeaders;
91
93
  requestParameters: URLSearchParams;
92
94
  update: Uint8Array;
@@ -97,6 +99,7 @@ export interface onDisconnectPayload {
97
99
  context: any;
98
100
  document: Document;
99
101
  documentName: string;
102
+ instance: Hocuspocus;
100
103
  requestHeaders: IncomingHttpHeaders;
101
104
  requestParameters: URLSearchParams;
102
105
  socketId: string;
@@ -116,6 +119,7 @@ export interface onListenPayload {
116
119
  port: number;
117
120
  }
118
121
  export interface onDestroyPayload {
122
+ instance: Hocuspocus;
119
123
  }
120
124
  export interface onConfigurePayload {
121
125
  configuration: Configuration;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/provider",
3
- "version": "1.0.0-alpha.12",
3
+ "version": "1.0.0-alpha.16",
4
4
  "description": "hocuspocus provider",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -23,11 +23,12 @@
23
23
  "dist"
24
24
  ],
25
25
  "dependencies": {
26
+ "@lifeomic/attempt": "^3.0.0",
26
27
  "lib0": "^0.2.42",
27
28
  "y-protocols": "^1.0.5",
28
29
  "yjs": "^13.5.8"
29
30
  },
30
- "gitHead": "f4b2c62f0322daa4c95f6be2009fc7a9c48557cb",
31
+ "gitHead": "981e76320fd0bac82cbbdb027ec3c91a4c6cfce4",
31
32
  "publishConfig": {
32
33
  "access": "public"
33
34
  }
@@ -1,13 +1,11 @@
1
- // @ts-nocheck
2
1
  import * as Y from 'yjs'
3
2
  import * as bc from 'lib0/broadcastchannel'
4
3
  import * as time from 'lib0/time'
5
4
  import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
6
5
  import * as mutex from 'lib0/mutex'
7
- import * as math from 'lib0/math'
8
6
  import * as url from 'lib0/url'
9
-
10
7
  import { CloseEvent, MessageEvent, OpenEvent } from 'ws'
8
+ import { retry } from '@lifeomic/attempt'
11
9
  import EventEmitter from './EventEmitter'
12
10
  import { IncomingMessage } from './IncomingMessage'
13
11
  import { MessageReceiver } from './MessageReceiver'
@@ -20,6 +18,7 @@ import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage'
20
18
  import { UpdateMessage } from './OutgoingMessages/UpdateMessage'
21
19
  import { OutgoingMessage } from './OutgoingMessage'
22
20
  import awarenessStatesToArray from './utils/awarenessStatesToArray'
21
+ import { ConstructableOutgoingMessage } from './types'
23
22
 
24
23
  export enum WebSocketStatus {
25
24
  Connecting = 'connecting',
@@ -28,21 +27,84 @@ export enum WebSocketStatus {
28
27
  }
29
28
 
30
29
  export interface HocuspocusProviderOptions {
30
+ /**
31
+ * URL of your @hocuspocus/server instance
32
+ */
31
33
  url: string,
34
+ /**
35
+ * The identifier/name of your document
36
+ */
32
37
  name: string,
38
+ /**
39
+ * The actual Y.js document
40
+ */
33
41
  document: Y.Doc,
42
+ /**
43
+ * Pass `false` to start the connection manually.
44
+ */
34
45
  connect: boolean,
46
+ /**
47
+ * Pass false to disable broadcasting between browser tabs.
48
+ */
35
49
  broadcast: boolean,
50
+ /**
51
+ * An Awareness instance to keep the presence state of all clients.
52
+ */
36
53
  awareness: Awareness,
37
- token: string,
54
+ /**
55
+ * A token that’s sent to the backend for authentication purposes.
56
+ */
57
+ token: string | (() => string) | (() => Promise<string>) | null,
58
+ /**
59
+ * URL parameters that should be added.
60
+ */
38
61
  parameters: { [key: string]: any },
62
+ /**
63
+ * An optional WebSocket polyfill, for example for Node.js
64
+ */
39
65
  WebSocketPolyfill: any,
66
+ /**
67
+ * Force syncing the document in the defined interval.
68
+ */
40
69
  forceSyncInterval: false | number,
41
- reconnectTimeoutBase: number,
42
- maxReconnectTimeout: number,
70
+ /**
71
+ * Disconnect when no message is received for the defined amount of milliseconds.
72
+ */
43
73
  messageReconnectTimeout: number,
74
+ /**
75
+ * The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.
76
+ */
77
+ delay: number,
78
+ /**
79
+ * 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.
80
+ */
81
+ initialDelay: number,
82
+ /**
83
+ * The factor option is used to grow the delay exponentially.
84
+ */
85
+ factor: number,
86
+ /**
87
+ * The maximum number of attempts or 0 if there is no limit on number of attempts.
88
+ */
89
+ maxAttempts: number,
90
+ /**
91
+ * minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.
92
+ */
93
+ minDelay: number,
94
+ /**
95
+ * 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.
96
+ */
97
+ maxDelay: number,
98
+ /**
99
+ * If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.
100
+ */
101
+ jitter: boolean,
102
+ /**
103
+ * 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.
104
+ */
105
+ timeout: number,
44
106
  onAuthenticated: () => void,
45
- onAuthenticationFailed: ({ reason: string }) => void,
107
+ onAuthenticationFailed: ({ reason }: { reason: string }) => void,
46
108
  onOpen: (event: OpenEvent) => void,
47
109
  onConnect: () => void,
48
110
  onMessage: (event: MessageEvent) => void,
@@ -54,23 +116,40 @@ export interface HocuspocusProviderOptions {
54
116
  onDestroy: () => void,
55
117
  onAwarenessUpdate: (states: any) => void,
56
118
  onAwarenessChange: (states: any) => void,
57
- debug: boolean,
58
119
  }
59
120
 
60
121
  export class HocuspocusProvider extends EventEmitter {
61
122
  public options: HocuspocusProviderOptions = {
123
+ // @ts-ignore
124
+ document: undefined,
125
+ // @ts-ignore
126
+ awareness: undefined,
127
+ WebSocketPolyfill: undefined,
62
128
  url: '',
63
129
  name: '',
64
130
  token: null,
65
131
  parameters: {},
66
- debug: false,
67
132
  connect: true,
68
133
  broadcast: true,
69
134
  forceSyncInterval: false,
70
- reconnectTimeoutBase: 1200,
71
- maxReconnectTimeout: 2500,
72
135
  // TODO: this should depend on awareness.outdatedTime
73
136
  messageReconnectTimeout: 30000,
137
+ // 1 second
138
+ delay: 1000,
139
+ // instant
140
+ initialDelay: 0,
141
+ // double the delay each time
142
+ factor: 2,
143
+ // unlimited retries
144
+ maxAttempts: 0,
145
+ // wait at least 1 second
146
+ minDelay: 1000,
147
+ // at least every 30 seconds
148
+ maxDelay: 30000,
149
+ // randomize
150
+ jitter: true,
151
+ // retry forever
152
+ timeout: 0,
74
153
  onAuthenticated: () => null,
75
154
  onAuthenticationFailed: () => null,
76
155
  onOpen: () => null,
@@ -86,17 +165,13 @@ export class HocuspocusProvider extends EventEmitter {
86
165
  onAwarenessChange: () => null,
87
166
  }
88
167
 
89
- awareness: Awareness
90
-
91
168
  subscribedToBroadcastChannel = false
92
169
 
93
170
  webSocket: any = null
94
171
 
95
172
  shouldConnect = true
96
173
 
97
- status: WebSocketStatus = WebSocketStatus.Disconnected
98
-
99
- failedConnectionAttempts = 0
174
+ status = WebSocketStatus.Disconnected
100
175
 
101
176
  isSynced = false
102
177
 
@@ -111,15 +186,17 @@ export class HocuspocusProvider extends EventEmitter {
111
186
  connectionChecker: null,
112
187
  }
113
188
 
189
+ connectionAttempt: {
190
+ resolve: (value?: any) => void
191
+ reject: (reason?: any) => void
192
+ } | null = null
193
+
114
194
  constructor(options: Partial<HocuspocusProviderOptions> = {}) {
115
195
  super()
116
-
117
196
  this.setOptions(options)
118
197
 
119
- this.options.document = options.document ? options.document : new Y.Doc()
120
198
  this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document)
121
199
  this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket
122
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect
123
200
 
124
201
  this.on('open', this.options.onOpen)
125
202
  this.on('authenticated', this.options.onAuthenticated)
@@ -136,26 +213,22 @@ export class HocuspocusProvider extends EventEmitter {
136
213
  this.on('awarenessChange', this.options.onAwarenessChange)
137
214
 
138
215
  this.awareness.on('update', () => {
139
- this.emit('awarenessUpdate', {
140
- states: awarenessStatesToArray(this.awareness.getStates()),
141
- })
216
+ this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) })
142
217
  })
143
218
 
144
219
  this.awareness.on('change', () => {
145
- this.emit('awarenessChange', {
146
- states: awarenessStatesToArray(this.awareness.getStates()),
147
- })
220
+ this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) })
148
221
  })
149
222
 
223
+ this.document.on('update', this.documentUpdateHandler.bind(this))
224
+ this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
225
+ this.registerBeforeUnloadEventListener()
226
+
150
227
  this.intervals.connectionChecker = setInterval(
151
228
  this.checkConnection.bind(this),
152
229
  this.options.messageReconnectTimeout / 10,
153
230
  )
154
231
 
155
- this.document.on('update', this.documentUpdateHandler.bind(this))
156
- this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
157
- this.registerBeforeUnloadEventListener()
158
-
159
232
  if (this.options.forceSyncInterval) {
160
233
  this.intervals.forceSync = setInterval(
161
234
  this.forceSync.bind(this),
@@ -163,15 +236,76 @@ export class HocuspocusProvider extends EventEmitter {
163
236
  )
164
237
  }
165
238
 
166
- if (this.options.connect) {
167
- this.connect()
239
+ if (typeof options.connect !== 'undefined') {
240
+ this.shouldConnect = options.connect
168
241
  }
242
+
243
+ if (!this.shouldConnect) {
244
+ return
245
+ }
246
+
247
+ this.connect()
169
248
  }
170
249
 
171
250
  public setOptions(options: Partial<HocuspocusProviderOptions> = {}): void {
172
251
  this.options = { ...this.options, ...options }
173
252
  }
174
253
 
254
+ connect() {
255
+ if (this.status === WebSocketStatus.Connected) {
256
+ return
257
+ }
258
+
259
+ this.shouldConnect = true
260
+ this.subscribeToBroadcastChannel()
261
+
262
+ retry(this.createWebSocketConnection.bind(this), {
263
+ delay: this.options.delay,
264
+ initialDelay: this.options.initialDelay,
265
+ factor: this.options.factor,
266
+ maxAttempts: this.options.maxAttempts,
267
+ minDelay: this.options.minDelay,
268
+ maxDelay: this.options.maxDelay,
269
+ jitter: this.options.jitter,
270
+ timeout: this.options.timeout,
271
+ })
272
+ }
273
+
274
+ createWebSocketConnection() {
275
+ return new Promise((resolve, reject) => {
276
+ // Init the WebSocket connection
277
+ this.webSocket = new this.options.WebSocketPolyfill(this.url)
278
+ this.webSocket.binaryType = 'arraybuffer'
279
+ this.webSocket.onmessage = this.onMessage.bind(this)
280
+ this.webSocket.onclose = this.onClose.bind(this)
281
+ this.webSocket.onopen = this.onOpen.bind(this)
282
+ this.webSocket.onerror = () => {
283
+ reject()
284
+ }
285
+
286
+ // Reset the status
287
+ this.synced = false
288
+ this.status = WebSocketStatus.Connecting
289
+ this.emit('status', { status: 'connecting' })
290
+
291
+ // Store resolve/reject for later use
292
+ this.connectionAttempt = {
293
+ resolve,
294
+ reject,
295
+ }
296
+ })
297
+ }
298
+
299
+ resolveConnectionAttempt() {
300
+ this.connectionAttempt?.resolve()
301
+ this.connectionAttempt = null
302
+ }
303
+
304
+ rejectConnectionAttempt() {
305
+ this.connectionAttempt?.reject()
306
+ this.connectionAttempt = null
307
+ }
308
+
175
309
  get document() {
176
310
  return this.options.document
177
311
  }
@@ -181,12 +315,12 @@ export class HocuspocusProvider extends EventEmitter {
181
315
  }
182
316
 
183
317
  checkConnection() {
184
- // Don’t close the connection when it’s not established anyway
318
+ // Don’t check the connection when it’s not even established
185
319
  if (this.status !== WebSocketStatus.Connected) {
186
320
  return
187
321
  }
188
322
 
189
- // Don’t just close then connection while waiting for the first message
323
+ // Don’t close then connection while waiting for the first message
190
324
  if (!this.lastMessageReceived) {
191
325
  return
192
326
  }
@@ -238,8 +372,6 @@ export class HocuspocusProvider extends EventEmitter {
238
372
 
239
373
  permissionDeniedHandler(reason: string) {
240
374
  this.emit('authenticationFailed', { reason })
241
- this.log('Permission denied', reason)
242
-
243
375
  this.isAuthenticated = false
244
376
  this.shouldConnect = false
245
377
  }
@@ -284,15 +416,6 @@ export class HocuspocusProvider extends EventEmitter {
284
416
  return !!this.options.token && !this.isAuthenticated
285
417
  }
286
418
 
287
- connect() {
288
- this.shouldConnect = true
289
-
290
- if (this.status !== WebSocketStatus.Connected) {
291
- this.createWebSocketConnection()
292
- this.subscribeToBroadcastChannel()
293
- }
294
- }
295
-
296
419
  disconnect() {
297
420
  this.shouldConnect = false
298
421
  this.disconnectBroadcastChannel()
@@ -308,24 +431,6 @@ export class HocuspocusProvider extends EventEmitter {
308
431
  }
309
432
  }
310
433
 
311
- createWebSocketConnection() {
312
- if (this.webSocket !== null) {
313
- return
314
- }
315
-
316
- this.webSocket = new this.options.WebSocketPolyfill(this.url)
317
- this.webSocket.binaryType = 'arraybuffer'
318
-
319
- this.status = WebSocketStatus.Connecting
320
- this.synced = false
321
-
322
- this.webSocket.onmessage = this.onMessage.bind(this)
323
- this.webSocket.onclose = this.onClose.bind(this)
324
- this.webSocket.onopen = this.onOpen.bind(this)
325
-
326
- this.emit('status', { status: 'connecting' })
327
- }
328
-
329
434
  onOpen(event: OpenEvent) {
330
435
  this.emit('open', { event })
331
436
 
@@ -334,18 +439,30 @@ export class HocuspocusProvider extends EventEmitter {
334
439
  }
335
440
  }
336
441
 
337
- webSocketConnectionEstablished() {
338
- this.failedConnectionAttempts = 0
442
+ async getToken() {
443
+ if (typeof this.options.token === 'function') {
444
+ const token = await this.options.token()
445
+ return token
446
+ }
447
+
448
+ return this.options.token
449
+ }
450
+
451
+ async webSocketConnectionEstablished() {
339
452
  this.status = WebSocketStatus.Connected
340
453
  this.emit('status', { status: 'connected' })
341
454
  this.emit('connect')
342
455
 
343
456
  if (this.isAuthenticationRequired) {
344
- this.send(AuthenticationMessage, { token: this.options.token })
457
+ this.send(AuthenticationMessage, {
458
+ token: await this.getToken(),
459
+ })
345
460
  return
346
461
  }
347
462
 
348
463
  this.startSync()
464
+
465
+ this.resolveConnectionAttempt()
349
466
  }
350
467
 
351
468
  startSync() {
@@ -359,11 +476,9 @@ export class HocuspocusProvider extends EventEmitter {
359
476
  }
360
477
  }
361
478
 
362
- send(Message: OutgoingMessage, args: any, broadcast = false) {
479
+ send(Message: ConstructableOutgoingMessage, args: any, broadcast = false) {
363
480
  if (broadcast) {
364
- this.mux(() => {
365
- this.broadcast(Message, args)
366
- })
481
+ this.mux(() => { this.broadcast(Message, args) })
367
482
  }
368
483
 
369
484
  if (this.status === WebSocketStatus.Connected) {
@@ -382,23 +497,16 @@ export class HocuspocusProvider extends EventEmitter {
382
497
  this.emit('message', { event, message })
383
498
 
384
499
  new MessageReceiver(message).apply(this)
385
-
386
- // TODO: What’s that doing?
387
- // Move to the MessageReceiver
388
- // if (encoding.length(encoder) > 1) {
389
- // this.send(encoding.toUint8Array(encoder))
390
- // }
391
500
  }
392
501
 
393
502
  onClose(event: CloseEvent) {
394
503
  this.emit('close', { event })
395
504
 
396
- this.isAuthenticated = false
397
505
  this.webSocket = null
506
+ this.isAuthenticated = false
507
+ this.synced = false
398
508
 
399
509
  if (this.status === WebSocketStatus.Connected) {
400
- this.synced = false
401
-
402
510
  // update awareness (all users except local left)
403
511
  removeAwarenessStates(
404
512
  this.awareness,
@@ -409,27 +517,30 @@ export class HocuspocusProvider extends EventEmitter {
409
517
  this.status = WebSocketStatus.Disconnected
410
518
  this.emit('status', { status: 'disconnected' })
411
519
  this.emit('disconnect', { event })
412
- } else {
413
- this.failedConnectionAttempts += 1
414
520
  }
415
521
 
416
- if (this.shouldConnect) {
417
- const wait = math.round(math.min(
418
- math.log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase,
419
- this.options.maxReconnectTimeout,
420
- ))
421
-
422
- this.log(`[close] Reconnecting in ${wait}ms …`)
423
- setTimeout(this.createWebSocketConnection.bind(this), wait)
522
+ if (this.connectionAttempt) {
523
+ // Okay, that connection attempt failed …
524
+ this.rejectConnectionAttempt()
525
+ } else if (this.shouldConnect) {
526
+ // The connection was closed by the server, so let’s just try again.
527
+ this.connect()
528
+ }
424
529
 
530
+ // If we’ll reconnect anyway, we’re done for now.
531
+ if (this.shouldConnect) {
425
532
  return
426
533
  }
427
534
 
428
- if (this.status !== WebSocketStatus.Disconnected) {
429
- this.status = WebSocketStatus.Disconnected
430
- this.emit('status', { status: 'disconnected' })
431
- this.emit('disconnect', { event })
535
+ // The status is set correctly already.
536
+ if (this.status === WebSocketStatus.Disconnected) {
537
+ return
432
538
  }
539
+
540
+ // Let’s update the connection status.
541
+ this.status = WebSocketStatus.Disconnected
542
+ this.emit('status', { status: 'disconnected' })
543
+ this.emit('disconnect', { event })
433
544
  }
434
545
 
435
546
  destroy() {
@@ -456,12 +567,9 @@ export class HocuspocusProvider extends EventEmitter {
456
567
  broadcastChannelSubscriber(data: ArrayBuffer) {
457
568
  this.mux(() => {
458
569
  const message = new IncomingMessage(data)
459
- const encoder = new MessageReceiver(message, this).apply(this, false)
460
-
461
- // TODO: What’s that doing?
462
- // if (encoding.length(encoder) > 1) {
463
- // this.broadcast(encoding.toUint8Array(encoder))
464
- // }
570
+ new MessageReceiver(message)
571
+ .setBroadcasted(true)
572
+ .apply(this, false)
465
573
  })
466
574
  }
467
575
 
@@ -493,7 +601,7 @@ export class HocuspocusProvider extends EventEmitter {
493
601
  }
494
602
  }
495
603
 
496
- broadcast(Message: OutgoingMessage, args: any) {
604
+ broadcast(Message: ConstructableOutgoingMessage, args?: any) {
497
605
  if (!this.options.broadcast) {
498
606
  return
499
607
  }
@@ -505,14 +613,6 @@ export class HocuspocusProvider extends EventEmitter {
505
613
  new MessageSender(Message, args).broadcast(this.broadcastChannel)
506
614
  }
507
615
 
508
- log(message: string): void {
509
- if (!this.options.debug) {
510
- return
511
- }
512
-
513
- console.log(message)
514
- }
515
-
516
616
  setAwarenessField(key: string, value: any) {
517
617
  this.awareness.setLocalStateField(key, value)
518
618
  }