@hocuspocus/provider 1.0.0-alpha.8 → 1.0.0-beta.2
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 +378 -185
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +376 -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 +117 -33
- 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 +27 -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 +180 -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 → playground}/backend/src/redis.d.ts +0 -0
- package/dist/{demos → playground}/backend/src/slow.d.ts +0 -0
- package/dist/{demos → playground}/backend/src/webhook.d.ts +0 -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/beforeHandleMessage.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/server/websocketError.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 +14 -10
- package/src/EventEmitter.ts +1 -1
- package/src/HocuspocusCloudProvider.ts +34 -0
- package/src/HocuspocusProvider.ts +415 -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 -56
- package/LICENSE.md +0 -21
- 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,54 +16,148 @@ 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
|
-
onStatus: (
|
|
47
|
-
onSynced: () => void,
|
|
48
|
-
onDisconnect: (
|
|
49
|
-
onClose: (
|
|
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,
|
|
50
118
|
onDestroy: () => void,
|
|
51
|
-
|
|
52
|
-
|
|
119
|
+
onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void,
|
|
120
|
+
onAwarenessChange: (data: onAwarenessChangeParameters) => void,
|
|
121
|
+
/**
|
|
122
|
+
* Don’t output any warnings.
|
|
123
|
+
*/
|
|
124
|
+
quiet: boolean,
|
|
53
125
|
}
|
|
54
126
|
|
|
55
127
|
export class HocuspocusProvider extends EventEmitter {
|
|
56
|
-
public
|
|
57
|
-
url: '',
|
|
128
|
+
public configuration: CompleteHocuspocusProviderConfiguration = {
|
|
58
129
|
name: '',
|
|
130
|
+
url: '',
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
document: undefined,
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
awareness: undefined,
|
|
135
|
+
WebSocketPolyfill: undefined,
|
|
136
|
+
token: null,
|
|
59
137
|
parameters: {},
|
|
60
|
-
debug: false,
|
|
61
138
|
connect: true,
|
|
139
|
+
broadcast: true,
|
|
62
140
|
forceSyncInterval: false,
|
|
63
|
-
reconnectTimeoutBase: 1200,
|
|
64
|
-
maxReconnectTimeout: 2500,
|
|
65
141
|
// TODO: this should depend on awareness.outdatedTime
|
|
66
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,
|
|
67
161
|
onOpen: () => null,
|
|
68
162
|
onConnect: () => null,
|
|
69
163
|
onMessage: () => null,
|
|
@@ -73,23 +167,25 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
73
167
|
onDisconnect: () => null,
|
|
74
168
|
onClose: () => null,
|
|
75
169
|
onDestroy: () => null,
|
|
170
|
+
onAwarenessUpdate: () => null,
|
|
76
171
|
onAwarenessChange: () => null,
|
|
172
|
+
quiet: false,
|
|
77
173
|
}
|
|
78
174
|
|
|
79
|
-
awareness: Awareness
|
|
80
|
-
|
|
81
175
|
subscribedToBroadcastChannel = false
|
|
82
176
|
|
|
83
|
-
webSocket:
|
|
177
|
+
webSocket: WebSocket | null = null
|
|
84
178
|
|
|
85
179
|
shouldConnect = true
|
|
86
180
|
|
|
87
|
-
status
|
|
88
|
-
|
|
89
|
-
failedConnectionAttempts = 0
|
|
181
|
+
status = WebSocketStatus.Disconnected
|
|
90
182
|
|
|
91
183
|
isSynced = false
|
|
92
184
|
|
|
185
|
+
unsyncedChanges = 0
|
|
186
|
+
|
|
187
|
+
isAuthenticated = false
|
|
188
|
+
|
|
93
189
|
lastMessageReceived = 0
|
|
94
190
|
|
|
95
191
|
mux = mutex.createMutex()
|
|
@@ -99,78 +195,210 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
99
195
|
connectionChecker: null,
|
|
100
196
|
}
|
|
101
197
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
198
|
+
connectionAttempt: {
|
|
199
|
+
resolve: (value?: any) => void
|
|
200
|
+
reject: (reason?: any) => void
|
|
201
|
+
} | null = null
|
|
106
202
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
this.on('
|
|
116
|
-
this.on('
|
|
117
|
-
this.on('
|
|
118
|
-
this.on('
|
|
119
|
-
this.on('
|
|
120
|
-
this.on('
|
|
121
|
-
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
|
+
})
|
|
122
228
|
|
|
123
229
|
this.awareness.on('change', () => {
|
|
124
|
-
this.emit('awarenessChange', {
|
|
125
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
126
|
-
})
|
|
230
|
+
this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) })
|
|
127
231
|
})
|
|
128
232
|
|
|
233
|
+
this.document.on('update', this.documentUpdateHandler.bind(this))
|
|
234
|
+
this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
|
|
235
|
+
this.registerEventListeners()
|
|
236
|
+
|
|
129
237
|
this.intervals.connectionChecker = setInterval(
|
|
130
238
|
this.checkConnection.bind(this),
|
|
131
|
-
this.
|
|
239
|
+
this.configuration.messageReconnectTimeout / 10,
|
|
132
240
|
)
|
|
133
241
|
|
|
134
|
-
|
|
135
|
-
this.awareness.on('update', this.awarenessUpdateHandler.bind(this))
|
|
136
|
-
this.registerBeforeUnloadEventListener()
|
|
137
|
-
|
|
138
|
-
if (this.options.forceSyncInterval) {
|
|
242
|
+
if (this.configuration.forceSyncInterval) {
|
|
139
243
|
this.intervals.forceSync = setInterval(
|
|
140
244
|
this.forceSync.bind(this),
|
|
141
|
-
this.
|
|
245
|
+
this.configuration.forceSyncInterval,
|
|
142
246
|
)
|
|
143
247
|
}
|
|
144
248
|
|
|
145
|
-
if (
|
|
146
|
-
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
|
+
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
|
+
}
|
|
147
314
|
}
|
|
315
|
+
|
|
316
|
+
const { retryPromise, cancelFunc } = abortableRetry()
|
|
317
|
+
this.cancelWebsocketRetry = cancelFunc
|
|
318
|
+
|
|
319
|
+
return retryPromise
|
|
148
320
|
}
|
|
149
321
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
152
369
|
}
|
|
153
370
|
|
|
154
371
|
get document() {
|
|
155
|
-
return this.
|
|
372
|
+
return this.configuration.document
|
|
156
373
|
}
|
|
157
374
|
|
|
158
375
|
get awareness() {
|
|
159
|
-
return this.
|
|
376
|
+
return this.configuration.awareness
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
get hasUnsyncedChanges() {
|
|
380
|
+
return this.unsyncedChanges > 0
|
|
160
381
|
}
|
|
161
382
|
|
|
162
383
|
checkConnection() {
|
|
384
|
+
// Don’t check the connection when it’s not even established
|
|
163
385
|
if (this.status !== WebSocketStatus.Connected) {
|
|
164
386
|
return
|
|
165
387
|
}
|
|
166
388
|
|
|
167
|
-
|
|
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) {
|
|
168
396
|
return
|
|
169
397
|
}
|
|
170
398
|
|
|
171
399
|
// No message received in a long time, not even your own
|
|
172
400
|
// Awareness updates, which are updated every 15 seconds.
|
|
173
|
-
this.webSocket
|
|
401
|
+
this.webSocket?.close()
|
|
174
402
|
}
|
|
175
403
|
|
|
176
404
|
forceSync() {
|
|
@@ -181,14 +409,19 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
181
409
|
this.send(SyncStepOneMessage, { document: this.document })
|
|
182
410
|
}
|
|
183
411
|
|
|
184
|
-
|
|
412
|
+
boundBeforeUnload = this.beforeUnload.bind(this)
|
|
413
|
+
|
|
414
|
+
beforeUnload() {
|
|
415
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload')
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
registerEventListeners() {
|
|
185
419
|
if (typeof window === 'undefined') {
|
|
186
420
|
return
|
|
187
421
|
}
|
|
188
422
|
|
|
189
|
-
window.addEventListener('
|
|
190
|
-
|
|
191
|
-
})
|
|
423
|
+
window.addEventListener('online', this.boundConnect)
|
|
424
|
+
window.addEventListener('beforeunload', this.boundBeforeUnload)
|
|
192
425
|
}
|
|
193
426
|
|
|
194
427
|
documentUpdateHandler(update: Uint8Array, origin: any) {
|
|
@@ -196,6 +429,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
196
429
|
return
|
|
197
430
|
}
|
|
198
431
|
|
|
432
|
+
this.unsyncedChanges += 1
|
|
199
433
|
this.send(UpdateMessage, { update }, true)
|
|
200
434
|
}
|
|
201
435
|
|
|
@@ -208,19 +442,32 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
208
442
|
}, true)
|
|
209
443
|
}
|
|
210
444
|
|
|
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
|
+
|
|
211
458
|
// Ensure that the URL always ends with /
|
|
212
459
|
get serverUrl() {
|
|
213
|
-
while (this.
|
|
214
|
-
return this.
|
|
460
|
+
while (this.configuration.url[this.configuration.url.length - 1] === '/') {
|
|
461
|
+
return this.configuration.url.slice(0, this.configuration.url.length - 1)
|
|
215
462
|
}
|
|
216
463
|
|
|
217
|
-
return this.
|
|
464
|
+
return this.configuration.url
|
|
218
465
|
}
|
|
219
466
|
|
|
220
467
|
get url() {
|
|
221
|
-
const encodedParams = url.encodeQueryParams(this.
|
|
468
|
+
const encodedParams = url.encodeQueryParams(this.configuration.parameters)
|
|
222
469
|
|
|
223
|
-
return `${this.serverUrl}/${this.
|
|
470
|
+
return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
|
|
224
471
|
}
|
|
225
472
|
|
|
226
473
|
get synced(): boolean {
|
|
@@ -237,13 +484,8 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
237
484
|
this.emit('sync', { state })
|
|
238
485
|
}
|
|
239
486
|
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
|
|
243
|
-
if (this.status !== WebSocketStatus.Connected) {
|
|
244
|
-
this.createWebSocketConnection()
|
|
245
|
-
this.subscribeToBroadcastChannel()
|
|
246
|
-
}
|
|
487
|
+
get isAuthenticationRequired(): boolean {
|
|
488
|
+
return !!this.configuration.token && !this.isAuthenticated
|
|
247
489
|
}
|
|
248
490
|
|
|
249
491
|
disconnect() {
|
|
@@ -261,34 +503,29 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
261
503
|
}
|
|
262
504
|
}
|
|
263
505
|
|
|
264
|
-
|
|
265
|
-
|
|
506
|
+
async onOpen(event: Event) {
|
|
507
|
+
this.emit('open', { event })
|
|
508
|
+
|
|
509
|
+
if (this.isAuthenticationRequired) {
|
|
510
|
+
this.send(AuthenticationMessage, {
|
|
511
|
+
token: await this.getToken(),
|
|
512
|
+
})
|
|
266
513
|
return
|
|
267
514
|
}
|
|
268
515
|
|
|
269
|
-
this.
|
|
270
|
-
this.webSocket.binaryType = 'arraybuffer'
|
|
271
|
-
|
|
272
|
-
this.status = WebSocketStatus.Connecting
|
|
273
|
-
this.synced = false
|
|
274
|
-
|
|
275
|
-
this.webSocket.onmessage = this.onMessage.bind(this)
|
|
276
|
-
this.webSocket.onclose = this.onClose.bind(this)
|
|
277
|
-
this.webSocket.onopen = this.onOpen.bind(this)
|
|
278
|
-
|
|
279
|
-
this.emit('status', { status: 'connecting' })
|
|
516
|
+
this.startSync()
|
|
280
517
|
}
|
|
281
518
|
|
|
282
|
-
|
|
283
|
-
this.
|
|
284
|
-
|
|
519
|
+
async getToken() {
|
|
520
|
+
if (typeof this.configuration.token === 'function') {
|
|
521
|
+
const token = await this.configuration.token()
|
|
522
|
+
return token
|
|
523
|
+
}
|
|
285
524
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.status = WebSocketStatus.Connected
|
|
289
|
-
this.emit('status', { status: 'connected' })
|
|
290
|
-
this.emit('connect')
|
|
525
|
+
return this.configuration.token
|
|
526
|
+
}
|
|
291
527
|
|
|
528
|
+
startSync() {
|
|
292
529
|
this.send(SyncStepOneMessage, { document: this.document })
|
|
293
530
|
|
|
294
531
|
if (this.awareness.getLocalState() !== null) {
|
|
@@ -299,14 +536,12 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
299
536
|
}
|
|
300
537
|
}
|
|
301
538
|
|
|
302
|
-
send(Message:
|
|
539
|
+
send(Message: ConstructableOutgoingMessage, args: any, broadcast = false) {
|
|
303
540
|
if (broadcast) {
|
|
304
|
-
this.mux(() => {
|
|
305
|
-
this.broadcast(Message, args)
|
|
306
|
-
})
|
|
541
|
+
this.mux(() => { this.broadcast(Message, args) })
|
|
307
542
|
}
|
|
308
543
|
|
|
309
|
-
if (this.
|
|
544
|
+
if (this.webSocket?.readyState === WsReadyStates.Open) {
|
|
310
545
|
const messageSender = new MessageSender(Message, args)
|
|
311
546
|
|
|
312
547
|
this.emit('outgoingMessage', { message: messageSender.message })
|
|
@@ -315,9 +550,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
315
550
|
}
|
|
316
551
|
|
|
317
552
|
onMessage(event: MessageEvent) {
|
|
318
|
-
|
|
319
|
-
this.webSocketConnectionEstablished()
|
|
320
|
-
}
|
|
553
|
+
this.resolveConnectionAttempt()
|
|
321
554
|
|
|
322
555
|
this.lastMessageReceived = time.getUnixTime()
|
|
323
556
|
|
|
@@ -325,22 +558,17 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
325
558
|
|
|
326
559
|
this.emit('message', { event, message })
|
|
327
560
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// TODO: What’s that doing?
|
|
331
|
-
// if (encoding.length(encoder) > 1) {
|
|
332
|
-
// this.send(encoding.toUint8Array(encoder))
|
|
333
|
-
// }
|
|
561
|
+
new MessageReceiver(message).apply(this)
|
|
334
562
|
}
|
|
335
563
|
|
|
336
564
|
onClose(event: CloseEvent) {
|
|
337
565
|
this.emit('close', { event })
|
|
338
566
|
|
|
339
567
|
this.webSocket = null
|
|
568
|
+
this.isAuthenticated = false
|
|
569
|
+
this.synced = false
|
|
340
570
|
|
|
341
571
|
if (this.status === WebSocketStatus.Connected) {
|
|
342
|
-
this.synced = false
|
|
343
|
-
|
|
344
572
|
// update awareness (all users except local left)
|
|
345
573
|
removeAwarenessStates(
|
|
346
574
|
this.awareness,
|
|
@@ -349,29 +577,46 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
349
577
|
)
|
|
350
578
|
|
|
351
579
|
this.status = WebSocketStatus.Disconnected
|
|
352
|
-
this.emit('status', { status:
|
|
580
|
+
this.emit('status', { status: WebSocketStatus.Disconnected })
|
|
353
581
|
this.emit('disconnect', { event })
|
|
354
|
-
} else {
|
|
355
|
-
this.failedConnectionAttempts += 1
|
|
356
582
|
}
|
|
357
583
|
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
))
|
|
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
|
+
}
|
|
363
588
|
|
|
364
|
-
this.
|
|
365
|
-
|
|
589
|
+
this.shouldConnect = false
|
|
590
|
+
}
|
|
366
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) {
|
|
367
608
|
return
|
|
368
609
|
}
|
|
369
610
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.emit('disconnect', { event })
|
|
611
|
+
// The status is set correctly already.
|
|
612
|
+
if (this.status === WebSocketStatus.Disconnected) {
|
|
613
|
+
return
|
|
374
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 })
|
|
375
620
|
}
|
|
376
621
|
|
|
377
622
|
destroy() {
|
|
@@ -383,33 +628,46 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
383
628
|
|
|
384
629
|
clearInterval(this.intervals.connectionChecker)
|
|
385
630
|
|
|
631
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
|
|
632
|
+
|
|
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
|
+
|
|
386
638
|
this.disconnect()
|
|
387
639
|
|
|
388
640
|
this.awareness.off('update', this.awarenessUpdateHandler)
|
|
389
641
|
this.document.off('update', this.documentUpdateHandler)
|
|
390
642
|
|
|
391
643
|
this.removeAllListeners()
|
|
644
|
+
|
|
645
|
+
if (typeof window === 'undefined') {
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
window.removeEventListener('online', this.boundConnect)
|
|
650
|
+
window.removeEventListener('beforeunload', this.boundBeforeUnload)
|
|
392
651
|
}
|
|
393
652
|
|
|
394
653
|
get broadcastChannel() {
|
|
395
|
-
return `${this.serverUrl}/${this.
|
|
654
|
+
return `${this.serverUrl}/${this.configuration.name}`
|
|
396
655
|
}
|
|
397
656
|
|
|
657
|
+
boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
|
|
658
|
+
|
|
398
659
|
broadcastChannelSubscriber(data: ArrayBuffer) {
|
|
399
660
|
this.mux(() => {
|
|
400
661
|
const message = new IncomingMessage(data)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// if (encoding.length(encoder) > 1) {
|
|
405
|
-
// this.broadcast(encoding.toUint8Array(encoder))
|
|
406
|
-
// }
|
|
662
|
+
new MessageReceiver(message)
|
|
663
|
+
.setBroadcasted(true)
|
|
664
|
+
.apply(this, false)
|
|
407
665
|
})
|
|
408
666
|
}
|
|
409
667
|
|
|
410
668
|
subscribeToBroadcastChannel() {
|
|
411
669
|
if (!this.subscribedToBroadcastChannel) {
|
|
412
|
-
bc.subscribe(this.broadcastChannel, this.
|
|
670
|
+
bc.subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber)
|
|
413
671
|
this.subscribedToBroadcastChannel = true
|
|
414
672
|
}
|
|
415
673
|
|
|
@@ -430,23 +688,21 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
430
688
|
}, true)
|
|
431
689
|
|
|
432
690
|
if (this.subscribedToBroadcastChannel) {
|
|
433
|
-
bc.unsubscribe(this.broadcastChannel, this.
|
|
691
|
+
bc.unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber)
|
|
434
692
|
this.subscribedToBroadcastChannel = false
|
|
435
693
|
}
|
|
436
694
|
}
|
|
437
695
|
|
|
438
|
-
broadcast(Message:
|
|
439
|
-
if (this.
|
|
440
|
-
|
|
696
|
+
broadcast(Message: ConstructableOutgoingMessage, args?: any) {
|
|
697
|
+
if (!this.configuration.broadcast) {
|
|
698
|
+
return
|
|
441
699
|
}
|
|
442
|
-
}
|
|
443
700
|
|
|
444
|
-
|
|
445
|
-
if (!this.options.debug) {
|
|
701
|
+
if (!this.subscribedToBroadcastChannel) {
|
|
446
702
|
return
|
|
447
703
|
}
|
|
448
704
|
|
|
449
|
-
|
|
705
|
+
new MessageSender(Message, args).broadcast(this.broadcastChannel)
|
|
450
706
|
}
|
|
451
707
|
|
|
452
708
|
setAwarenessField(key: string, value: any) {
|