@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.
- package/dist/hocuspocus-provider.cjs +370 -217
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +370 -218
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-monitor/src/Dashboard.d.ts +1 -1
- package/dist/packages/extension-monitor/src/index.d.ts +1 -1
- package/dist/packages/provider/src/HocuspocusCloudProvider.d.ts +2 -1
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +11 -69
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +115 -0
- package/dist/packages/provider/src/IncomingMessage.d.ts +2 -0
- package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +8 -0
- package/dist/packages/provider/src/index.d.ts +1 -0
- package/dist/packages/provider/src/types.d.ts +3 -1
- package/dist/packages/server/src/Connection.d.ts +6 -12
- package/dist/packages/server/src/Hocuspocus.d.ts +3 -8
- package/dist/packages/server/src/IncomingMessage.d.ts +1 -0
- package/dist/packages/server/src/MessageReceiver.d.ts +1 -2
- package/dist/packages/server/src/OutgoingMessage.d.ts +1 -1
- package/dist/packages/server/src/types.d.ts +2 -1
- package/dist/tests/utils/index.d.ts +1 -0
- package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
- package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +3 -0
- package/package.json +4 -3
- package/src/HocuspocusCloudProvider.ts +8 -1
- package/src/HocuspocusProvider.ts +108 -361
- package/src/HocuspocusProviderWebsocket.ts +475 -0
- package/src/IncomingMessage.ts +10 -0
- package/src/MessageReceiver.ts +4 -2
- package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
- package/src/OutgoingMessages/AwarenessMessage.ts +1 -0
- package/src/OutgoingMessages/CloseMessage.ts +16 -0
- package/src/OutgoingMessages/QueryAwarenessMessage.ts +5 -0
- package/src/OutgoingMessages/StatelessMessage.ts +1 -0
- package/src/OutgoingMessages/SyncStepOneMessage.ts +1 -0
- package/src/OutgoingMessages/SyncStepTwoMessage.ts +1 -1
- package/src/OutgoingMessages/UpdateMessage.ts +3 -1
- package/src/index.ts +1 -0
- package/src/types.ts +2 -0
- package/dist/tests/server/getDocumentName.d.ts +0 -1
- /package/dist/tests/{provider → providerwebsocket}/configuration.d.ts +0 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import * as time from 'lib0/time'
|
|
2
|
+
import * as mutex from 'lib0/mutex'
|
|
3
|
+
import * as url from 'lib0/url'
|
|
4
|
+
import type { MessageEvent } from 'ws'
|
|
5
|
+
import { retry } from '@lifeomic/attempt'
|
|
6
|
+
import {
|
|
7
|
+
Forbidden, Unauthorized, WsReadyStates,
|
|
8
|
+
} from '@hocuspocus/common'
|
|
9
|
+
import { Event } from 'ws'
|
|
10
|
+
import EventEmitter from './EventEmitter'
|
|
11
|
+
import {
|
|
12
|
+
onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters, WebSocketStatus,
|
|
13
|
+
} from './types'
|
|
14
|
+
import { HocuspocusProvider, onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.'
|
|
15
|
+
|
|
16
|
+
export type HocuspocusProviderWebsocketConfiguration =
|
|
17
|
+
Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>>
|
|
18
|
+
& Partial<CompleteHocuspocusProviderWebsocketConfiguration>
|
|
19
|
+
|
|
20
|
+
export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
21
|
+
/**
|
|
22
|
+
* URL of your @hocuspocus/server instance
|
|
23
|
+
*/
|
|
24
|
+
url: string,
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pass `false` to start the connection manually.
|
|
28
|
+
*/
|
|
29
|
+
connect: boolean,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* URL parameters that should be added.
|
|
33
|
+
*/
|
|
34
|
+
parameters: { [key: string]: any },
|
|
35
|
+
/**
|
|
36
|
+
* An optional WebSocket polyfill, for example for Node.js
|
|
37
|
+
*/
|
|
38
|
+
WebSocketPolyfill: any,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Disconnect when no message is received for the defined amount of milliseconds.
|
|
42
|
+
*/
|
|
43
|
+
messageReconnectTimeout: number,
|
|
44
|
+
/**
|
|
45
|
+
* The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.
|
|
46
|
+
*/
|
|
47
|
+
delay: number,
|
|
48
|
+
/**
|
|
49
|
+
* 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.
|
|
50
|
+
*/
|
|
51
|
+
initialDelay: number,
|
|
52
|
+
/**
|
|
53
|
+
* The factor option is used to grow the delay exponentially.
|
|
54
|
+
*/
|
|
55
|
+
factor: number,
|
|
56
|
+
/**
|
|
57
|
+
* The maximum number of attempts or 0 if there is no limit on number of attempts.
|
|
58
|
+
*/
|
|
59
|
+
maxAttempts: number,
|
|
60
|
+
/**
|
|
61
|
+
* minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.
|
|
62
|
+
*/
|
|
63
|
+
minDelay: number,
|
|
64
|
+
/**
|
|
65
|
+
* 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.
|
|
66
|
+
*/
|
|
67
|
+
maxDelay: number,
|
|
68
|
+
/**
|
|
69
|
+
* If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.
|
|
70
|
+
*/
|
|
71
|
+
jitter: boolean,
|
|
72
|
+
/**
|
|
73
|
+
* 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.
|
|
74
|
+
*/
|
|
75
|
+
timeout: number,
|
|
76
|
+
onOpen: (data: onOpenParameters) => void,
|
|
77
|
+
onConnect: () => void,
|
|
78
|
+
onMessage: (data: onMessageParameters) => void,
|
|
79
|
+
onOutgoingMessage: (data: onOutgoingMessageParameters) => void,
|
|
80
|
+
onStatus: (data: onStatusParameters) => void,
|
|
81
|
+
onDisconnect: (data: onDisconnectParameters) => void,
|
|
82
|
+
onClose: (data: onCloseParameters) => void,
|
|
83
|
+
onDestroy: () => void,
|
|
84
|
+
onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void,
|
|
85
|
+
onAwarenessChange: (data: onAwarenessChangeParameters) => void,
|
|
86
|
+
/**
|
|
87
|
+
* Don’t output any warnings.
|
|
88
|
+
*/
|
|
89
|
+
quiet: boolean,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
93
|
+
public configuration: CompleteHocuspocusProviderWebsocketConfiguration = {
|
|
94
|
+
url: '',
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
document: undefined,
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
awareness: undefined,
|
|
99
|
+
WebSocketPolyfill: undefined,
|
|
100
|
+
parameters: {},
|
|
101
|
+
connect: true,
|
|
102
|
+
broadcast: true,
|
|
103
|
+
forceSyncInterval: false,
|
|
104
|
+
// TODO: this should depend on awareness.outdatedTime
|
|
105
|
+
messageReconnectTimeout: 30000,
|
|
106
|
+
// 1 second
|
|
107
|
+
delay: 1000,
|
|
108
|
+
// instant
|
|
109
|
+
initialDelay: 0,
|
|
110
|
+
// double the delay each time
|
|
111
|
+
factor: 2,
|
|
112
|
+
// unlimited retries
|
|
113
|
+
maxAttempts: 0,
|
|
114
|
+
// wait at least 1 second
|
|
115
|
+
minDelay: 1000,
|
|
116
|
+
// at least every 30 seconds
|
|
117
|
+
maxDelay: 30000,
|
|
118
|
+
// randomize
|
|
119
|
+
jitter: true,
|
|
120
|
+
// retry forever
|
|
121
|
+
timeout: 0,
|
|
122
|
+
onOpen: () => null,
|
|
123
|
+
onConnect: () => null,
|
|
124
|
+
onMessage: () => null,
|
|
125
|
+
onOutgoingMessage: () => null,
|
|
126
|
+
onStatus: () => null,
|
|
127
|
+
onDisconnect: () => null,
|
|
128
|
+
onClose: () => null,
|
|
129
|
+
onDestroy: () => null,
|
|
130
|
+
onAwarenessUpdate: () => null,
|
|
131
|
+
onAwarenessChange: () => null,
|
|
132
|
+
quiet: false,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
subscribedToBroadcastChannel = false
|
|
136
|
+
|
|
137
|
+
webSocket: WebSocket | null = null
|
|
138
|
+
|
|
139
|
+
shouldConnect = true
|
|
140
|
+
|
|
141
|
+
status = WebSocketStatus.Disconnected
|
|
142
|
+
|
|
143
|
+
lastMessageReceived = 0
|
|
144
|
+
|
|
145
|
+
mux = mutex.createMutex()
|
|
146
|
+
|
|
147
|
+
intervals: any = {
|
|
148
|
+
forceSync: null,
|
|
149
|
+
connectionChecker: null,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
connectionAttempt: {
|
|
153
|
+
resolve: (value?: any) => void
|
|
154
|
+
reject: (reason?: any) => void
|
|
155
|
+
} | null = null
|
|
156
|
+
|
|
157
|
+
constructor(configuration: HocuspocusProviderWebsocketConfiguration) {
|
|
158
|
+
super()
|
|
159
|
+
this.setConfiguration(configuration)
|
|
160
|
+
|
|
161
|
+
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket
|
|
162
|
+
|
|
163
|
+
this.on('open', this.configuration.onOpen)
|
|
164
|
+
this.on('open', this.onOpen.bind(this))
|
|
165
|
+
this.on('connect', this.configuration.onConnect)
|
|
166
|
+
this.on('message', this.configuration.onMessage)
|
|
167
|
+
this.on('outgoingMessage', this.configuration.onOutgoingMessage)
|
|
168
|
+
this.on('status', this.configuration.onStatus)
|
|
169
|
+
this.on('status', this.onStatus.bind(this))
|
|
170
|
+
this.on('disconnect', this.configuration.onDisconnect)
|
|
171
|
+
this.on('close', this.configuration.onClose)
|
|
172
|
+
this.on('destroy', this.configuration.onDestroy)
|
|
173
|
+
this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
|
|
174
|
+
this.on('awarenessChange', this.configuration.onAwarenessChange)
|
|
175
|
+
|
|
176
|
+
this.on('close', this.onClose.bind(this))
|
|
177
|
+
this.on('message', this.onMessage.bind(this))
|
|
178
|
+
|
|
179
|
+
this.registerEventListeners()
|
|
180
|
+
|
|
181
|
+
this.intervals.connectionChecker = setInterval(
|
|
182
|
+
this.checkConnection.bind(this),
|
|
183
|
+
this.configuration.messageReconnectTimeout / 10,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if (typeof configuration.connect !== 'undefined') {
|
|
187
|
+
this.shouldConnect = configuration.connect
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!this.shouldConnect) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.connect()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
receivedOnOpenPayload?: Event | undefined = undefined
|
|
198
|
+
|
|
199
|
+
receivedOnStatusPayload?: onStatusParameters | undefined = undefined
|
|
200
|
+
|
|
201
|
+
async onOpen(event: Event) {
|
|
202
|
+
this.receivedOnOpenPayload = event
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async onStatus(data: onStatusParameters) {
|
|
206
|
+
this.receivedOnStatusPayload = data
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
attach(provider: HocuspocusProvider) {
|
|
210
|
+
if (this.receivedOnOpenPayload) {
|
|
211
|
+
provider.onOpen(this.receivedOnOpenPayload)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (this.receivedOnStatusPayload) {
|
|
215
|
+
provider.onStatus(this.receivedOnStatusPayload)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
detach(provider: HocuspocusProvider) {
|
|
220
|
+
// tell the server to remove the listener
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public setConfiguration(configuration: Partial<HocuspocusProviderWebsocketConfiguration> = {}): void {
|
|
225
|
+
this.configuration = { ...this.configuration, ...configuration }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
boundConnect = this.connect.bind(this)
|
|
229
|
+
|
|
230
|
+
cancelWebsocketRetry?: () => void
|
|
231
|
+
|
|
232
|
+
async connect() {
|
|
233
|
+
if (this.status === WebSocketStatus.Connected) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Always cancel any previously initiated connection retryer instances
|
|
238
|
+
if (this.cancelWebsocketRetry) {
|
|
239
|
+
this.cancelWebsocketRetry()
|
|
240
|
+
this.cancelWebsocketRetry = undefined
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.shouldConnect = true
|
|
244
|
+
|
|
245
|
+
const abortableRetry = () => {
|
|
246
|
+
let cancelAttempt = false
|
|
247
|
+
|
|
248
|
+
const retryPromise = retry(this.createWebSocketConnection.bind(this), {
|
|
249
|
+
delay: this.configuration.delay,
|
|
250
|
+
initialDelay: this.configuration.initialDelay,
|
|
251
|
+
factor: this.configuration.factor,
|
|
252
|
+
maxAttempts: this.configuration.maxAttempts,
|
|
253
|
+
minDelay: this.configuration.minDelay,
|
|
254
|
+
maxDelay: this.configuration.maxDelay,
|
|
255
|
+
jitter: this.configuration.jitter,
|
|
256
|
+
timeout: this.configuration.timeout,
|
|
257
|
+
beforeAttempt: context => {
|
|
258
|
+
if (!this.shouldConnect || cancelAttempt) {
|
|
259
|
+
context.abort()
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
}).catch((error: any) => {
|
|
263
|
+
// If we aborted the connection attempt then don’t throw an error
|
|
264
|
+
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
265
|
+
if (error && error.code !== 'ATTEMPT_ABORTED') {
|
|
266
|
+
throw error
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
retryPromise,
|
|
272
|
+
cancelFunc: () => {
|
|
273
|
+
cancelAttempt = true
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const { retryPromise, cancelFunc } = abortableRetry()
|
|
279
|
+
this.cancelWebsocketRetry = cancelFunc
|
|
280
|
+
|
|
281
|
+
return retryPromise
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
createWebSocketConnection() {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
if (this.webSocket) {
|
|
287
|
+
this.webSocket.close()
|
|
288
|
+
this.webSocket = null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Init the WebSocket connection
|
|
292
|
+
const ws = new this.configuration.WebSocketPolyfill(this.url)
|
|
293
|
+
ws.binaryType = 'arraybuffer'
|
|
294
|
+
ws.onmessage = (payload: any) => this.emit('message', payload)
|
|
295
|
+
ws.onclose = (payload: any) => this.emit('close', { event: payload })
|
|
296
|
+
ws.onopen = (payload: any) => this.emit('open', payload)
|
|
297
|
+
ws.onerror = (err: any) => {
|
|
298
|
+
reject(err)
|
|
299
|
+
}
|
|
300
|
+
this.webSocket = ws
|
|
301
|
+
|
|
302
|
+
// Reset the status
|
|
303
|
+
this.status = WebSocketStatus.Connecting
|
|
304
|
+
this.emit('status', { status: WebSocketStatus.Connecting })
|
|
305
|
+
|
|
306
|
+
// Store resolve/reject for later use
|
|
307
|
+
this.connectionAttempt = {
|
|
308
|
+
resolve,
|
|
309
|
+
reject,
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
onMessage(event: MessageEvent) {
|
|
315
|
+
this.resolveConnectionAttempt()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
resolveConnectionAttempt() {
|
|
319
|
+
if (this.connectionAttempt) {
|
|
320
|
+
this.connectionAttempt.resolve()
|
|
321
|
+
this.connectionAttempt = null
|
|
322
|
+
|
|
323
|
+
this.status = WebSocketStatus.Connected
|
|
324
|
+
this.emit('status', { status: WebSocketStatus.Connected })
|
|
325
|
+
this.emit('connect')
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
stopConnectionAttempt() {
|
|
330
|
+
this.connectionAttempt = null
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
rejectConnectionAttempt() {
|
|
334
|
+
this.connectionAttempt?.reject()
|
|
335
|
+
this.connectionAttempt = null
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
checkConnection() {
|
|
339
|
+
// Don’t check the connection when it’s not even established
|
|
340
|
+
if (this.status !== WebSocketStatus.Connected) {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Don’t close then connection while waiting for the first message
|
|
345
|
+
if (!this.lastMessageReceived) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Don’t close the connection when a message was received recently
|
|
350
|
+
if (this.configuration.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// No message received in a long time, not even your own
|
|
355
|
+
// Awareness updates, which are updated every 15 seconds.
|
|
356
|
+
this.webSocket?.close()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
registerEventListeners() {
|
|
360
|
+
if (typeof window === 'undefined') {
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
window.addEventListener('online', this.boundConnect)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Ensure that the URL always ends with /
|
|
368
|
+
get serverUrl() {
|
|
369
|
+
while (this.configuration.url[this.configuration.url.length - 1] === '/') {
|
|
370
|
+
return this.configuration.url.slice(0, this.configuration.url.length - 1)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return this.configuration.url
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
get url() {
|
|
377
|
+
const encodedParams = url.encodeQueryParams(this.configuration.parameters)
|
|
378
|
+
|
|
379
|
+
return `${this.serverUrl}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
disconnect() {
|
|
383
|
+
this.shouldConnect = false
|
|
384
|
+
|
|
385
|
+
if (this.webSocket === null) {
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
this.webSocket.close()
|
|
391
|
+
} catch {
|
|
392
|
+
//
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
send(message: any) {
|
|
397
|
+
if (this.webSocket?.readyState === WsReadyStates.Open) {
|
|
398
|
+
this.webSocket.send(message)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
onClose({ event }: onCloseParameters) {
|
|
403
|
+
this.webSocket = null
|
|
404
|
+
|
|
405
|
+
if (this.status === WebSocketStatus.Connected) {
|
|
406
|
+
this.status = WebSocketStatus.Disconnected
|
|
407
|
+
this.emit('status', { status: WebSocketStatus.Disconnected })
|
|
408
|
+
this.emit('disconnect', { event })
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (event.code === Unauthorized.code) {
|
|
412
|
+
if (!this.configuration.quiet) {
|
|
413
|
+
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.')
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this.shouldConnect = false
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (event.code === Forbidden.code) {
|
|
420
|
+
if (!this.configuration.quiet) {
|
|
421
|
+
console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.')
|
|
422
|
+
return // TODO REMOVE ME
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (this.connectionAttempt) {
|
|
427
|
+
// That connection attempt failed.
|
|
428
|
+
this.rejectConnectionAttempt()
|
|
429
|
+
} else if (this.shouldConnect) {
|
|
430
|
+
// The connection was closed by the server. Let’s just try again.
|
|
431
|
+
this.connect()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// If we’ll reconnect, we’re done for now.
|
|
435
|
+
if (this.shouldConnect) {
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The status is set correctly already.
|
|
440
|
+
if (this.status === WebSocketStatus.Disconnected) {
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Let’s update the connection status.
|
|
445
|
+
this.status = WebSocketStatus.Disconnected
|
|
446
|
+
this.emit('status', { status: WebSocketStatus.Disconnected })
|
|
447
|
+
this.emit('disconnect', { event })
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
destroy() {
|
|
451
|
+
this.emit('destroy')
|
|
452
|
+
|
|
453
|
+
if (this.intervals.forceSync) {
|
|
454
|
+
clearInterval(this.intervals.forceSync)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
clearInterval(this.intervals.connectionChecker)
|
|
458
|
+
|
|
459
|
+
// If there is still a connection attempt outstanding then we should stop
|
|
460
|
+
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
461
|
+
// handler and trigger a retry
|
|
462
|
+
this.stopConnectionAttempt()
|
|
463
|
+
|
|
464
|
+
this.disconnect()
|
|
465
|
+
|
|
466
|
+
this.removeAllListeners()
|
|
467
|
+
|
|
468
|
+
if (typeof window === 'undefined') {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
window.removeEventListener('online', this.boundConnect)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
}
|
package/src/IncomingMessage.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createDecoder,
|
|
3
3
|
readVarUint,
|
|
4
4
|
readVarUint8Array,
|
|
5
|
+
readVarString,
|
|
5
6
|
Decoder,
|
|
6
7
|
} from 'lib0/decoding'
|
|
7
8
|
import {
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
createEncoder,
|
|
10
11
|
writeVarUint,
|
|
11
12
|
writeVarUint8Array,
|
|
13
|
+
writeVarString,
|
|
12
14
|
length,
|
|
13
15
|
} from 'lib0/encoding'
|
|
14
16
|
import { MessageType } from './types'
|
|
@@ -31,6 +33,10 @@ export class IncomingMessage {
|
|
|
31
33
|
return readVarUint(this.decoder)
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
readVarString(): string {
|
|
37
|
+
return readVarString(this.decoder)
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
readVarUint8Array() {
|
|
35
41
|
return readVarUint8Array(this.decoder)
|
|
36
42
|
}
|
|
@@ -39,6 +45,10 @@ export class IncomingMessage {
|
|
|
39
45
|
return writeVarUint(this.encoder, type)
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
writeVarString(string: string) {
|
|
49
|
+
return writeVarString(this.encoder, string)
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
writeVarUint8Array(data: Uint8Array) {
|
|
43
53
|
return writeVarUint8Array(this.encoder, data)
|
|
44
54
|
}
|
package/src/MessageReceiver.ts
CHANGED
|
@@ -27,6 +27,8 @@ export class MessageReceiver {
|
|
|
27
27
|
const { message } = this
|
|
28
28
|
const type = message.readVarUint()
|
|
29
29
|
|
|
30
|
+
const emptyMessageLength = message.length()
|
|
31
|
+
|
|
30
32
|
switch (type) {
|
|
31
33
|
case MessageType.Sync:
|
|
32
34
|
this.applySyncMessage(provider, emitSynced)
|
|
@@ -53,7 +55,7 @@ export class MessageReceiver {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
// Reply
|
|
56
|
-
if (message.length() > 1) {
|
|
58
|
+
if (message.length() > emptyMessageLength + 1) { // length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)
|
|
57
59
|
if (this.broadcasted) {
|
|
58
60
|
// TODO: Some weird TypeScript error
|
|
59
61
|
// @ts-ignore
|
|
@@ -80,7 +82,7 @@ export class MessageReceiver {
|
|
|
80
82
|
)
|
|
81
83
|
|
|
82
84
|
// Synced once we receive Step2
|
|
83
|
-
if (emitSynced &&
|
|
85
|
+
if (emitSynced && syncMessageType === messageYjsSyncStep2) {
|
|
84
86
|
provider.synced = true
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeVarUint } from 'lib0/encoding'
|
|
1
|
+
import { writeVarString, writeVarUint } from 'lib0/encoding'
|
|
2
2
|
import { writeAuthentication } from '@hocuspocus/common'
|
|
3
3
|
import { MessageType, OutgoingMessageArguments } from '../types'
|
|
4
4
|
import { OutgoingMessage } from '../OutgoingMessage'
|
|
@@ -13,6 +13,7 @@ export class AuthenticationMessage extends OutgoingMessage {
|
|
|
13
13
|
throw new Error('The authentication message requires `token` as an argument.')
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
writeVarString(this.encoder, args.documentName!)
|
|
16
17
|
writeVarUint(this.encoder, this.type)
|
|
17
18
|
writeAuthentication(this.encoder, args.token)
|
|
18
19
|
|
|
@@ -17,6 +17,7 @@ export class AwarenessMessage extends OutgoingMessage {
|
|
|
17
17
|
throw new Error('The awareness message requires clients as an argument')
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
encoding.writeVarString(this.encoder, args.documentName!)
|
|
20
21
|
encoding.writeVarUint(this.encoder, this.type)
|
|
21
22
|
|
|
22
23
|
let awarenessUpdate
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as encoding from 'lib0/encoding'
|
|
2
|
+
import { MessageType, OutgoingMessageArguments } from '../types'
|
|
3
|
+
import { OutgoingMessage } from '../OutgoingMessage'
|
|
4
|
+
|
|
5
|
+
export class CloseMessage extends OutgoingMessage {
|
|
6
|
+
type = MessageType.CLOSE
|
|
7
|
+
|
|
8
|
+
description = 'Ask the server to close the connection'
|
|
9
|
+
|
|
10
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
11
|
+
encoding.writeVarString(this.encoder, args.documentName!)
|
|
12
|
+
encoding.writeVarUint(this.encoder, this.type)
|
|
13
|
+
|
|
14
|
+
return this.encoder
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -8,6 +8,11 @@ export class QueryAwarenessMessage extends OutgoingMessage {
|
|
|
8
8
|
description = 'Queries awareness states'
|
|
9
9
|
|
|
10
10
|
get(args: Partial<OutgoingMessageArguments>) {
|
|
11
|
+
|
|
12
|
+
console.log('queryAwareness: writing string docName', args.documentName)
|
|
13
|
+
console.log(this.encoder.cpos)
|
|
14
|
+
|
|
15
|
+
encoding.writeVarString(this.encoder, args.documentName!)
|
|
11
16
|
encoding.writeVarUint(this.encoder, this.type)
|
|
12
17
|
|
|
13
18
|
return this.encoder
|
|
@@ -8,6 +8,7 @@ export class StatelessMessage extends OutgoingMessage {
|
|
|
8
8
|
description = 'A stateless message'
|
|
9
9
|
|
|
10
10
|
get(args: Partial<OutgoingMessageArguments>) {
|
|
11
|
+
writeVarString(this.encoder, args.documentName!)
|
|
11
12
|
writeVarUint(this.encoder, this.type)
|
|
12
13
|
writeVarString(this.encoder, args.payload ?? '')
|
|
13
14
|
|
|
@@ -13,6 +13,7 @@ export class SyncStepOneMessage extends OutgoingMessage {
|
|
|
13
13
|
throw new Error('The sync step one message requires document as an argument')
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
encoding.writeVarString(this.encoder, args.documentName!)
|
|
16
17
|
encoding.writeVarUint(this.encoder, this.type)
|
|
17
18
|
syncProtocol.writeSyncStep1(this.encoder, args.document)
|
|
18
19
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as Y from 'yjs'
|
|
2
1
|
import * as encoding from 'lib0/encoding'
|
|
3
2
|
import * as syncProtocol from 'y-protocols/sync'
|
|
4
3
|
import { MessageType, OutgoingMessageArguments } from '../types'
|
|
@@ -14,6 +13,7 @@ export class SyncStepTwoMessage extends OutgoingMessage {
|
|
|
14
13
|
throw new Error('The sync step two message requires document as an argument')
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
encoding.writeVarString(this.encoder, args.documentName!)
|
|
17
17
|
encoding.writeVarUint(this.encoder, this.type)
|
|
18
18
|
syncProtocol.writeSyncStep2(this.encoder, args.document)
|
|
19
19
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeVarUint } from 'lib0/encoding'
|
|
1
|
+
import { writeVarString, writeVarUint } from 'lib0/encoding'
|
|
2
2
|
import { writeUpdate } from 'y-protocols/sync'
|
|
3
3
|
import { MessageType, OutgoingMessageArguments } from '../types'
|
|
4
4
|
import { OutgoingMessage } from '../OutgoingMessage'
|
|
@@ -9,7 +9,9 @@ export class UpdateMessage extends OutgoingMessage {
|
|
|
9
9
|
description = 'A document update'
|
|
10
10
|
|
|
11
11
|
get(args: Partial<OutgoingMessageArguments>) {
|
|
12
|
+
writeVarString(this.encoder, args.documentName!)
|
|
12
13
|
writeVarUint(this.encoder, this.type)
|
|
14
|
+
|
|
13
15
|
writeUpdate(this.encoder, args.update)
|
|
14
16
|
|
|
15
17
|
return this.encoder
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ export enum MessageType {
|
|
|
17
17
|
Auth = 2,
|
|
18
18
|
QueryAwareness = 3,
|
|
19
19
|
Stateless = 5,
|
|
20
|
+
CLOSE = 7,
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export enum WebSocketStatus {
|
|
@@ -31,6 +32,7 @@ export interface OutgoingMessageInterface {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface OutgoingMessageArguments {
|
|
35
|
+
documentName: string,
|
|
34
36
|
token: string,
|
|
35
37
|
document: Y.Doc,
|
|
36
38
|
awareness: Awareness,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
File without changes
|