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