@hocuspocus/provider 1.0.0-alpha.4 → 1.0.0-alpha.40
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.js → hocuspocus-provider.cjs} +362 -186
- package/dist/hocuspocus-provider.cjs.map +1 -0
- package/dist/hocuspocus-provider.esm.js +359 -181
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/common/src/CloseEvents.d.ts +23 -0
- package/dist/packages/common/src/auth.d.ts +6 -0
- package/dist/packages/common/src/awarenessStatesToArray.d.ts +3 -0
- package/dist/packages/common/src/index.d.ts +4 -0
- package/dist/packages/common/src/types.d.ts +10 -0
- package/dist/packages/extension-database/src/Database.d.ts +30 -0
- package/dist/packages/extension-database/src/index.d.ts +1 -0
- package/dist/packages/extension-logger/src/Logger.d.ts +67 -0
- package/dist/packages/extension-logger/src/index.d.ts +1 -0
- package/dist/packages/{monitor → extension-monitor}/src/Collector.d.ts +4 -5
- package/dist/packages/{monitor → extension-monitor}/src/Dashboard.d.ts +2 -2
- package/dist/packages/{monitor → extension-monitor}/src/Storage.d.ts +0 -0
- package/dist/packages/{monitor → extension-monitor}/src/index.d.ts +3 -5
- package/dist/packages/extension-redis/src/Redis.d.ts +98 -0
- package/dist/packages/extension-redis/src/index.d.ts +1 -0
- package/dist/packages/extension-sqlite/src/SQLite.d.ts +26 -0
- package/dist/packages/extension-sqlite/src/index.d.ts +1 -0
- package/dist/packages/extension-throttle/src/index.d.ts +24 -0
- package/dist/packages/{webhook → extension-webhook}/src/index.d.ts +5 -11
- package/dist/packages/provider/src/EventEmitter.d.ts +1 -1
- package/dist/packages/provider/src/HocuspocusCloudProvider.d.ts +11 -0
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +116 -32
- package/dist/packages/provider/src/IncomingMessage.d.ts +9 -6
- package/dist/packages/provider/src/MessageReceiver.d.ts +3 -2
- package/dist/packages/provider/src/MessageSender.d.ts +4 -9
- package/dist/packages/provider/src/OutgoingMessage.d.ts +5 -4
- package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +7 -0
- package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +1 -2
- package/dist/packages/provider/src/index.d.ts +1 -1
- package/dist/packages/provider/src/types.d.ts +54 -2
- package/dist/packages/server/src/Connection.d.ts +16 -7
- package/dist/packages/server/src/Debugger.d.ts +14 -0
- package/dist/packages/server/src/Document.d.ts +11 -5
- package/dist/packages/server/src/Hocuspocus.d.ts +63 -17
- package/dist/packages/server/src/IncomingMessage.d.ts +11 -7
- package/dist/packages/server/src/MessageReceiver.d.ts +13 -0
- package/dist/packages/server/src/OutgoingMessage.d.ts +6 -0
- package/dist/packages/server/src/index.d.ts +6 -0
- package/dist/packages/server/src/types.d.ts +168 -26
- package/dist/{demos/backend/src/create-document.d.ts → playground/backend/src/default.d.ts} +0 -0
- package/dist/{demos → playground}/backend/src/express.d.ts +0 -0
- package/dist/{demos → playground}/backend/src/koa.d.ts +0 -0
- package/dist/{demos/backend/src/minimal.d.ts → playground/backend/src/load-document.d.ts} +0 -0
- package/dist/{demos → playground}/backend/src/monitor.d.ts +0 -0
- package/dist/{demos/backend/src/webhook.d.ts → playground/backend/src/redis.d.ts} +0 -0
- package/dist/playground/backend/src/slow.d.ts +1 -0
- package/dist/playground/backend/src/webhook.d.ts +1 -0
- package/dist/tests/extension-database/fetch.d.ts +1 -0
- package/dist/tests/extension-logger/onListen.d.ts +1 -0
- package/dist/tests/extension-redis/closeConnections.d.ts +1 -0
- package/dist/tests/extension-redis/getConnectionCount.d.ts +1 -0
- package/dist/tests/extension-redis/getDocumentsCount.d.ts +1 -0
- package/dist/tests/extension-redis/onAwarenessChange.d.ts +1 -0
- package/dist/tests/extension-redis/onChange.d.ts +1 -0
- package/dist/tests/extension-redis/onStoreDocument.d.ts +1 -0
- package/dist/tests/extension-throttle/configuration.d.ts +1 -0
- package/dist/tests/provider/configuration.d.ts +1 -0
- package/dist/tests/provider/observe.d.ts +1 -0
- package/dist/tests/provider/observeDeep.d.ts +1 -0
- package/dist/tests/provider/onAuthenticated.d.ts +1 -0
- package/dist/tests/provider/onAuthenticationFailed.d.ts +1 -0
- package/dist/tests/provider/onAwarenessChange.d.ts +1 -0
- package/dist/tests/provider/onAwarenessUpdate.d.ts +1 -0
- package/dist/tests/provider/onClose.d.ts +1 -0
- package/dist/tests/provider/onConnect.d.ts +1 -0
- package/dist/tests/provider/onDisconnect.d.ts +1 -0
- package/dist/tests/provider/onMessage.d.ts +1 -0
- package/dist/tests/provider/onOpen.d.ts +1 -0
- package/dist/tests/provider/onSynced.d.ts +1 -0
- package/dist/tests/server/address.d.ts +1 -0
- package/dist/tests/server/afterStoreDocument.d.ts +1 -0
- package/dist/tests/server/closeConnections.d.ts +1 -0
- package/dist/tests/server/getConnectionsCount.d.ts +1 -0
- package/dist/tests/server/getDocumentName.d.ts +1 -0
- package/dist/tests/server/getDocumentsCount.d.ts +1 -0
- package/dist/tests/server/getMessageLogs.d.ts +1 -0
- package/dist/tests/server/listen.d.ts +1 -0
- package/dist/tests/server/onAuthenticate.d.ts +1 -0
- package/dist/tests/server/onAwarenessUpdate.d.ts +1 -0
- package/dist/tests/server/onChange.d.ts +1 -0
- package/dist/tests/server/onConfigure.d.ts +1 -0
- package/dist/tests/server/onConnect.d.ts +1 -0
- package/dist/tests/server/onDestroy.d.ts +1 -0
- package/dist/tests/server/onDisconnect.d.ts +1 -0
- package/dist/tests/server/onListen.d.ts +1 -0
- package/dist/tests/server/onLoadDocument.d.ts +1 -0
- package/dist/tests/server/onRequest.d.ts +1 -0
- package/dist/tests/server/onStoreDocument.d.ts +1 -0
- package/dist/tests/server/onUpgrade.d.ts +1 -0
- package/dist/tests/server/requiresAuthentication.d.ts +1 -0
- package/dist/tests/transformer/TiptapTransformer.d.ts +1 -0
- package/dist/tests/utils/createDirectory.d.ts +1 -0
- package/dist/tests/utils/flushRedis.d.ts +1 -0
- package/dist/tests/utils/index.d.ts +8 -0
- package/dist/tests/utils/newHocuspocus.d.ts +2 -0
- package/dist/tests/utils/newHocuspocusProvider.d.ts +3 -0
- package/dist/tests/utils/randomInteger.d.ts +1 -0
- package/dist/tests/utils/redisConnectionSettings.d.ts +4 -0
- package/dist/tests/utils/removeDirectory.d.ts +1 -0
- package/dist/tests/utils/retryableAssertion.d.ts +2 -0
- package/dist/tests/utils/sleep.d.ts +1 -0
- package/package.json +15 -11
- package/src/EventEmitter.ts +1 -1
- package/src/HocuspocusCloudProvider.ts +34 -0
- package/src/HocuspocusProvider.ts +389 -159
- package/src/IncomingMessage.ts +35 -11
- package/src/MessageReceiver.ts +56 -24
- package/src/MessageSender.ts +5 -17
- package/src/OutgoingMessage.ts +9 -9
- package/src/OutgoingMessages/AuthenticationMessage.ts +21 -0
- package/src/OutgoingMessages/AwarenessMessage.ts +1 -1
- package/src/OutgoingMessages/SyncStepOneMessage.ts +0 -1
- package/src/OutgoingMessages/UpdateMessage.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types.ts +70 -3
- package/CHANGELOG.md +0 -24
- package/dist/hocuspocus-provider.js.map +0 -1
- package/dist/packages/logger/src/index.d.ts +0 -13
- package/dist/packages/provider/src/utils/awarenessStatesToArray.d.ts +0 -4
- package/dist/packages/provider/src/utils/index.d.ts +0 -1
- package/dist/packages/redis/src/Redis.d.ts +0 -22
- package/dist/packages/redis/src/RedisCluster.d.ts +0 -4
- package/dist/packages/redis/src/index.d.ts +0 -2
- package/dist/packages/rocksdb/src/index.d.ts +0 -30
- package/dist/packages/server/src/CloseEvents.d.ts +0 -3
- package/dist/packages/throttle/src/index.d.ts +0 -28
- package/src/utils/awarenessStatesToArray.ts +0 -8
- package/src/utils/index.ts +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import * as Y from 'yjs'
|
|
3
2
|
import * as bc from 'lib0/broadcastchannel'
|
|
4
3
|
import * as time from 'lib0/time'
|
|
5
|
-
import * as encoding from 'lib0/encoding'
|
|
6
4
|
import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
|
|
7
5
|
import * as mutex from 'lib0/mutex'
|
|
8
|
-
import * as math from 'lib0/math'
|
|
9
6
|
import * as url from 'lib0/url'
|
|
10
|
-
|
|
11
|
-
import {
|
|
7
|
+
import type { Event, CloseEvent, MessageEvent } from 'ws'
|
|
8
|
+
import { retry } from '@lifeomic/attempt'
|
|
9
|
+
import {
|
|
10
|
+
awarenessStatesToArray, Forbidden, Unauthorized, WsReadyStates,
|
|
11
|
+
} from '@hocuspocus/common'
|
|
12
12
|
import EventEmitter from './EventEmitter'
|
|
13
13
|
import { IncomingMessage } from './IncomingMessage'
|
|
14
14
|
import { MessageReceiver } from './MessageReceiver'
|
|
@@ -16,78 +16,176 @@ import { MessageSender } from './MessageSender'
|
|
|
16
16
|
import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage'
|
|
17
17
|
import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage'
|
|
18
18
|
import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage'
|
|
19
|
+
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage'
|
|
19
20
|
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage'
|
|
20
21
|
import { UpdateMessage } from './OutgoingMessages/UpdateMessage'
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export interface
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
import {
|
|
23
|
+
ConstructableOutgoingMessage, onAuthenticationFailedParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters, onSyncedParameters, WebSocketStatus,
|
|
24
|
+
} from './types'
|
|
25
|
+
import { onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.'
|
|
26
|
+
|
|
27
|
+
export type HocuspocusProviderConfiguration =
|
|
28
|
+
Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>>
|
|
29
|
+
& Partial<CompleteHocuspocusProviderConfiguration>
|
|
30
|
+
|
|
31
|
+
export interface CompleteHocuspocusProviderConfiguration {
|
|
32
|
+
/**
|
|
33
|
+
* URL of your @hocuspocus/server instance
|
|
34
|
+
*/
|
|
35
|
+
url: string,
|
|
36
|
+
/**
|
|
37
|
+
* The identifier/name of your document
|
|
38
|
+
*/
|
|
39
|
+
name: string,
|
|
40
|
+
/**
|
|
41
|
+
* The actual Y.js document
|
|
42
|
+
*/
|
|
33
43
|
document: Y.Doc,
|
|
44
|
+
/**
|
|
45
|
+
* Pass `false` to start the connection manually.
|
|
46
|
+
*/
|
|
34
47
|
connect: boolean,
|
|
48
|
+
/**
|
|
49
|
+
* Pass false to disable broadcasting between browser tabs.
|
|
50
|
+
*/
|
|
51
|
+
broadcast: boolean,
|
|
52
|
+
/**
|
|
53
|
+
* An Awareness instance to keep the presence state of all clients.
|
|
54
|
+
*/
|
|
35
55
|
awareness: Awareness,
|
|
56
|
+
/**
|
|
57
|
+
* A token that’s sent to the backend for authentication purposes.
|
|
58
|
+
*/
|
|
59
|
+
token: string | (() => string) | (() => Promise<string>) | null,
|
|
60
|
+
/**
|
|
61
|
+
* URL parameters that should be added.
|
|
62
|
+
*/
|
|
36
63
|
parameters: { [key: string]: any },
|
|
64
|
+
/**
|
|
65
|
+
* An optional WebSocket polyfill, for example for Node.js
|
|
66
|
+
*/
|
|
37
67
|
WebSocketPolyfill: any,
|
|
68
|
+
/**
|
|
69
|
+
* Force syncing the document in the defined interval.
|
|
70
|
+
*/
|
|
38
71
|
forceSyncInterval: false | number,
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Disconnect when no message is received for the defined amount of milliseconds.
|
|
74
|
+
*/
|
|
41
75
|
messageReconnectTimeout: number,
|
|
42
|
-
|
|
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,
|
|
108
|
+
onAuthenticated: () => void,
|
|
109
|
+
onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void,
|
|
110
|
+
onOpen: (data: onOpenParameters) => void,
|
|
43
111
|
onConnect: () => void,
|
|
44
|
-
onMessage: (
|
|
45
|
-
onOutgoingMessage: (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
112
|
+
onMessage: (data: onMessageParameters) => void,
|
|
113
|
+
onOutgoingMessage: (data: onOutgoingMessageParameters) => void,
|
|
114
|
+
onStatus: (data: onStatusParameters) => void,
|
|
115
|
+
onSynced: (data: onSyncedParameters) => void,
|
|
116
|
+
onDisconnect: (data: onDisconnectParameters) => void,
|
|
117
|
+
onClose: (data: onCloseParameters) => void,
|
|
49
118
|
onDestroy: () => void,
|
|
50
|
-
|
|
51
|
-
|
|
119
|
+
onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void,
|
|
120
|
+
onAwarenessChange: (data: onAwarenessChangeParameters) => void,
|
|
121
|
+
/**
|
|
122
|
+
* Don’t output any warnings.
|
|
123
|
+
*/
|
|
124
|
+
quiet: boolean,
|
|
52
125
|
}
|
|
53
126
|
|
|
54
127
|
export class HocuspocusProvider extends EventEmitter {
|
|
55
|
-
public
|
|
56
|
-
url: '',
|
|
128
|
+
public configuration: CompleteHocuspocusProviderConfiguration = {
|
|
57
129
|
name: '',
|
|
130
|
+
url: '',
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
document: undefined,
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
awareness: undefined,
|
|
135
|
+
WebSocketPolyfill: undefined,
|
|
136
|
+
token: null,
|
|
58
137
|
parameters: {},
|
|
59
|
-
debug: false,
|
|
60
138
|
connect: true,
|
|
139
|
+
broadcast: true,
|
|
61
140
|
forceSyncInterval: false,
|
|
62
|
-
reconnectTimeoutBase: 1200,
|
|
63
|
-
maxReconnectTimeout: 2500,
|
|
64
141
|
// TODO: this should depend on awareness.outdatedTime
|
|
65
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
|
+
onAuthenticated: () => null,
|
|
160
|
+
onAuthenticationFailed: () => null,
|
|
66
161
|
onOpen: () => null,
|
|
67
162
|
onConnect: () => null,
|
|
68
163
|
onMessage: () => null,
|
|
69
164
|
onOutgoingMessage: () => null,
|
|
165
|
+
onStatus: () => null,
|
|
70
166
|
onSynced: () => null,
|
|
71
167
|
onDisconnect: () => null,
|
|
72
168
|
onClose: () => null,
|
|
73
169
|
onDestroy: () => null,
|
|
170
|
+
onAwarenessUpdate: () => null,
|
|
74
171
|
onAwarenessChange: () => null,
|
|
172
|
+
quiet: false,
|
|
75
173
|
}
|
|
76
174
|
|
|
77
|
-
awareness: Awareness
|
|
78
|
-
|
|
79
175
|
subscribedToBroadcastChannel = false
|
|
80
176
|
|
|
81
|
-
webSocket:
|
|
177
|
+
webSocket: WebSocket | null = null
|
|
82
178
|
|
|
83
179
|
shouldConnect = true
|
|
84
180
|
|
|
85
|
-
status
|
|
86
|
-
|
|
87
|
-
failedConnectionAttempts = 0
|
|
181
|
+
status = WebSocketStatus.Disconnected
|
|
88
182
|
|
|
89
183
|
isSynced = false
|
|
90
184
|
|
|
185
|
+
unsyncedChanges = 0
|
|
186
|
+
|
|
187
|
+
isAuthenticated = false
|
|
188
|
+
|
|
91
189
|
lastMessageReceived = 0
|
|
92
190
|
|
|
93
191
|
mux = mutex.createMutex()
|
|
@@ -97,77 +195,183 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
97
195
|
connectionChecker: null,
|
|
98
196
|
}
|
|
99
197
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
198
|
+
connectionAttempt: {
|
|
199
|
+
resolve: (value?: any) => void
|
|
200
|
+
reject: (reason?: any) => void
|
|
201
|
+
} | null = null
|
|
104
202
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
this.on('
|
|
114
|
-
this.on('
|
|
115
|
-
this.on('
|
|
116
|
-
this.on('
|
|
117
|
-
this.on('
|
|
118
|
-
this.on('
|
|
203
|
+
constructor(configuration: HocuspocusProviderConfiguration) {
|
|
204
|
+
super()
|
|
205
|
+
this.setConfiguration(configuration)
|
|
206
|
+
|
|
207
|
+
this.configuration.document = configuration.document ? configuration.document : new Y.Doc()
|
|
208
|
+
this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document)
|
|
209
|
+
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket
|
|
210
|
+
|
|
211
|
+
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
|
+
this.on('message', this.configuration.onMessage)
|
|
216
|
+
this.on('outgoingMessage', this.configuration.onOutgoingMessage)
|
|
217
|
+
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
|
+
this.on('destroy', this.configuration.onDestroy)
|
|
222
|
+
this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
|
|
223
|
+
this.on('awarenessChange', this.configuration.onAwarenessChange)
|
|
224
|
+
|
|
225
|
+
this.awareness.on('update', () => {
|
|
226
|
+
this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) })
|
|
227
|
+
})
|
|
119
228
|
|
|
120
229
|
this.awareness.on('change', () => {
|
|
121
|
-
this.emit('awarenessChange', {
|
|
122
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
123
|
-
})
|
|
230
|
+
this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) })
|
|
124
231
|
})
|
|
125
232
|
|
|
233
|
+
this.document.on('update', this.documentUpdateHandler.bind(this))
|
|
234
|
+
this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
|
|
235
|
+
this.registerEventListeners()
|
|
236
|
+
|
|
126
237
|
this.intervals.connectionChecker = setInterval(
|
|
127
238
|
this.checkConnection.bind(this),
|
|
128
|
-
this.
|
|
239
|
+
this.configuration.messageReconnectTimeout / 10,
|
|
129
240
|
)
|
|
130
241
|
|
|
131
|
-
|
|
132
|
-
this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
|
|
133
|
-
this.registerBeforeUnloadEventListener()
|
|
134
|
-
|
|
135
|
-
if (this.options.forceSyncInterval) {
|
|
242
|
+
if (this.configuration.forceSyncInterval) {
|
|
136
243
|
this.intervals.forceSync = setInterval(
|
|
137
244
|
this.forceSync.bind(this),
|
|
138
|
-
this.
|
|
245
|
+
this.configuration.forceSyncInterval,
|
|
139
246
|
)
|
|
140
247
|
}
|
|
141
248
|
|
|
142
|
-
if (
|
|
143
|
-
this.connect
|
|
249
|
+
if (typeof configuration.connect !== 'undefined') {
|
|
250
|
+
this.shouldConnect = configuration.connect
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!this.shouldConnect) {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.connect()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public setConfiguration(configuration: Partial<HocuspocusProviderConfiguration> = {}): void {
|
|
261
|
+
this.configuration = { ...this.configuration, ...configuration }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
boundConnect = this.connect.bind(this)
|
|
265
|
+
|
|
266
|
+
async connect() {
|
|
267
|
+
if (this.status === WebSocketStatus.Connected) {
|
|
268
|
+
return
|
|
144
269
|
}
|
|
270
|
+
|
|
271
|
+
this.unsyncedChanges = 0 // set to 0 in case we got reconnected
|
|
272
|
+
this.shouldConnect = true
|
|
273
|
+
this.subscribeToBroadcastChannel()
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
await retry(this.createWebSocketConnection.bind(this), {
|
|
277
|
+
delay: this.configuration.delay,
|
|
278
|
+
initialDelay: this.configuration.initialDelay,
|
|
279
|
+
factor: this.configuration.factor,
|
|
280
|
+
maxAttempts: this.configuration.maxAttempts,
|
|
281
|
+
minDelay: this.configuration.minDelay,
|
|
282
|
+
maxDelay: this.configuration.maxDelay,
|
|
283
|
+
jitter: this.configuration.jitter,
|
|
284
|
+
timeout: this.configuration.timeout,
|
|
285
|
+
beforeAttempt: context => {
|
|
286
|
+
if (!this.shouldConnect) {
|
|
287
|
+
context.abort()
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
} catch (error: any) {
|
|
292
|
+
// If we aborted the connection attempt then don’t throw an error
|
|
293
|
+
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
294
|
+
if (error && error.code !== 'ATTEMPT_ABORTED') {
|
|
295
|
+
throw error
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
createWebSocketConnection() {
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
// Init the WebSocket connection
|
|
303
|
+
const ws = new this.configuration.WebSocketPolyfill(this.url)
|
|
304
|
+
ws.binaryType = 'arraybuffer'
|
|
305
|
+
ws.onmessage = this.onMessage.bind(this)
|
|
306
|
+
ws.onclose = this.onClose.bind(this)
|
|
307
|
+
ws.onopen = this.onOpen.bind(this)
|
|
308
|
+
ws.onerror = (err: any) => {
|
|
309
|
+
reject(err)
|
|
310
|
+
}
|
|
311
|
+
this.webSocket = ws
|
|
312
|
+
|
|
313
|
+
// Reset the status
|
|
314
|
+
this.synced = false
|
|
315
|
+
this.status = WebSocketStatus.Connecting
|
|
316
|
+
this.emit('status', { status: WebSocketStatus.Connecting })
|
|
317
|
+
|
|
318
|
+
// Store resolve/reject for later use
|
|
319
|
+
this.connectionAttempt = {
|
|
320
|
+
resolve,
|
|
321
|
+
reject,
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
resolveConnectionAttempt() {
|
|
327
|
+
this.connectionAttempt?.resolve()
|
|
328
|
+
this.connectionAttempt = null
|
|
329
|
+
|
|
330
|
+
this.status = WebSocketStatus.Connected
|
|
331
|
+
this.emit('status', { status: WebSocketStatus.Connected })
|
|
332
|
+
this.emit('connect')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
stopConnectionAttempt() {
|
|
336
|
+
this.connectionAttempt = null
|
|
145
337
|
}
|
|
146
338
|
|
|
147
|
-
|
|
148
|
-
this.
|
|
339
|
+
rejectConnectionAttempt() {
|
|
340
|
+
this.connectionAttempt?.reject()
|
|
341
|
+
this.connectionAttempt = null
|
|
149
342
|
}
|
|
150
343
|
|
|
151
344
|
get document() {
|
|
152
|
-
return this.
|
|
345
|
+
return this.configuration.document
|
|
153
346
|
}
|
|
154
347
|
|
|
155
348
|
get awareness() {
|
|
156
|
-
return this.
|
|
349
|
+
return this.configuration.awareness
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
get hasUnsyncedChanges() {
|
|
353
|
+
return this.unsyncedChanges > 0
|
|
157
354
|
}
|
|
158
355
|
|
|
159
356
|
checkConnection() {
|
|
357
|
+
// Don’t check the connection when it’s not even established
|
|
160
358
|
if (this.status !== WebSocketStatus.Connected) {
|
|
161
359
|
return
|
|
162
360
|
}
|
|
163
361
|
|
|
164
|
-
|
|
362
|
+
// Don’t close then connection while waiting for the first message
|
|
363
|
+
if (!this.lastMessageReceived) {
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Don’t close the connection when a message was received recently
|
|
368
|
+
if (this.configuration.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
|
|
165
369
|
return
|
|
166
370
|
}
|
|
167
371
|
|
|
168
372
|
// No message received in a long time, not even your own
|
|
169
373
|
// Awareness updates, which are updated every 15 seconds.
|
|
170
|
-
this.webSocket
|
|
374
|
+
this.webSocket?.close()
|
|
171
375
|
}
|
|
172
376
|
|
|
173
377
|
forceSync() {
|
|
@@ -178,14 +382,19 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
178
382
|
this.send(SyncStepOneMessage, { document: this.document })
|
|
179
383
|
}
|
|
180
384
|
|
|
181
|
-
|
|
385
|
+
boundBeforeUnload = this.beforeUnload.bind(this)
|
|
386
|
+
|
|
387
|
+
beforeUnload() {
|
|
388
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
registerEventListeners() {
|
|
182
392
|
if (typeof window === 'undefined') {
|
|
183
393
|
return
|
|
184
394
|
}
|
|
185
395
|
|
|
186
|
-
window.addEventListener('
|
|
187
|
-
|
|
188
|
-
})
|
|
396
|
+
window.addEventListener('online', this.boundConnect)
|
|
397
|
+
window.addEventListener('beforeunload', this.boundBeforeUnload)
|
|
189
398
|
}
|
|
190
399
|
|
|
191
400
|
documentUpdateHandler(update: Uint8Array, origin: any) {
|
|
@@ -193,6 +402,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
193
402
|
return
|
|
194
403
|
}
|
|
195
404
|
|
|
405
|
+
this.unsyncedChanges += 1
|
|
196
406
|
this.send(UpdateMessage, { update }, true)
|
|
197
407
|
}
|
|
198
408
|
|
|
@@ -205,19 +415,32 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
205
415
|
}, true)
|
|
206
416
|
}
|
|
207
417
|
|
|
418
|
+
permissionDeniedHandler(reason: string) {
|
|
419
|
+
this.emit('authenticationFailed', { reason })
|
|
420
|
+
this.isAuthenticated = false
|
|
421
|
+
this.shouldConnect = false
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
authenticatedHandler() {
|
|
425
|
+
this.isAuthenticated = true
|
|
426
|
+
|
|
427
|
+
this.emit('authenticated')
|
|
428
|
+
this.startSync()
|
|
429
|
+
}
|
|
430
|
+
|
|
208
431
|
// Ensure that the URL always ends with /
|
|
209
432
|
get serverUrl() {
|
|
210
|
-
while (this.
|
|
211
|
-
return this.
|
|
433
|
+
while (this.configuration.url[this.configuration.url.length - 1] === '/') {
|
|
434
|
+
return this.configuration.url.slice(0, this.configuration.url.length - 1)
|
|
212
435
|
}
|
|
213
436
|
|
|
214
|
-
return this.
|
|
437
|
+
return this.configuration.url
|
|
215
438
|
}
|
|
216
439
|
|
|
217
440
|
get url() {
|
|
218
|
-
const encodedParams = url.encodeQueryParams(this.
|
|
441
|
+
const encodedParams = url.encodeQueryParams(this.configuration.parameters)
|
|
219
442
|
|
|
220
|
-
return `${this.serverUrl}/${this.
|
|
443
|
+
return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
|
|
221
444
|
}
|
|
222
445
|
|
|
223
446
|
get synced(): boolean {
|
|
@@ -234,13 +457,8 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
234
457
|
this.emit('sync', { state })
|
|
235
458
|
}
|
|
236
459
|
|
|
237
|
-
|
|
238
|
-
this.
|
|
239
|
-
|
|
240
|
-
if (this.status !== WebSocketStatus.Connected) {
|
|
241
|
-
this.createWebSocketConnection()
|
|
242
|
-
this.subscribeToBroadcastChannel()
|
|
243
|
-
}
|
|
460
|
+
get isAuthenticationRequired(): boolean {
|
|
461
|
+
return !!this.configuration.token && !this.isAuthenticated
|
|
244
462
|
}
|
|
245
463
|
|
|
246
464
|
disconnect() {
|
|
@@ -258,34 +476,29 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
258
476
|
}
|
|
259
477
|
}
|
|
260
478
|
|
|
261
|
-
|
|
262
|
-
|
|
479
|
+
async onOpen(event: Event) {
|
|
480
|
+
this.emit('open', { event })
|
|
481
|
+
|
|
482
|
+
if (this.isAuthenticationRequired) {
|
|
483
|
+
this.send(AuthenticationMessage, {
|
|
484
|
+
token: await this.getToken(),
|
|
485
|
+
})
|
|
263
486
|
return
|
|
264
487
|
}
|
|
265
488
|
|
|
266
|
-
this.
|
|
267
|
-
this.webSocket.binaryType = 'arraybuffer'
|
|
268
|
-
|
|
269
|
-
this.status = WebSocketStatus.Connecting
|
|
270
|
-
this.synced = false
|
|
271
|
-
|
|
272
|
-
this.webSocket.onmessage = this.onMessage.bind(this)
|
|
273
|
-
this.webSocket.onclose = this.onClose.bind(this)
|
|
274
|
-
this.webSocket.onopen = this.onOpen.bind(this)
|
|
275
|
-
|
|
276
|
-
this.emit('status', { status: 'connecting' })
|
|
489
|
+
this.startSync()
|
|
277
490
|
}
|
|
278
491
|
|
|
279
|
-
|
|
280
|
-
this.
|
|
281
|
-
|
|
492
|
+
async getToken() {
|
|
493
|
+
if (typeof this.configuration.token === 'function') {
|
|
494
|
+
const token = await this.configuration.token()
|
|
495
|
+
return token
|
|
496
|
+
}
|
|
282
497
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.status = WebSocketStatus.Connected
|
|
286
|
-
this.emit('status', { status: 'connected' })
|
|
287
|
-
this.emit('connect')
|
|
498
|
+
return this.configuration.token
|
|
499
|
+
}
|
|
288
500
|
|
|
501
|
+
startSync() {
|
|
289
502
|
this.send(SyncStepOneMessage, { document: this.document })
|
|
290
503
|
|
|
291
504
|
if (this.awareness.getLocalState() !== null) {
|
|
@@ -296,16 +509,12 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
296
509
|
}
|
|
297
510
|
}
|
|
298
511
|
|
|
299
|
-
send(Message:
|
|
300
|
-
const message = new Message()
|
|
301
|
-
|
|
512
|
+
send(Message: ConstructableOutgoingMessage, args: any, broadcast = false) {
|
|
302
513
|
if (broadcast) {
|
|
303
|
-
this.mux(() => {
|
|
304
|
-
this.broadcast(Message, args)
|
|
305
|
-
})
|
|
514
|
+
this.mux(() => { this.broadcast(Message, args) })
|
|
306
515
|
}
|
|
307
516
|
|
|
308
|
-
if (this.
|
|
517
|
+
if (this.webSocket?.readyState === WsReadyStates.Open) {
|
|
309
518
|
const messageSender = new MessageSender(Message, args)
|
|
310
519
|
|
|
311
520
|
this.emit('outgoingMessage', { message: messageSender.message })
|
|
@@ -314,9 +523,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
314
523
|
}
|
|
315
524
|
|
|
316
525
|
onMessage(event: MessageEvent) {
|
|
317
|
-
|
|
318
|
-
this.webSocketConnectionEstablished()
|
|
319
|
-
}
|
|
526
|
+
this.resolveConnectionAttempt()
|
|
320
527
|
|
|
321
528
|
this.lastMessageReceived = time.getUnixTime()
|
|
322
529
|
|
|
@@ -324,22 +531,17 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
324
531
|
|
|
325
532
|
this.emit('message', { event, message })
|
|
326
533
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// TODO: What’s that doing?
|
|
330
|
-
// if (encoding.length(encoder) > 1) {
|
|
331
|
-
// this.send(encoding.toUint8Array(encoder))
|
|
332
|
-
// }
|
|
534
|
+
new MessageReceiver(message).apply(this)
|
|
333
535
|
}
|
|
334
536
|
|
|
335
537
|
onClose(event: CloseEvent) {
|
|
336
538
|
this.emit('close', { event })
|
|
337
539
|
|
|
338
540
|
this.webSocket = null
|
|
541
|
+
this.isAuthenticated = false
|
|
542
|
+
this.synced = false
|
|
339
543
|
|
|
340
544
|
if (this.status === WebSocketStatus.Connected) {
|
|
341
|
-
this.synced = false
|
|
342
|
-
|
|
343
545
|
// update awareness (all users except local left)
|
|
344
546
|
removeAwarenessStates(
|
|
345
547
|
this.awareness,
|
|
@@ -348,29 +550,46 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
348
550
|
)
|
|
349
551
|
|
|
350
552
|
this.status = WebSocketStatus.Disconnected
|
|
351
|
-
this.emit('status', { status:
|
|
553
|
+
this.emit('status', { status: WebSocketStatus.Disconnected })
|
|
352
554
|
this.emit('disconnect', { event })
|
|
353
|
-
} else {
|
|
354
|
-
this.failedConnectionAttempts += 1
|
|
355
555
|
}
|
|
356
556
|
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
))
|
|
557
|
+
if (event.code === Unauthorized.code) {
|
|
558
|
+
if (!this.configuration.quiet) {
|
|
559
|
+
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.')
|
|
560
|
+
}
|
|
362
561
|
|
|
363
|
-
this.
|
|
364
|
-
|
|
562
|
+
this.shouldConnect = false
|
|
563
|
+
}
|
|
365
564
|
|
|
565
|
+
if (event.code === Forbidden.code) {
|
|
566
|
+
if (!this.configuration.quiet) {
|
|
567
|
+
console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.')
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (this.connectionAttempt) {
|
|
572
|
+
// That connection attempt failed.
|
|
573
|
+
this.rejectConnectionAttempt()
|
|
574
|
+
} else if (this.shouldConnect) {
|
|
575
|
+
// The connection was closed by the server. Let’s just try again.
|
|
576
|
+
this.connect()
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// If we’ll reconnect, we’re done for now.
|
|
580
|
+
if (this.shouldConnect) {
|
|
366
581
|
return
|
|
367
582
|
}
|
|
368
583
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this.emit('disconnect', { event })
|
|
584
|
+
// The status is set correctly already.
|
|
585
|
+
if (this.status === WebSocketStatus.Disconnected) {
|
|
586
|
+
return
|
|
373
587
|
}
|
|
588
|
+
|
|
589
|
+
// Let’s update the connection status.
|
|
590
|
+
this.status = WebSocketStatus.Disconnected
|
|
591
|
+
this.emit('status', { status: WebSocketStatus.Disconnected })
|
|
592
|
+
this.emit('disconnect', { event })
|
|
374
593
|
}
|
|
375
594
|
|
|
376
595
|
destroy() {
|
|
@@ -382,33 +601,46 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
382
601
|
|
|
383
602
|
clearInterval(this.intervals.connectionChecker)
|
|
384
603
|
|
|
604
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
|
|
605
|
+
|
|
606
|
+
// If there is still a connection attempt outstanding then we should stop
|
|
607
|
+
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
608
|
+
// handler and trigger a retry
|
|
609
|
+
this.stopConnectionAttempt()
|
|
610
|
+
|
|
385
611
|
this.disconnect()
|
|
386
612
|
|
|
387
613
|
this.awareness.off('update', this.awarenessUpdateHandler)
|
|
388
614
|
this.document.off('update', this.documentUpdateHandler)
|
|
389
615
|
|
|
390
616
|
this.removeAllListeners()
|
|
617
|
+
|
|
618
|
+
if (typeof window === 'undefined') {
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
window.removeEventListener('online', this.boundConnect)
|
|
623
|
+
window.removeEventListener('beforeunload', this.boundBeforeUnload)
|
|
391
624
|
}
|
|
392
625
|
|
|
393
626
|
get broadcastChannel() {
|
|
394
|
-
return `${this.serverUrl}/${this.
|
|
627
|
+
return `${this.serverUrl}/${this.configuration.name}`
|
|
395
628
|
}
|
|
396
629
|
|
|
630
|
+
boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
|
|
631
|
+
|
|
397
632
|
broadcastChannelSubscriber(data: ArrayBuffer) {
|
|
398
633
|
this.mux(() => {
|
|
399
634
|
const message = new IncomingMessage(data)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// if (encoding.length(encoder) > 1) {
|
|
404
|
-
// this.broadcast(encoding.toUint8Array(encoder))
|
|
405
|
-
// }
|
|
635
|
+
new MessageReceiver(message)
|
|
636
|
+
.setBroadcasted(true)
|
|
637
|
+
.apply(this, false)
|
|
406
638
|
})
|
|
407
639
|
}
|
|
408
640
|
|
|
409
641
|
subscribeToBroadcastChannel() {
|
|
410
642
|
if (!this.subscribedToBroadcastChannel) {
|
|
411
|
-
bc.subscribe(this.broadcastChannel, this.
|
|
643
|
+
bc.subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber)
|
|
412
644
|
this.subscribedToBroadcastChannel = true
|
|
413
645
|
}
|
|
414
646
|
|
|
@@ -429,23 +661,21 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
429
661
|
}, true)
|
|
430
662
|
|
|
431
663
|
if (this.subscribedToBroadcastChannel) {
|
|
432
|
-
bc.unsubscribe(this.broadcastChannel, this.
|
|
664
|
+
bc.unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber)
|
|
433
665
|
this.subscribedToBroadcastChannel = false
|
|
434
666
|
}
|
|
435
667
|
}
|
|
436
668
|
|
|
437
|
-
broadcast(Message:
|
|
438
|
-
if (this.
|
|
439
|
-
|
|
669
|
+
broadcast(Message: ConstructableOutgoingMessage, args?: any) {
|
|
670
|
+
if (!this.configuration.broadcast) {
|
|
671
|
+
return
|
|
440
672
|
}
|
|
441
|
-
}
|
|
442
673
|
|
|
443
|
-
|
|
444
|
-
if (!this.options.debug) {
|
|
674
|
+
if (!this.subscribedToBroadcastChannel) {
|
|
445
675
|
return
|
|
446
676
|
}
|
|
447
677
|
|
|
448
|
-
|
|
678
|
+
new MessageSender(Message, args).broadcast(this.broadcastChannel)
|
|
449
679
|
}
|
|
450
680
|
|
|
451
681
|
setAwarenessField(key: string, value: any) {
|