@hocuspocus/provider 3.4.6-rc.2 → 4.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/dist/hocuspocus-provider.cjs +138 -69
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +139 -70
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/index.d.ts +44 -13
- package/package.json +6 -3
- package/src/HocuspocusProvider.ts +58 -27
- package/src/HocuspocusProviderWebsocket.ts +87 -11
- package/src/MessageReceiver.ts +2 -6
- package/src/OutgoingMessages/AuthenticationMessage.ts +2 -0
- package/src/types.ts +2 -1
- package/src/version.ts +5 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Event, MessageEvent } from "ws";
|
|
2
1
|
import * as Y from "yjs";
|
|
3
2
|
import { CloseEvent } from "@hocuspocus/common";
|
|
4
3
|
|
|
5
|
-
//#region node_modules/lib0/observable.d.ts
|
|
4
|
+
//#region node_modules/.pnpm/lib0@0.2.117/node_modules/lib0/observable.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Handles named events.
|
|
8
7
|
*
|
|
@@ -43,7 +42,7 @@ declare class Observable<N> {
|
|
|
43
42
|
destroy(): void;
|
|
44
43
|
}
|
|
45
44
|
//#endregion
|
|
46
|
-
//#region node_modules/y-protocols/awareness.d.ts
|
|
45
|
+
//#region node_modules/.pnpm/y-protocols@1.0.7_yjs@13.6.29/node_modules/y-protocols/awareness.d.ts
|
|
47
46
|
/**
|
|
48
47
|
* @typedef {Object} MetaClientState
|
|
49
48
|
* @property {number} MetaClientState.clock
|
|
@@ -132,34 +131,35 @@ declare class EventEmitter {
|
|
|
132
131
|
removeAllListeners(): void;
|
|
133
132
|
}
|
|
134
133
|
//#endregion
|
|
135
|
-
//#region node_modules/lib0/encoding.d.ts
|
|
134
|
+
//#region node_modules/.pnpm/lib0@0.2.117/node_modules/lib0/encoding.d.ts
|
|
136
135
|
/**
|
|
137
136
|
* A BinaryEncoder handles the encoding to an Uint8Array.
|
|
138
137
|
*/
|
|
139
138
|
declare class Encoder {
|
|
140
139
|
cpos: number;
|
|
141
|
-
cbuf: Uint8Array
|
|
140
|
+
cbuf: Uint8Array<ArrayBuffer>;
|
|
142
141
|
/**
|
|
143
142
|
* @type {Array<Uint8Array>}
|
|
144
143
|
*/
|
|
145
144
|
bufs: Array<Uint8Array>;
|
|
146
145
|
}
|
|
147
146
|
//#endregion
|
|
148
|
-
//#region node_modules/lib0/decoding.d.ts
|
|
147
|
+
//#region node_modules/.pnpm/lib0@0.2.117/node_modules/lib0/decoding.d.ts
|
|
149
148
|
/**
|
|
150
149
|
* A Decoder handles the decoding of an Uint8Array.
|
|
150
|
+
* @template {ArrayBufferLike} [Buf=ArrayBufferLike]
|
|
151
151
|
*/
|
|
152
|
-
declare class Decoder {
|
|
152
|
+
declare class Decoder<Buf extends ArrayBufferLike = ArrayBufferLike> {
|
|
153
153
|
/**
|
|
154
|
-
* @param {Uint8Array} uint8Array Binary data to decode
|
|
154
|
+
* @param {Uint8Array<Buf>} uint8Array Binary data to decode
|
|
155
155
|
*/
|
|
156
|
-
constructor(uint8Array: Uint8Array);
|
|
156
|
+
constructor(uint8Array: Uint8Array<Buf>);
|
|
157
157
|
/**
|
|
158
158
|
* Decoding target.
|
|
159
159
|
*
|
|
160
|
-
* @type {Uint8Array}
|
|
160
|
+
* @type {Uint8Array<Buf>}
|
|
161
161
|
*/
|
|
162
|
-
arr: Uint8Array
|
|
162
|
+
arr: Uint8Array<Buf>;
|
|
163
163
|
/**
|
|
164
164
|
* Current decoding position.
|
|
165
165
|
*
|
|
@@ -190,7 +190,7 @@ declare class OutgoingMessage implements OutgoingMessageInterface {
|
|
|
190
190
|
type?: MessageType;
|
|
191
191
|
constructor();
|
|
192
192
|
get(args: Partial<OutgoingMessageArguments>): Encoder | undefined;
|
|
193
|
-
toUint8Array(): Uint8Array<
|
|
193
|
+
toUint8Array(): Uint8Array<ArrayBuffer>;
|
|
194
194
|
}
|
|
195
195
|
//#endregion
|
|
196
196
|
//#region packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts
|
|
@@ -243,7 +243,9 @@ declare enum MessageType {
|
|
|
243
243
|
QueryAwareness = 3,
|
|
244
244
|
Stateless = 5,
|
|
245
245
|
CLOSE = 7,
|
|
246
|
-
SyncStatus = 8
|
|
246
|
+
SyncStatus = 8,
|
|
247
|
+
Ping = 9,
|
|
248
|
+
Pong = 10
|
|
247
249
|
}
|
|
248
250
|
declare enum WebSocketStatus {
|
|
249
251
|
Connecting = "connecting",
|
|
@@ -394,6 +396,7 @@ interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
|
394
396
|
providerMap: Map<string, HocuspocusProvider>;
|
|
395
397
|
}
|
|
396
398
|
declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
399
|
+
private static readonly DEDUPLICATABLE_TYPES;
|
|
397
400
|
private messageQueue;
|
|
398
401
|
configuration: CompleteHocuspocusProviderWebsocketConfiguration;
|
|
399
402
|
webSocket: HocusPocusWebSocket | null;
|
|
@@ -421,6 +424,10 @@ declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
421
424
|
cleanupWebSocket(): void;
|
|
422
425
|
createWebSocketConnection(): Promise<unknown>;
|
|
423
426
|
onMessage(event: MessageEvent): void;
|
|
427
|
+
/**
|
|
428
|
+
* Send application-level Pong response to server Ping
|
|
429
|
+
*/
|
|
430
|
+
private sendPong;
|
|
424
431
|
resolveConnectionAttempt(): void;
|
|
425
432
|
stopConnectionAttempt(): void;
|
|
426
433
|
rejectConnectionAttempt(): void;
|
|
@@ -429,6 +436,8 @@ declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
429
436
|
get serverUrl(): string;
|
|
430
437
|
get url(): string;
|
|
431
438
|
disconnect(): void;
|
|
439
|
+
private parseQueuedMessage;
|
|
440
|
+
private addToQueue;
|
|
432
441
|
send(message: any): void;
|
|
433
442
|
onClose({
|
|
434
443
|
event
|
|
@@ -464,6 +473,17 @@ interface CompleteHocuspocusProviderConfiguration {
|
|
|
464
473
|
* Hocuspocus websocket provider
|
|
465
474
|
*/
|
|
466
475
|
websocketProvider: HocuspocusProviderWebsocket;
|
|
476
|
+
/**
|
|
477
|
+
* Enable session-aware multiplexing. When true, the provider embeds a unique
|
|
478
|
+
* sessionId in the documentName field of every message, allowing multiple
|
|
479
|
+
* providers with the same document name on a single WebSocket connection.
|
|
480
|
+
*
|
|
481
|
+
* Only set this to `false` when connecting to a v3 server that does not
|
|
482
|
+
* support session awareness.
|
|
483
|
+
*
|
|
484
|
+
* Default: true
|
|
485
|
+
*/
|
|
486
|
+
sessionAwareness: boolean;
|
|
467
487
|
/**
|
|
468
488
|
* Force syncing the document in the defined interval.
|
|
469
489
|
*/
|
|
@@ -495,6 +515,17 @@ declare class HocuspocusProvider extends EventEmitter {
|
|
|
495
515
|
authorizedScope: AuthorizedScope | undefined;
|
|
496
516
|
manageSocket: boolean;
|
|
497
517
|
private _isAttached;
|
|
518
|
+
/**
|
|
519
|
+
* Unique session identifier for this provider instance.
|
|
520
|
+
* Used for multiplexing multiple providers with the same document name on a single WebSocket.
|
|
521
|
+
*/
|
|
522
|
+
sessionId: string;
|
|
523
|
+
/**
|
|
524
|
+
* The effective name used as the first VarString in messages.
|
|
525
|
+
* When `sessionAwareness` is enabled, returns a composite key (documentName\0sessionId).
|
|
526
|
+
* Otherwise, returns the plain document name.
|
|
527
|
+
*/
|
|
528
|
+
get effectiveName(): string;
|
|
498
529
|
intervals: any;
|
|
499
530
|
constructor(configuration: HocuspocusProviderConfiguration);
|
|
500
531
|
boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hocuspocus/provider",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.1",
|
|
4
4
|
"description": "hocuspocus provider",
|
|
5
5
|
"homepage": "https://hocuspocus.dev",
|
|
6
6
|
"keywords": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@hocuspocus/common": "^
|
|
32
|
+
"@hocuspocus/common": "^4.0.0-rc.1",
|
|
33
33
|
"@lifeomic/attempt": "^3.0.2",
|
|
34
34
|
"lib0": "^0.2.87",
|
|
35
35
|
"ws": "^8.17.1"
|
|
@@ -38,8 +38,11 @@
|
|
|
38
38
|
"y-protocols": "^1.0.6",
|
|
39
39
|
"yjs": "^13.6.8"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "730ac02724fcc44bbbfcc5849df4b0cafb996450",
|
|
42
42
|
"repository": {
|
|
43
43
|
"url": "https://github.com/ueberdosis/hocuspocus"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { awarenessStatesToArray } from "@hocuspocus/common";
|
|
2
|
-
import type { Event, MessageEvent } from "ws";
|
|
1
|
+
import { awarenessStatesToArray, makeRoutingKey, parseRoutingKey } from "@hocuspocus/common";
|
|
3
2
|
import { Awareness, removeAwarenessStates } from "y-protocols/awareness";
|
|
4
3
|
import * as Y from "yjs";
|
|
5
4
|
import EventEmitter from "./EventEmitter.ts";
|
|
@@ -13,22 +12,22 @@ import { AwarenessMessage } from "./OutgoingMessages/AwarenessMessage.ts";
|
|
|
13
12
|
import { StatelessMessage } from "./OutgoingMessages/StatelessMessage.ts";
|
|
14
13
|
import { SyncStepOneMessage } from "./OutgoingMessages/SyncStepOneMessage.ts";
|
|
15
14
|
import { UpdateMessage } from "./OutgoingMessages/UpdateMessage.ts";
|
|
16
|
-
import
|
|
17
|
-
AuthorizedScope,
|
|
18
|
-
ConstructableOutgoingMessage,
|
|
19
|
-
onAuthenticatedParameters,
|
|
20
|
-
onAuthenticationFailedParameters,
|
|
21
|
-
onAwarenessChangeParameters,
|
|
22
|
-
onAwarenessUpdateParameters,
|
|
23
|
-
onCloseParameters,
|
|
24
|
-
onDisconnectParameters,
|
|
25
|
-
onMessageParameters,
|
|
26
|
-
onOpenParameters,
|
|
27
|
-
onOutgoingMessageParameters,
|
|
28
|
-
onStatelessParameters,
|
|
29
|
-
onStatusParameters,
|
|
30
|
-
onSyncedParameters,
|
|
31
|
-
onUnsyncedChangesParameters,
|
|
15
|
+
import {
|
|
16
|
+
type AuthorizedScope,
|
|
17
|
+
type ConstructableOutgoingMessage,
|
|
18
|
+
type onAuthenticatedParameters,
|
|
19
|
+
type onAuthenticationFailedParameters,
|
|
20
|
+
type onAwarenessChangeParameters,
|
|
21
|
+
type onAwarenessUpdateParameters,
|
|
22
|
+
type onCloseParameters,
|
|
23
|
+
type onDisconnectParameters,
|
|
24
|
+
type onMessageParameters,
|
|
25
|
+
type onOpenParameters,
|
|
26
|
+
type onOutgoingMessageParameters,
|
|
27
|
+
type onStatelessParameters,
|
|
28
|
+
type onStatusParameters,
|
|
29
|
+
type onSyncedParameters,
|
|
30
|
+
type onUnsyncedChangesParameters,
|
|
32
31
|
} from "./types.ts";
|
|
33
32
|
|
|
34
33
|
export type HocuspocusProviderConfiguration = Required<
|
|
@@ -78,6 +77,18 @@ export interface CompleteHocuspocusProviderConfiguration {
|
|
|
78
77
|
*/
|
|
79
78
|
websocketProvider: HocuspocusProviderWebsocket;
|
|
80
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Enable session-aware multiplexing. When true, the provider embeds a unique
|
|
82
|
+
* sessionId in the documentName field of every message, allowing multiple
|
|
83
|
+
* providers with the same document name on a single WebSocket connection.
|
|
84
|
+
*
|
|
85
|
+
* Only set this to `false` when connecting to a v3 server that does not
|
|
86
|
+
* support session awareness.
|
|
87
|
+
*
|
|
88
|
+
* Default: true
|
|
89
|
+
*/
|
|
90
|
+
sessionAwareness: boolean;
|
|
91
|
+
|
|
81
92
|
/**
|
|
82
93
|
* Force syncing the document in the defined interval.
|
|
83
94
|
*/
|
|
@@ -112,6 +123,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
112
123
|
// @ts-ignore
|
|
113
124
|
awareness: undefined,
|
|
114
125
|
token: null,
|
|
126
|
+
sessionAwareness: true,
|
|
115
127
|
forceSyncInterval: false,
|
|
116
128
|
onAuthenticated: () => null,
|
|
117
129
|
onAuthenticationFailed: () => null,
|
|
@@ -143,6 +155,23 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
143
155
|
|
|
144
156
|
private _isAttached = false;
|
|
145
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Unique session identifier for this provider instance.
|
|
160
|
+
* Used for multiplexing multiple providers with the same document name on a single WebSocket.
|
|
161
|
+
*/
|
|
162
|
+
sessionId: string = Math.random().toString(36).slice(2);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The effective name used as the first VarString in messages.
|
|
166
|
+
* When `sessionAwareness` is enabled, returns a composite key (documentName\0sessionId).
|
|
167
|
+
* Otherwise, returns the plain document name.
|
|
168
|
+
*/
|
|
169
|
+
get effectiveName(): string {
|
|
170
|
+
return this.configuration.sessionAwareness
|
|
171
|
+
? makeRoutingKey(this.configuration.name, this.sessionId)
|
|
172
|
+
: this.configuration.name;
|
|
173
|
+
}
|
|
174
|
+
|
|
146
175
|
intervals: any = {
|
|
147
176
|
forceSync: null,
|
|
148
177
|
};
|
|
@@ -280,7 +309,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
280
309
|
|
|
281
310
|
this.send(SyncStepOneMessage, {
|
|
282
311
|
document: this.document,
|
|
283
|
-
documentName: this.
|
|
312
|
+
documentName: this.effectiveName,
|
|
284
313
|
});
|
|
285
314
|
}
|
|
286
315
|
|
|
@@ -304,7 +333,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
304
333
|
|
|
305
334
|
sendStateless(payload: string) {
|
|
306
335
|
this.send(StatelessMessage, {
|
|
307
|
-
documentName: this.
|
|
336
|
+
documentName: this.effectiveName,
|
|
308
337
|
payload,
|
|
309
338
|
});
|
|
310
339
|
}
|
|
@@ -322,7 +351,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
322
351
|
|
|
323
352
|
this.send(AuthenticationMessage, {
|
|
324
353
|
token: token ?? "",
|
|
325
|
-
documentName: this.
|
|
354
|
+
documentName: this.effectiveName,
|
|
326
355
|
});
|
|
327
356
|
}
|
|
328
357
|
|
|
@@ -332,7 +361,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
332
361
|
}
|
|
333
362
|
|
|
334
363
|
this.incrementUnsyncedChanges();
|
|
335
|
-
this.send(UpdateMessage, { update, documentName: this.
|
|
364
|
+
this.send(UpdateMessage, { update, documentName: this.effectiveName });
|
|
336
365
|
}
|
|
337
366
|
|
|
338
367
|
awarenessUpdateHandler({ added, updated, removed }: any, origin: any) {
|
|
@@ -341,7 +370,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
341
370
|
this.send(AwarenessMessage, {
|
|
342
371
|
awareness: this.awareness,
|
|
343
372
|
clients: changedClients,
|
|
344
|
-
documentName: this.
|
|
373
|
+
documentName: this.effectiveName,
|
|
345
374
|
});
|
|
346
375
|
}
|
|
347
376
|
|
|
@@ -414,14 +443,14 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
414
443
|
|
|
415
444
|
this.send(SyncStepOneMessage, {
|
|
416
445
|
document: this.document,
|
|
417
|
-
documentName: this.
|
|
446
|
+
documentName: this.effectiveName,
|
|
418
447
|
});
|
|
419
448
|
|
|
420
449
|
if (this.awareness && this.awareness.getLocalState() !== null) {
|
|
421
450
|
this.send(AwarenessMessage, {
|
|
422
451
|
awareness: this.awareness,
|
|
423
452
|
clients: [this.document.clientID],
|
|
424
|
-
documentName: this.
|
|
453
|
+
documentName: this.effectiveName,
|
|
425
454
|
});
|
|
426
455
|
}
|
|
427
456
|
}
|
|
@@ -438,9 +467,11 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
438
467
|
onMessage(event: MessageEvent) {
|
|
439
468
|
const message = new IncomingMessage(event.data);
|
|
440
469
|
|
|
441
|
-
const
|
|
470
|
+
const rawKey = message.readVarString();
|
|
471
|
+
// Extract actual documentName from potentially composite routing key
|
|
472
|
+
const { documentName } = parseRoutingKey(rawKey);
|
|
442
473
|
|
|
443
|
-
message.writeVarString(
|
|
474
|
+
message.writeVarString(this.effectiveName);
|
|
444
475
|
|
|
445
476
|
this.emit("message", { event, message: new IncomingMessage(event.data) });
|
|
446
477
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { WsReadyStates } from "@hocuspocus/common";
|
|
2
2
|
import { retry } from "@lifeomic/attempt";
|
|
3
|
+
import { createDecoder, readVarString, readVarUint } from "lib0/decoding";
|
|
4
|
+
import * as encoding from "lib0/encoding";
|
|
3
5
|
import * as time from "lib0/time";
|
|
4
|
-
import type { Event, MessageEvent } from "ws";
|
|
5
6
|
import EventEmitter from "./EventEmitter.ts";
|
|
6
7
|
import type { HocuspocusProvider } from "./HocuspocusProvider.ts";
|
|
7
8
|
import { IncomingMessage } from "./IncomingMessage.ts";
|
|
8
9
|
import { CloseMessage } from "./OutgoingMessages/CloseMessage.ts";
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
+
MessageType,
|
|
11
12
|
type onAwarenessChangeParameters,
|
|
12
13
|
type onAwarenessUpdateParameters,
|
|
13
14
|
type onCloseParameters,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
type onOpenParameters,
|
|
17
18
|
type onOutgoingMessageParameters,
|
|
18
19
|
type onStatusParameters,
|
|
20
|
+
WebSocketStatus,
|
|
19
21
|
} from "./types.ts";
|
|
20
22
|
|
|
21
23
|
export type HocuspocusWebSocket = WebSocket & { identifier: string };
|
|
@@ -103,13 +105,18 @@ export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
108
|
+
private static readonly DEDUPLICATABLE_TYPES = new Set([
|
|
109
|
+
MessageType.Awareness,
|
|
110
|
+
MessageType.QueryAwareness,
|
|
111
|
+
]);
|
|
112
|
+
|
|
106
113
|
private messageQueue: any[] = [];
|
|
107
114
|
|
|
108
115
|
public configuration: CompleteHocuspocusProviderWebsocketConfiguration = {
|
|
109
116
|
url: "",
|
|
110
117
|
autoConnect: true,
|
|
111
118
|
preserveTrailingSlash: false,
|
|
112
|
-
// @ts-
|
|
119
|
+
// @ts-expect-error
|
|
113
120
|
document: undefined,
|
|
114
121
|
WebSocketPolyfill: undefined,
|
|
115
122
|
// TODO: this should depend on awareness.outdatedTime
|
|
@@ -209,7 +216,20 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
attach(provider: HocuspocusProvider) {
|
|
212
|
-
|
|
219
|
+
const key = provider.effectiveName;
|
|
220
|
+
const existing = this.configuration.providerMap.get(key);
|
|
221
|
+
|
|
222
|
+
if (existing && existing !== provider) {
|
|
223
|
+
// Allow replacing a provider that hasn't authenticated (e.g., after auth failure retry)
|
|
224
|
+
if (existing.isAuthenticated) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Cannot attach two providers with the same effective name "${key}". ` +
|
|
227
|
+
"Use sessionAwareness: true to multiplex providers with the same document name.",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.configuration.providerMap.set(key, provider);
|
|
213
233
|
|
|
214
234
|
if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {
|
|
215
235
|
this.connect();
|
|
@@ -224,11 +244,12 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
224
244
|
}
|
|
225
245
|
|
|
226
246
|
detach(provider: HocuspocusProvider) {
|
|
227
|
-
|
|
247
|
+
const key = provider.effectiveName;
|
|
248
|
+
if (this.configuration.providerMap.has(key)) {
|
|
228
249
|
provider.send(CloseMessage, {
|
|
229
|
-
documentName:
|
|
250
|
+
documentName: key,
|
|
230
251
|
});
|
|
231
|
-
this.configuration.providerMap.delete(
|
|
252
|
+
this.configuration.providerMap.delete(key);
|
|
232
253
|
}
|
|
233
254
|
}
|
|
234
255
|
|
|
@@ -373,10 +394,31 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
373
394
|
|
|
374
395
|
this.lastMessageReceived = time.getUnixTime();
|
|
375
396
|
|
|
376
|
-
const
|
|
377
|
-
|
|
397
|
+
const data = new Uint8Array(event.data as ArrayBuffer);
|
|
398
|
+
|
|
399
|
+
// Check for connection-level Ping message (no document name prefix)
|
|
400
|
+
// Ping messages are sent as just the message type byte (length 1)
|
|
401
|
+
// We check length to avoid confusing with regular messages that happen to have
|
|
402
|
+
// a document name length of 9 as the first byte
|
|
403
|
+
if (data.length === 1 && data[0] === MessageType.Ping) {
|
|
404
|
+
this.sendPong();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const message = new IncomingMessage(data);
|
|
409
|
+
const rawKey = message.peekVarString();
|
|
410
|
+
|
|
411
|
+
const provider = this.configuration.providerMap.get(rawKey);
|
|
412
|
+
provider?.onMessage(event);
|
|
413
|
+
}
|
|
378
414
|
|
|
379
|
-
|
|
415
|
+
/**
|
|
416
|
+
* Send application-level Pong response to server Ping
|
|
417
|
+
*/
|
|
418
|
+
private sendPong() {
|
|
419
|
+
const encoder = encoding.createEncoder();
|
|
420
|
+
encoding.writeVarUint(encoder, MessageType.Pong);
|
|
421
|
+
this.send(encoding.toUint8Array(encoder));
|
|
380
422
|
}
|
|
381
423
|
|
|
382
424
|
resolveConnectionAttempt() {
|
|
@@ -474,11 +516,45 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
474
516
|
}
|
|
475
517
|
}
|
|
476
518
|
|
|
519
|
+
private parseQueuedMessage(
|
|
520
|
+
message: Uint8Array,
|
|
521
|
+
): { documentName: string; messageType: number } | null {
|
|
522
|
+
try {
|
|
523
|
+
const decoder = createDecoder(message);
|
|
524
|
+
const documentName = readVarString(decoder);
|
|
525
|
+
const messageType = readVarUint(decoder);
|
|
526
|
+
return { documentName, messageType };
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private addToQueue(message: any) {
|
|
533
|
+
if (message instanceof Uint8Array) {
|
|
534
|
+
const parsed = this.parseQueuedMessage(message);
|
|
535
|
+
if (
|
|
536
|
+
parsed &&
|
|
537
|
+
HocuspocusProviderWebsocket.DEDUPLICATABLE_TYPES.has(parsed.messageType)
|
|
538
|
+
) {
|
|
539
|
+
this.messageQueue = this.messageQueue.filter((queued) => {
|
|
540
|
+
if (!(queued instanceof Uint8Array)) return true;
|
|
541
|
+
const queuedParsed = this.parseQueuedMessage(queued);
|
|
542
|
+
if (!queuedParsed) return true;
|
|
543
|
+
return !(
|
|
544
|
+
queuedParsed.documentName === parsed.documentName &&
|
|
545
|
+
queuedParsed.messageType === parsed.messageType
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
this.messageQueue.push(message);
|
|
551
|
+
}
|
|
552
|
+
|
|
477
553
|
send(message: any) {
|
|
478
554
|
if (this.webSocket?.readyState === WsReadyStates.Open) {
|
|
479
555
|
this.webSocket.send(message);
|
|
480
556
|
} else {
|
|
481
|
-
this.
|
|
557
|
+
this.addToQueue(message);
|
|
482
558
|
}
|
|
483
559
|
}
|
|
484
560
|
|
package/src/MessageReceiver.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { readAuthMessage } from "@hocuspocus/common";
|
|
1
|
+
import { type CloseEvent, readAuthMessage } from "@hocuspocus/common";
|
|
2
2
|
import { readVarInt, readVarString } from "lib0/decoding";
|
|
3
|
-
import type { CloseEvent } from "ws";
|
|
4
3
|
import * as awarenessProtocol from "y-protocols/awareness";
|
|
5
4
|
import { messageYjsSyncStep2, readSyncMessage } from "y-protocols/sync";
|
|
6
5
|
import type { HocuspocusProvider } from "./HocuspocusProvider.ts";
|
|
@@ -54,9 +53,6 @@ export class MessageReceiver {
|
|
|
54
53
|
const event: CloseEvent = {
|
|
55
54
|
code: 1000,
|
|
56
55
|
reason: readVarString(message.decoder),
|
|
57
|
-
// @ts-ignore
|
|
58
|
-
target: provider.configuration.websocketProvider.webSocket!,
|
|
59
|
-
type: "close",
|
|
60
56
|
};
|
|
61
57
|
provider.onClose();
|
|
62
58
|
provider.configuration.onClose({ event });
|
|
@@ -64,7 +60,7 @@ export class MessageReceiver {
|
|
|
64
60
|
break;
|
|
65
61
|
|
|
66
62
|
default:
|
|
67
|
-
|
|
63
|
+
console.error(`Can’t apply message of unknown type: ${type}`);
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
// Reply
|
|
@@ -3,6 +3,7 @@ import { writeAuthentication } from "@hocuspocus/common";
|
|
|
3
3
|
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
4
|
import { MessageType } from "../types.ts";
|
|
5
5
|
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
import { version } from "../version.ts";
|
|
6
7
|
|
|
7
8
|
export class AuthenticationMessage extends OutgoingMessage {
|
|
8
9
|
type = MessageType.Auth;
|
|
@@ -19,6 +20,7 @@ export class AuthenticationMessage extends OutgoingMessage {
|
|
|
19
20
|
writeVarString(this.encoder, args.documentName!);
|
|
20
21
|
writeVarUint(this.encoder, this.type);
|
|
21
22
|
writeAuthentication(this.encoder, args.token);
|
|
23
|
+
writeVarString(this.encoder, version);
|
|
22
24
|
|
|
23
25
|
return this.encoder;
|
|
24
26
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Encoder } from "lib0/encoding";
|
|
2
|
-
import type { Event, MessageEvent } from "ws";
|
|
3
2
|
import type { Awareness } from "y-protocols/awareness";
|
|
4
3
|
import type * as Y from "yjs";
|
|
5
4
|
import type { CloseEvent } from "@hocuspocus/common";
|
|
@@ -20,6 +19,8 @@ export enum MessageType {
|
|
|
20
19
|
Stateless = 5,
|
|
21
20
|
CLOSE = 7,
|
|
22
21
|
SyncStatus = 8,
|
|
22
|
+
Ping = 9,
|
|
23
|
+
Pong = 10,
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export enum WebSocketStatus {
|