@hocuspocus/provider 3.1.2 → 3.1.4

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 (64) hide show
  1. package/dist/hocuspocus-provider.cjs +111 -92
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +111 -92
  4. package/dist/hocuspocus-provider.esm.js.map +1 -1
  5. package/dist/packages/common/src/auth.d.ts +2 -2
  6. package/dist/packages/common/src/index.d.ts +4 -4
  7. package/dist/packages/extension-database/src/Database.d.ts +1 -1
  8. package/dist/packages/extension-database/src/index.d.ts +1 -1
  9. package/dist/packages/extension-logger/src/Logger.d.ts +1 -1
  10. package/dist/packages/extension-logger/src/index.d.ts +1 -1
  11. package/dist/packages/extension-redis/src/index.d.ts +1 -1
  12. package/dist/packages/extension-sqlite/src/SQLite.d.ts +3 -3
  13. package/dist/packages/extension-sqlite/src/index.d.ts +1 -1
  14. package/dist/packages/extension-throttle/src/index.d.ts +1 -1
  15. package/dist/packages/extension-webhook/src/index.d.ts +3 -3
  16. package/dist/packages/provider/src/HocuspocusProvider.d.ts +10 -10
  17. package/dist/packages/provider/src/IncomingMessage.d.ts +3 -3
  18. package/dist/packages/provider/src/MessageReceiver.d.ts +2 -2
  19. package/dist/packages/provider/src/OutgoingMessage.d.ts +2 -2
  20. package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +3 -3
  21. package/dist/packages/provider/src/OutgoingMessages/AwarenessMessage.d.ts +4 -4
  22. package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +4 -4
  23. package/dist/packages/provider/src/OutgoingMessages/QueryAwarenessMessage.d.ts +4 -4
  24. package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +3 -3
  25. package/dist/packages/provider/src/OutgoingMessages/SyncStepOneMessage.d.ts +4 -4
  26. package/dist/packages/provider/src/OutgoingMessages/SyncStepTwoMessage.d.ts +4 -4
  27. package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +3 -3
  28. package/dist/packages/provider/src/index.d.ts +3 -3
  29. package/dist/packages/provider/src/types.d.ts +14 -14
  30. package/dist/packages/server/src/ClientConnection.d.ts +17 -17
  31. package/dist/packages/server/src/Connection.d.ts +6 -6
  32. package/dist/packages/server/src/DirectConnection.d.ts +3 -3
  33. package/dist/packages/server/src/Document.d.ts +4 -4
  34. package/dist/packages/server/src/Hocuspocus.d.ts +8 -8
  35. package/dist/packages/server/src/IncomingMessage.d.ts +3 -3
  36. package/dist/packages/server/src/MessageReceiver.d.ts +3 -3
  37. package/dist/packages/server/src/OutgoingMessage.d.ts +3 -3
  38. package/dist/packages/server/src/Server.d.ts +5 -5
  39. package/dist/packages/server/src/index.d.ts +9 -9
  40. package/dist/packages/server/src/types.d.ts +7 -7
  41. package/dist/packages/server/src/util/getParameters.d.ts +5 -5
  42. package/dist/packages/transformer/src/Prosemirror.d.ts +3 -3
  43. package/dist/packages/transformer/src/Tiptap.d.ts +3 -3
  44. package/dist/packages/transformer/src/index.d.ts +3 -3
  45. package/dist/packages/transformer/src/types.d.ts +1 -1
  46. package/dist/playground/frontend/app/SocketContext1.d.ts +2 -0
  47. package/dist/playground/frontend/app/SocketContext2.d.ts +2 -0
  48. package/package.json +40 -40
  49. package/src/EventEmitter.ts +32 -32
  50. package/src/HocuspocusProvider.ts +566 -498
  51. package/src/IncomingMessage.ts +47 -50
  52. package/src/MessageReceiver.ts +135 -132
  53. package/src/OutgoingMessage.ts +18 -14
  54. package/src/OutgoingMessages/AuthenticationMessage.ts +18 -16
  55. package/src/OutgoingMessages/AwarenessMessage.ts +38 -32
  56. package/src/OutgoingMessages/CloseMessage.ts +11 -11
  57. package/src/OutgoingMessages/QueryAwarenessMessage.ts +11 -12
  58. package/src/OutgoingMessages/StatelessMessage.ts +12 -12
  59. package/src/OutgoingMessages/SyncStepOneMessage.ts +18 -16
  60. package/src/OutgoingMessages/SyncStepTwoMessage.ts +18 -16
  61. package/src/OutgoingMessages/UpdateMessage.ts +13 -13
  62. package/src/index.ts +3 -3
  63. package/src/types.ts +69 -69
  64. package/dist/playground/frontend/app/SocketContext.d.ts +0 -2
@@ -1,508 +1,576 @@
1
- import { awarenessStatesToArray } from '@hocuspocus/common'
2
- import type { Event, MessageEvent } from 'ws'
3
- import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
4
- import * as Y from 'yjs'
5
- import EventEmitter from './EventEmitter.ts'
1
+ import { awarenessStatesToArray } from "@hocuspocus/common";
2
+ import type { Event, MessageEvent } from "ws";
3
+ import { Awareness, removeAwarenessStates } from "y-protocols/awareness";
4
+ import * as Y from "yjs";
5
+ import EventEmitter from "./EventEmitter.ts";
6
+ import type { CompleteHocuspocusProviderWebsocketConfiguration } from "./HocuspocusProviderWebsocket.ts";
7
+ import { HocuspocusProviderWebsocket } from "./HocuspocusProviderWebsocket.ts";
8
+ import { IncomingMessage } from "./IncomingMessage.ts";
9
+ import { MessageReceiver } from "./MessageReceiver.ts";
10
+ import { MessageSender } from "./MessageSender.ts";
11
+ import { AuthenticationMessage } from "./OutgoingMessages/AuthenticationMessage.ts";
12
+ import { AwarenessMessage } from "./OutgoingMessages/AwarenessMessage.ts";
13
+ import { StatelessMessage } from "./OutgoingMessages/StatelessMessage.ts";
14
+ import { SyncStepOneMessage } from "./OutgoingMessages/SyncStepOneMessage.ts";
15
+ import { UpdateMessage } from "./OutgoingMessages/UpdateMessage.ts";
6
16
  import type {
7
- CompleteHocuspocusProviderWebsocketConfiguration} from './HocuspocusProviderWebsocket.ts'
8
- import {
9
- HocuspocusProviderWebsocket,
10
- } from './HocuspocusProviderWebsocket.ts'
11
- import { IncomingMessage } from './IncomingMessage.ts'
12
- import { MessageReceiver } from './MessageReceiver.ts'
13
- import { MessageSender } from './MessageSender.ts'
14
- import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage.ts'
15
- import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage.ts'
16
- import { StatelessMessage } from './OutgoingMessages/StatelessMessage.ts'
17
- import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage.ts'
18
- import { UpdateMessage } from './OutgoingMessages/UpdateMessage.ts'
19
- import type {
20
- ConstructableOutgoingMessage,
21
- onAuthenticatedParameters,
22
- onAuthenticationFailedParameters,
23
- onAwarenessChangeParameters,
24
- onAwarenessUpdateParameters,
25
- onCloseParameters,
26
- onDisconnectParameters,
27
- onMessageParameters,
28
- onOpenParameters,
29
- onOutgoingMessageParameters,
30
- onStatelessParameters,
31
- onSyncedParameters,
32
- onUnsyncedChangesParameters,
33
- } from './types.ts'
34
-
35
- export type HocuspocusProviderConfiguration =
36
- Required<Pick<CompleteHocuspocusProviderConfiguration, 'name'>>
37
- & Partial<CompleteHocuspocusProviderConfiguration> & (
38
- Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>> |
39
- Required<Pick<CompleteHocuspocusProviderConfiguration, 'websocketProvider'>>
40
- )
17
+ ConstructableOutgoingMessage,
18
+ onAuthenticatedParameters,
19
+ onAuthenticationFailedParameters,
20
+ onAwarenessChangeParameters,
21
+ onAwarenessUpdateParameters,
22
+ onCloseParameters,
23
+ onDisconnectParameters,
24
+ onMessageParameters,
25
+ onOpenParameters,
26
+ onOutgoingMessageParameters,
27
+ onStatelessParameters,
28
+ onSyncedParameters,
29
+ onUnsyncedChangesParameters,
30
+ } from "./types.ts";
31
+
32
+ export type HocuspocusProviderConfiguration = Required<
33
+ Pick<CompleteHocuspocusProviderConfiguration, "name">
34
+ > &
35
+ Partial<CompleteHocuspocusProviderConfiguration> &
36
+ (
37
+ | Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, "url">>
38
+ | Required<
39
+ Pick<CompleteHocuspocusProviderConfiguration, "websocketProvider">
40
+ >
41
+ );
41
42
 
42
43
  export interface CompleteHocuspocusProviderConfiguration {
43
- /**
44
- * The identifier/name of your document
45
- */
46
- name: string,
47
- /**
48
- * The actual Y.js document
49
- */
50
- document: Y.Doc,
51
-
52
- /**
53
- * An Awareness instance to keep the presence state of all clients.
54
- *
55
- * You can disable sharing awareness information by passing `null`.
56
- * Note that having no awareness information shared across all connections will break our ping checks
57
- * and thus trigger reconnects. You should always have at least one Provider with enabled awareness per
58
- * socket connection, or ensure that the Provider receives messages before running into `HocuspocusProviderWebsocket.messageReconnectTimeout`.
59
- */
60
- awareness: Awareness | null,
61
-
62
- /**
63
- * A token that’s sent to the backend for authentication purposes.
64
- */
65
- token: string | (() => string) | (() => Promise<string>) | null,
66
-
67
- /**
68
- * Hocuspocus websocket provider
69
- */
70
- websocketProvider: HocuspocusProviderWebsocket,
71
-
72
- /**
73
- * Force syncing the document in the defined interval.
74
- */
75
- forceSyncInterval: false | number,
76
-
77
- onAuthenticated: (data: onAuthenticatedParameters) => void,
78
- onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void,
79
- onOpen: (data: onOpenParameters) => void,
80
- onConnect: () => void,
81
- onMessage: (data: onMessageParameters) => void,
82
- onOutgoingMessage: (data: onOutgoingMessageParameters) => void,
83
- onSynced: (data: onSyncedParameters) => void,
84
- onDisconnect: (data: onDisconnectParameters) => void,
85
- onClose: (data: onCloseParameters) => void,
86
- onDestroy: () => void,
87
- onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void,
88
- onAwarenessChange: (data: onAwarenessChangeParameters) => void,
89
- onStateless: (data: onStatelessParameters) => void
90
- onUnsyncedChanges: (data: onUnsyncedChangesParameters) => void
44
+ /**
45
+ * The identifier/name of your document
46
+ */
47
+ name: string;
48
+ /**
49
+ * The actual Y.js document
50
+ */
51
+ document: Y.Doc;
52
+
53
+ /**
54
+ * An Awareness instance to keep the presence state of all clients.
55
+ *
56
+ * You can disable sharing awareness information by passing `null`.
57
+ * Note that having no awareness information shared across all connections will break our ping checks
58
+ * and thus trigger reconnects. You should always have at least one Provider with enabled awareness per
59
+ * socket connection, or ensure that the Provider receives messages before running into `HocuspocusProviderWebsocket.messageReconnectTimeout`.
60
+ */
61
+ awareness: Awareness | null;
62
+
63
+ /**
64
+ * A token that’s sent to the backend for authentication purposes.
65
+ */
66
+ token: string | (() => string) | (() => Promise<string>) | null;
67
+
68
+ /**
69
+ * Hocuspocus websocket provider
70
+ */
71
+ websocketProvider: HocuspocusProviderWebsocket;
72
+
73
+ /**
74
+ * Force syncing the document in the defined interval.
75
+ */
76
+ forceSyncInterval: false | number;
77
+
78
+ onAuthenticated: (data: onAuthenticatedParameters) => void;
79
+ onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void;
80
+ onOpen: (data: onOpenParameters) => void;
81
+ onConnect: () => void;
82
+ onMessage: (data: onMessageParameters) => void;
83
+ onOutgoingMessage: (data: onOutgoingMessageParameters) => void;
84
+ onSynced: (data: onSyncedParameters) => void;
85
+ onDisconnect: (data: onDisconnectParameters) => void;
86
+ onClose: (data: onCloseParameters) => void;
87
+ onDestroy: () => void;
88
+ onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void;
89
+ onAwarenessChange: (data: onAwarenessChangeParameters) => void;
90
+ onStateless: (data: onStatelessParameters) => void;
91
+ onUnsyncedChanges: (data: onUnsyncedChangesParameters) => void;
91
92
  }
92
93
 
93
94
  export class AwarenessError extends Error {
94
- code = 1001
95
+ code = 1001;
95
96
  }
96
97
 
97
98
  export class HocuspocusProvider extends EventEmitter {
98
- public configuration: CompleteHocuspocusProviderConfiguration = {
99
- name: '',
100
- // @ts-ignore
101
- document: undefined,
102
- // @ts-ignore
103
- awareness: undefined,
104
- token: null,
105
- forceSyncInterval: false,
106
- onAuthenticated: () => null,
107
- onAuthenticationFailed: () => null,
108
- onOpen: () => null,
109
- onConnect: () => null,
110
- onMessage: () => null,
111
- onOutgoingMessage: () => null,
112
- onSynced: () => null,
113
- onDisconnect: () => null,
114
- onClose: () => null,
115
- onDestroy: () => null,
116
- onAwarenessUpdate: () => null,
117
- onAwarenessChange: () => null,
118
- onStateless: () => null,
119
- onUnsyncedChanges: () => null,
120
- }
121
-
122
- isSynced = false
123
-
124
- unsyncedChanges = 0
125
-
126
- isAuthenticated = false
127
-
128
- authorizedScope: string | undefined = undefined
129
-
130
- // @internal
131
- manageSocket = false
132
-
133
- private _isAttached = false
134
-
135
- intervals: any = {
136
- forceSync: null,
137
- }
138
-
139
- constructor(configuration: HocuspocusProviderConfiguration) {
140
- super()
141
- this.setConfiguration(configuration)
142
-
143
- this.configuration.document = configuration.document ? configuration.document : new Y.Doc()
144
- this.configuration.awareness = configuration.awareness !== undefined ? configuration.awareness : new Awareness(this.document)
145
-
146
- this.on('open', this.configuration.onOpen)
147
- this.on('message', this.configuration.onMessage)
148
- this.on('outgoingMessage', this.configuration.onOutgoingMessage)
149
- this.on('synced', this.configuration.onSynced)
150
- this.on('destroy', this.configuration.onDestroy)
151
- this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
152
- this.on('awarenessChange', this.configuration.onAwarenessChange)
153
- this.on('stateless', this.configuration.onStateless)
154
- this.on('unsyncedChanges', this.configuration.onUnsyncedChanges)
155
-
156
- this.on('authenticated', this.configuration.onAuthenticated)
157
- this.on('authenticationFailed', this.configuration.onAuthenticationFailed)
158
-
159
- this.awareness?.on('update', () => {
160
- this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness!.getStates()) })
161
- })
162
-
163
- this.awareness?.on('change', () => {
164
- this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness!.getStates()) })
165
- })
166
-
167
- this.document.on('update', this.boundDocumentUpdateHandler)
168
- this.awareness?.on('update', this.boundAwarenessUpdateHandler)
169
-
170
- this.registerEventListeners()
171
-
172
- if (
173
- this.configuration.forceSyncInterval
174
- && typeof this.configuration.forceSyncInterval === 'number'
175
- ) {
176
- this.intervals.forceSync = setInterval(
177
- this.forceSync.bind(this),
178
- this.configuration.forceSyncInterval,
179
- )
180
- }
181
-
182
- if( this.manageSocket ) {
183
- this.attach()
184
- }
185
- }
186
-
187
- boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this)
188
-
189
- boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this)
190
-
191
- boundPageHide = this.pageHide.bind(this)
192
-
193
- boundOnOpen = this.onOpen.bind(this)
194
-
195
- boundOnClose = this.onClose.bind(this)
196
-
197
- forwardConnect = (e: any) => this.emit('connect', e)
198
-
199
- forwardClose = (e: any) => this.emit('close', e)
200
-
201
- forwardDisconnect = (e: any) => this.emit('disconnect', e)
202
-
203
- forwardDestroy = (e: any) => this.emit('destroy', e)
204
-
205
- public setConfiguration(configuration: Partial<HocuspocusProviderConfiguration> = {}): void {
206
- if (!configuration.websocketProvider) {
207
- const websocketProviderConfig = configuration as CompleteHocuspocusProviderWebsocketConfiguration
208
- this.manageSocket = true
209
- this.configuration.websocketProvider = new HocuspocusProviderWebsocket({
210
- url: websocketProviderConfig.url,
211
- })
212
- }
213
-
214
- this.configuration = { ...this.configuration, ...configuration }
215
- }
216
-
217
- get document() {
218
- return this.configuration.document
219
- }
220
-
221
- public get isAttached() {
222
- return this._isAttached
223
- }
224
-
225
- get awareness() {
226
- return this.configuration.awareness
227
- }
228
-
229
- get hasUnsyncedChanges(): boolean {
230
- return this.unsyncedChanges > 0
231
- }
232
-
233
- private resetUnsyncedChanges() {
234
- this.unsyncedChanges = 1
235
- this.emit('unsyncedChanges', { number: this.unsyncedChanges })
236
- }
237
-
238
- incrementUnsyncedChanges() {
239
- this.unsyncedChanges += 1
240
- this.emit('unsyncedChanges', { number: this.unsyncedChanges })
241
- }
242
-
243
- decrementUnsyncedChanges() {
244
- if( this.unsyncedChanges > 0 ) {
245
- this.unsyncedChanges -= 1
246
- }
247
-
248
- if (this.unsyncedChanges === 0) {
249
- this.synced = true
250
- }
251
-
252
- this.emit('unsyncedChanges', { number: this.unsyncedChanges })
253
- }
254
-
255
- forceSync() {
256
- this.resetUnsyncedChanges()
257
-
258
- this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
259
- }
260
-
261
- pageHide() {
262
- if (this.awareness) {
263
- removeAwarenessStates(this.awareness, [this.document.clientID], 'page hide')
264
- }
265
- }
266
-
267
- registerEventListeners() {
268
- if (typeof window === 'undefined' || !('addEventListener' in window)) {
269
- return
270
- }
271
-
272
- window.addEventListener('pagehide', this.boundPageHide)
273
- }
274
-
275
- sendStateless(payload: string) {
276
- this.send(StatelessMessage, { documentName: this.configuration.name, payload })
277
- }
278
-
279
- documentUpdateHandler(update: Uint8Array, origin: any) {
280
- if (origin === this) {
281
- return
282
- }
283
-
284
- this.incrementUnsyncedChanges()
285
- this.send(UpdateMessage, { update, documentName: this.configuration.name })
286
- }
287
-
288
- awarenessUpdateHandler({ added, updated, removed }: any, origin: any) {
289
- const changedClients = added.concat(updated).concat(removed)
290
-
291
- this.send(AwarenessMessage, {
292
- awareness: this.awareness,
293
- clients: changedClients,
294
- documentName: this.configuration.name,
295
- })
296
- }
297
-
298
- /**
299
- * Indicates whether a first handshake with the server has been established
300
- *
301
- * Note: this does not mean all updates from the client have been persisted to the backend. For this,
302
- * use `hasUnsyncedChanges`.
303
- */
304
- get synced(): boolean {
305
- return this.isSynced
306
- }
307
-
308
- set synced(state) {
309
- if (this.isSynced === state) {
310
- return
311
- }
312
-
313
- this.isSynced = state
314
-
315
- if( state ) {
316
- this.emit('synced', { state })
317
- }
318
- }
319
-
320
- receiveStateless(payload: string) {
321
- this.emit('stateless', { payload })
322
- }
323
-
324
- // not needed, but provides backward compatibility with e.g. lexical/yjs
325
- async connect() {
326
- if( this.manageSocket ) {
327
- return this.configuration.websocketProvider.connect()
328
- }
329
-
330
- console.warn('HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.')
331
- }
332
-
333
- disconnect() {
334
- if( this.manageSocket ) {
335
- return this.configuration.websocketProvider.disconnect()
336
- }
337
-
338
- console.warn('HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.')
339
- }
340
-
341
- async onOpen(event: Event) {
342
- this.isAuthenticated = false
343
-
344
- this.emit('open', { event })
345
-
346
- let token: string | null
347
- try {
348
- token = await this.getToken()
349
- } catch (error) {
350
- this.permissionDeniedHandler(`Failed to get token: ${error}`)
351
- return
352
- }
353
-
354
- this.send(AuthenticationMessage, {
355
- token: token ?? '',
356
- documentName: this.configuration.name,
357
- })
358
-
359
- this.startSync()
360
- }
361
-
362
- async getToken() {
363
- if (typeof this.configuration.token === 'function') {
364
- const token = await this.configuration.token()
365
- return token
366
- }
367
-
368
- return this.configuration.token
369
- }
370
-
371
- startSync() {
372
- this.resetUnsyncedChanges()
373
-
374
- this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
375
-
376
- if (this.awareness && this.awareness.getLocalState() !== null) {
377
- this.send(AwarenessMessage, {
378
- awareness: this.awareness,
379
- clients: [this.document.clientID],
380
- documentName: this.configuration.name,
381
- })
382
- }
383
- }
384
-
385
- send(message: ConstructableOutgoingMessage, args: any) {
386
- if( !this._isAttached ) return;
387
-
388
- const messageSender = new MessageSender(message, args)
389
-
390
- this.emit('outgoingMessage', { message: messageSender.message })
391
- messageSender.send(this.configuration.websocketProvider)
392
- }
393
-
394
- onMessage(event: MessageEvent) {
395
- const message = new IncomingMessage(event.data)
396
-
397
- const documentName = message.readVarString()
398
-
399
- message.writeVarString(documentName)
400
-
401
- this.emit('message', { event, message: new IncomingMessage(event.data) })
402
-
403
- new MessageReceiver(message).apply(this, true)
404
- }
405
-
406
- onClose() {
407
- this.isAuthenticated = false
408
- this.synced = false
409
-
410
- // update awareness (all users except local left)
411
- if (this.awareness) {
412
- removeAwarenessStates(
413
- this.awareness,
414
- Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID),
415
- this,
416
- )
417
- }
418
- }
419
-
420
- destroy() {
421
- this.emit('destroy')
422
-
423
- if (this.intervals.forceSync) {
424
- clearInterval(this.intervals.forceSync)
425
- }
426
-
427
- if (this.awareness) {
428
- removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
429
- this.awareness.off('update', this.boundAwarenessUpdateHandler)
430
- this.awareness.destroy()
431
- }
432
-
433
- this.document.off('update', this.boundDocumentUpdateHandler)
434
-
435
- this.removeAllListeners()
436
-
437
- this.detach()
438
-
439
- if( this.manageSocket ) {
440
- this.configuration.websocketProvider.destroy()
441
- }
442
-
443
- if (typeof window === 'undefined' || !('removeEventListener' in window)) {
444
- return
445
- }
446
-
447
- window.removeEventListener('pagehide', this.boundPageHide)
448
- }
449
-
450
- detach() {
451
- this.configuration.websocketProvider.off('connect', this.configuration.onConnect)
452
- this.configuration.websocketProvider.off('connect', this.forwardConnect)
453
- this.configuration.websocketProvider.off('open', this.boundOnOpen)
454
- this.configuration.websocketProvider.off('close', this.boundOnClose)
455
- this.configuration.websocketProvider.off('close', this.configuration.onClose)
456
- this.configuration.websocketProvider.off('close', this.forwardClose)
457
- this.configuration.websocketProvider.off('disconnect', this.configuration.onDisconnect)
458
- this.configuration.websocketProvider.off('disconnect', this.forwardDisconnect)
459
- this.configuration.websocketProvider.off('destroy', this.configuration.onDestroy)
460
- this.configuration.websocketProvider.off('destroy', this.forwardDestroy)
461
-
462
- this.configuration.websocketProvider.detach(this)
463
-
464
- this._isAttached = false
465
- }
466
-
467
- attach() {
468
- if( this._isAttached ) return
469
-
470
- this.configuration.websocketProvider.on('connect', this.configuration.onConnect)
471
- this.configuration.websocketProvider.on('connect', this.forwardConnect)
472
-
473
- this.configuration.websocketProvider.on('open', this.boundOnOpen)
474
-
475
- this.configuration.websocketProvider.on('close', this.boundOnClose)
476
- this.configuration.websocketProvider.on('close', this.configuration.onClose)
477
- this.configuration.websocketProvider.on('close', this.forwardClose)
478
-
479
- this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect)
480
- this.configuration.websocketProvider.on('disconnect', this.forwardDisconnect)
481
-
482
- this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy)
483
- this.configuration.websocketProvider.on('destroy', this.forwardDestroy)
484
-
485
- this.configuration.websocketProvider.attach(this)
486
-
487
- this._isAttached = true
488
- }
489
-
490
- permissionDeniedHandler(reason: string) {
491
- this.emit('authenticationFailed', { reason })
492
- this.isAuthenticated = false
493
- }
494
-
495
- authenticatedHandler(scope: string) {
496
- this.isAuthenticated = true
497
- this.authorizedScope = scope
498
-
499
- this.emit('authenticated', { scope })
500
- }
501
-
502
- setAwarenessField(key: string, value: any) {
503
- if (!this.awareness) {
504
- throw new AwarenessError(`Cannot set awareness field "${key}" to ${JSON.stringify(value)}. You have disabled Awareness for this provider by explicitly passing awareness: null in the provider configuration.`)
505
- }
506
- this.awareness.setLocalStateField(key, value)
507
- }
99
+ public configuration: CompleteHocuspocusProviderConfiguration = {
100
+ name: "",
101
+ // @ts-ignore
102
+ document: undefined,
103
+ // @ts-ignore
104
+ awareness: undefined,
105
+ token: null,
106
+ forceSyncInterval: false,
107
+ onAuthenticated: () => null,
108
+ onAuthenticationFailed: () => null,
109
+ onOpen: () => null,
110
+ onConnect: () => null,
111
+ onMessage: () => null,
112
+ onOutgoingMessage: () => null,
113
+ onSynced: () => null,
114
+ onDisconnect: () => null,
115
+ onClose: () => null,
116
+ onDestroy: () => null,
117
+ onAwarenessUpdate: () => null,
118
+ onAwarenessChange: () => null,
119
+ onStateless: () => null,
120
+ onUnsyncedChanges: () => null,
121
+ };
122
+
123
+ isSynced = false;
124
+
125
+ unsyncedChanges = 0;
126
+
127
+ isAuthenticated = false;
128
+
129
+ authorizedScope: string | undefined = undefined;
130
+
131
+ // @internal
132
+ manageSocket = false;
133
+
134
+ private _isAttached = false;
135
+
136
+ intervals: any = {
137
+ forceSync: null,
138
+ };
139
+
140
+ constructor(configuration: HocuspocusProviderConfiguration) {
141
+ super();
142
+ this.setConfiguration(configuration);
143
+
144
+ this.configuration.document = configuration.document
145
+ ? configuration.document
146
+ : new Y.Doc();
147
+ this.configuration.awareness =
148
+ configuration.awareness !== undefined
149
+ ? configuration.awareness
150
+ : new Awareness(this.document);
151
+
152
+ this.on("open", this.configuration.onOpen);
153
+ this.on("message", this.configuration.onMessage);
154
+ this.on("outgoingMessage", this.configuration.onOutgoingMessage);
155
+ this.on("synced", this.configuration.onSynced);
156
+ this.on("destroy", this.configuration.onDestroy);
157
+ this.on("awarenessUpdate", this.configuration.onAwarenessUpdate);
158
+ this.on("awarenessChange", this.configuration.onAwarenessChange);
159
+ this.on("stateless", this.configuration.onStateless);
160
+ this.on("unsyncedChanges", this.configuration.onUnsyncedChanges);
161
+
162
+ this.on("authenticated", this.configuration.onAuthenticated);
163
+ this.on("authenticationFailed", this.configuration.onAuthenticationFailed);
164
+
165
+ this.awareness?.on("update", () => {
166
+ this.emit("awarenessUpdate", {
167
+ states: awarenessStatesToArray(this.awareness!.getStates()),
168
+ });
169
+ });
170
+
171
+ this.awareness?.on("change", () => {
172
+ this.emit("awarenessChange", {
173
+ states: awarenessStatesToArray(this.awareness!.getStates()),
174
+ });
175
+ });
176
+
177
+ this.document.on("update", this.boundDocumentUpdateHandler);
178
+ this.awareness?.on("update", this.boundAwarenessUpdateHandler);
179
+
180
+ this.registerEventListeners();
181
+
182
+ if (
183
+ this.configuration.forceSyncInterval &&
184
+ typeof this.configuration.forceSyncInterval === "number"
185
+ ) {
186
+ this.intervals.forceSync = setInterval(
187
+ this.forceSync.bind(this),
188
+ this.configuration.forceSyncInterval,
189
+ );
190
+ }
191
+
192
+ if (this.manageSocket) {
193
+ this.attach();
194
+ }
195
+ }
196
+
197
+ boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
198
+
199
+ boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
200
+
201
+ boundPageHide = this.pageHide.bind(this);
202
+
203
+ boundOnOpen = this.onOpen.bind(this);
204
+
205
+ boundOnClose = this.onClose.bind(this);
206
+
207
+ forwardConnect = (e: any) => this.emit("connect", e);
208
+
209
+ forwardClose = (e: any) => this.emit("close", e);
210
+
211
+ forwardDisconnect = (e: any) => this.emit("disconnect", e);
212
+
213
+ forwardDestroy = (e: any) => this.emit("destroy", e);
214
+
215
+ public setConfiguration(
216
+ configuration: Partial<HocuspocusProviderConfiguration> = {},
217
+ ): void {
218
+ if (!configuration.websocketProvider) {
219
+ const websocketProviderConfig =
220
+ configuration as CompleteHocuspocusProviderWebsocketConfiguration;
221
+ this.manageSocket = true;
222
+ this.configuration.websocketProvider = new HocuspocusProviderWebsocket({
223
+ url: websocketProviderConfig.url,
224
+ });
225
+ }
226
+
227
+ this.configuration = { ...this.configuration, ...configuration };
228
+ }
229
+
230
+ get document() {
231
+ return this.configuration.document;
232
+ }
233
+
234
+ public get isAttached() {
235
+ return this._isAttached;
236
+ }
237
+
238
+ get awareness() {
239
+ return this.configuration.awareness;
240
+ }
241
+
242
+ get hasUnsyncedChanges(): boolean {
243
+ return this.unsyncedChanges > 0;
244
+ }
245
+
246
+ private resetUnsyncedChanges() {
247
+ this.unsyncedChanges = 1;
248
+ this.emit("unsyncedChanges", { number: this.unsyncedChanges });
249
+ }
250
+
251
+ incrementUnsyncedChanges() {
252
+ this.unsyncedChanges += 1;
253
+ this.emit("unsyncedChanges", { number: this.unsyncedChanges });
254
+ }
255
+
256
+ decrementUnsyncedChanges() {
257
+ if (this.unsyncedChanges > 0) {
258
+ this.unsyncedChanges -= 1;
259
+ }
260
+
261
+ if (this.unsyncedChanges === 0) {
262
+ this.synced = true;
263
+ }
264
+
265
+ this.emit("unsyncedChanges", { number: this.unsyncedChanges });
266
+ }
267
+
268
+ forceSync() {
269
+ this.resetUnsyncedChanges();
270
+
271
+ this.send(SyncStepOneMessage, {
272
+ document: this.document,
273
+ documentName: this.configuration.name,
274
+ });
275
+ }
276
+
277
+ pageHide() {
278
+ if (this.awareness) {
279
+ removeAwarenessStates(
280
+ this.awareness,
281
+ [this.document.clientID],
282
+ "page hide",
283
+ );
284
+ }
285
+ }
286
+
287
+ registerEventListeners() {
288
+ if (typeof window === "undefined" || !("addEventListener" in window)) {
289
+ return;
290
+ }
291
+
292
+ window.addEventListener("pagehide", this.boundPageHide);
293
+ }
294
+
295
+ sendStateless(payload: string) {
296
+ this.send(StatelessMessage, {
297
+ documentName: this.configuration.name,
298
+ payload,
299
+ });
300
+ }
301
+
302
+ documentUpdateHandler(update: Uint8Array, origin: any) {
303
+ if (origin === this) {
304
+ return;
305
+ }
306
+
307
+ this.incrementUnsyncedChanges();
308
+ this.send(UpdateMessage, { update, documentName: this.configuration.name });
309
+ }
310
+
311
+ awarenessUpdateHandler({ added, updated, removed }: any, origin: any) {
312
+ const changedClients = added.concat(updated).concat(removed);
313
+
314
+ this.send(AwarenessMessage, {
315
+ awareness: this.awareness,
316
+ clients: changedClients,
317
+ documentName: this.configuration.name,
318
+ });
319
+ }
320
+
321
+ /**
322
+ * Indicates whether a first handshake with the server has been established
323
+ *
324
+ * Note: this does not mean all updates from the client have been persisted to the backend. For this,
325
+ * use `hasUnsyncedChanges`.
326
+ */
327
+ get synced(): boolean {
328
+ return this.isSynced;
329
+ }
330
+
331
+ set synced(state) {
332
+ if (this.isSynced === state) {
333
+ return;
334
+ }
335
+
336
+ this.isSynced = state;
337
+
338
+ if (state) {
339
+ this.emit("synced", { state });
340
+ }
341
+ }
342
+
343
+ receiveStateless(payload: string) {
344
+ this.emit("stateless", { payload });
345
+ }
346
+
347
+ // not needed, but provides backward compatibility with e.g. lexical/yjs
348
+ async connect() {
349
+ if (this.manageSocket) {
350
+ return this.configuration.websocketProvider.connect();
351
+ }
352
+
353
+ console.warn(
354
+ "HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.",
355
+ );
356
+ }
357
+
358
+ disconnect() {
359
+ if (this.manageSocket) {
360
+ return this.configuration.websocketProvider.disconnect();
361
+ }
362
+
363
+ console.warn(
364
+ "HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.",
365
+ );
366
+ }
367
+
368
+ async onOpen(event: Event) {
369
+ this.isAuthenticated = false;
370
+
371
+ this.emit("open", { event });
372
+
373
+ let token: string | null;
374
+ try {
375
+ token = await this.getToken();
376
+ } catch (error) {
377
+ this.permissionDeniedHandler(`Failed to get token: ${error}`);
378
+ return;
379
+ }
380
+
381
+ this.send(AuthenticationMessage, {
382
+ token: token ?? "",
383
+ documentName: this.configuration.name,
384
+ });
385
+
386
+ this.startSync();
387
+ }
388
+
389
+ async getToken() {
390
+ if (typeof this.configuration.token === "function") {
391
+ const token = await this.configuration.token();
392
+ return token;
393
+ }
394
+
395
+ return this.configuration.token;
396
+ }
397
+
398
+ startSync() {
399
+ this.resetUnsyncedChanges();
400
+
401
+ this.send(SyncStepOneMessage, {
402
+ document: this.document,
403
+ documentName: this.configuration.name,
404
+ });
405
+
406
+ if (this.awareness && this.awareness.getLocalState() !== null) {
407
+ this.send(AwarenessMessage, {
408
+ awareness: this.awareness,
409
+ clients: [this.document.clientID],
410
+ documentName: this.configuration.name,
411
+ });
412
+ }
413
+ }
414
+
415
+ send(message: ConstructableOutgoingMessage, args: any) {
416
+ if (!this._isAttached) return;
417
+
418
+ const messageSender = new MessageSender(message, args);
419
+
420
+ this.emit("outgoingMessage", { message: messageSender.message });
421
+ messageSender.send(this.configuration.websocketProvider);
422
+ }
423
+
424
+ onMessage(event: MessageEvent) {
425
+ const message = new IncomingMessage(event.data);
426
+
427
+ const documentName = message.readVarString();
428
+
429
+ message.writeVarString(documentName);
430
+
431
+ this.emit("message", { event, message: new IncomingMessage(event.data) });
432
+
433
+ new MessageReceiver(message).apply(this, true);
434
+ }
435
+
436
+ onClose() {
437
+ this.isAuthenticated = false;
438
+ this.synced = false;
439
+
440
+ // update awareness (all users except local left)
441
+ if (this.awareness) {
442
+ removeAwarenessStates(
443
+ this.awareness,
444
+ Array.from(this.awareness.getStates().keys()).filter(
445
+ (client) => client !== this.document.clientID,
446
+ ),
447
+ this,
448
+ );
449
+ }
450
+ }
451
+
452
+ destroy() {
453
+ this.emit("destroy");
454
+
455
+ if (this.intervals.forceSync) {
456
+ clearInterval(this.intervals.forceSync);
457
+ }
458
+
459
+ if (this.awareness) {
460
+ removeAwarenessStates(
461
+ this.awareness,
462
+ [this.document.clientID],
463
+ "provider destroy",
464
+ );
465
+ this.awareness.off("update", this.boundAwarenessUpdateHandler);
466
+ this.awareness.destroy();
467
+ }
468
+
469
+ this.document.off("update", this.boundDocumentUpdateHandler);
470
+
471
+ this.removeAllListeners();
472
+
473
+ this.detach();
474
+
475
+ if (this.manageSocket) {
476
+ this.configuration.websocketProvider.destroy();
477
+ }
478
+
479
+ if (typeof window === "undefined" || !("removeEventListener" in window)) {
480
+ return;
481
+ }
482
+
483
+ window.removeEventListener("pagehide", this.boundPageHide);
484
+ }
485
+
486
+ detach() {
487
+ this.configuration.websocketProvider.off(
488
+ "connect",
489
+ this.configuration.onConnect,
490
+ );
491
+ this.configuration.websocketProvider.off("connect", this.forwardConnect);
492
+ this.configuration.websocketProvider.off("open", this.boundOnOpen);
493
+ this.configuration.websocketProvider.off("close", this.boundOnClose);
494
+ this.configuration.websocketProvider.off(
495
+ "close",
496
+ this.configuration.onClose,
497
+ );
498
+ this.configuration.websocketProvider.off("close", this.forwardClose);
499
+ this.configuration.websocketProvider.off(
500
+ "disconnect",
501
+ this.configuration.onDisconnect,
502
+ );
503
+ this.configuration.websocketProvider.off(
504
+ "disconnect",
505
+ this.forwardDisconnect,
506
+ );
507
+ this.configuration.websocketProvider.off(
508
+ "destroy",
509
+ this.configuration.onDestroy,
510
+ );
511
+ this.configuration.websocketProvider.off("destroy", this.forwardDestroy);
512
+
513
+ this.configuration.websocketProvider.detach(this);
514
+
515
+ this._isAttached = false;
516
+ }
517
+
518
+ attach() {
519
+ if (this._isAttached) return;
520
+
521
+ this.configuration.websocketProvider.on(
522
+ "connect",
523
+ this.configuration.onConnect,
524
+ );
525
+ this.configuration.websocketProvider.on("connect", this.forwardConnect);
526
+
527
+ this.configuration.websocketProvider.on("open", this.boundOnOpen);
528
+
529
+ this.configuration.websocketProvider.on("close", this.boundOnClose);
530
+ this.configuration.websocketProvider.on(
531
+ "close",
532
+ this.configuration.onClose,
533
+ );
534
+ this.configuration.websocketProvider.on("close", this.forwardClose);
535
+
536
+ this.configuration.websocketProvider.on(
537
+ "disconnect",
538
+ this.configuration.onDisconnect,
539
+ );
540
+ this.configuration.websocketProvider.on(
541
+ "disconnect",
542
+ this.forwardDisconnect,
543
+ );
544
+
545
+ this.configuration.websocketProvider.on(
546
+ "destroy",
547
+ this.configuration.onDestroy,
548
+ );
549
+ this.configuration.websocketProvider.on("destroy", this.forwardDestroy);
550
+
551
+ this.configuration.websocketProvider.attach(this);
552
+
553
+ this._isAttached = true;
554
+ }
555
+
556
+ permissionDeniedHandler(reason: string) {
557
+ this.emit("authenticationFailed", { reason });
558
+ this.isAuthenticated = false;
559
+ }
560
+
561
+ authenticatedHandler(scope: string) {
562
+ this.isAuthenticated = true;
563
+ this.authorizedScope = scope;
564
+
565
+ this.emit("authenticated", { scope });
566
+ }
567
+
568
+ setAwarenessField(key: string, value: any) {
569
+ if (!this.awareness) {
570
+ throw new AwarenessError(
571
+ `Cannot set awareness field "${key}" to ${JSON.stringify(value)}. You have disabled Awareness for this provider by explicitly passing awareness: null in the provider configuration.`,
572
+ );
573
+ }
574
+ this.awareness.setLocalStateField(key, value);
575
+ }
508
576
  }