@hocuspocus/provider 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hocuspocus-provider.cjs +797 -689
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +799 -691
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +1 -1
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +23 -8
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +2 -1
- package/dist/packages/provider/src/MessageReceiver.d.ts +2 -1
- package/dist/packages/provider/src/TiptapCollabProvider.d.ts +15 -0
- package/dist/packages/provider/src/types.d.ts +6 -5
- package/dist/packages/server/src/Hocuspocus.d.ts +3 -2
- package/dist/packages/server/src/MessageReceiver.d.ts +1 -1
- package/dist/packages/server/src/OutgoingMessage.d.ts +1 -0
- package/dist/packages/server/src/types.d.ts +30 -6
- package/dist/tests/provider/hasUnsyncedChanges.d.ts +1 -0
- package/dist/tests/server/afterUnloadDocument.d.ts +1 -0
- package/package.json +3 -3
- package/src/HocuspocusProvider.ts +60 -31
- package/src/HocuspocusProviderWebsocket.ts +23 -7
- package/src/MessageReceiver.ts +12 -9
- package/src/TiptapCollabProvider.ts +50 -0
- package/src/types.ts +5 -4
|
@@ -69,7 +69,7 @@ export declare class Redis implements Extension {
|
|
|
69
69
|
private subKey;
|
|
70
70
|
private lockKey;
|
|
71
71
|
/**
|
|
72
|
-
* Once a document is
|
|
72
|
+
* Once a document is loaded, subscribe to the channel in Redis.
|
|
73
73
|
*/
|
|
74
74
|
afterLoadDocument({ documentName, document }: afterLoadDocumentPayload): Promise<unknown>;
|
|
75
75
|
/**
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import * as Y from 'yjs';
|
|
2
|
-
import { Awareness } from 'y-protocols/awareness';
|
|
3
1
|
import * as mutex from 'lib0/mutex';
|
|
4
2
|
import type { CloseEvent, Event, MessageEvent } from 'ws';
|
|
3
|
+
import { Awareness } from 'y-protocols/awareness';
|
|
4
|
+
import * as Y from 'yjs';
|
|
5
5
|
import EventEmitter from './EventEmitter.js';
|
|
6
|
-
import { ConstructableOutgoingMessage, onAuthenticationFailedParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatelessParameters, onStatusParameters, onSyncedParameters, WebSocketStatus, onAwarenessChangeParameters, onAwarenessUpdateParameters } from './types.js';
|
|
7
6
|
import { CompleteHocuspocusProviderWebsocketConfiguration, HocuspocusProviderWebsocket } from './HocuspocusProviderWebsocket.js';
|
|
7
|
+
import { ConstructableOutgoingMessage, WebSocketStatus, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatelessParameters, onStatusParameters, onSyncedParameters } from './types.js';
|
|
8
8
|
export type HocuspocusProviderConfiguration = Required<Pick<CompleteHocuspocusProviderConfiguration, 'name'>> & Partial<CompleteHocuspocusProviderConfiguration> & (Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>> | Required<Pick<CompleteHocuspocusProviderConfiguration, 'websocketProvider'>>);
|
|
9
9
|
export interface CompleteHocuspocusProviderConfiguration {
|
|
10
10
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
* The identifier/name of your document
|
|
12
|
+
*/
|
|
13
13
|
name: string;
|
|
14
14
|
/**
|
|
15
15
|
* The actual Y.js document
|
|
@@ -59,6 +59,14 @@ export interface CompleteHocuspocusProviderConfiguration {
|
|
|
59
59
|
* Don’t output any warnings.
|
|
60
60
|
*/
|
|
61
61
|
quiet: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Pass `false` to start the connection manually.
|
|
64
|
+
*/
|
|
65
|
+
connect: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Pass `false` to close the connection manually.
|
|
68
|
+
*/
|
|
69
|
+
preserveConnection: boolean;
|
|
62
70
|
}
|
|
63
71
|
export declare class HocuspocusProvider extends EventEmitter {
|
|
64
72
|
configuration: CompleteHocuspocusProviderConfiguration;
|
|
@@ -73,7 +81,7 @@ export declare class HocuspocusProvider extends EventEmitter {
|
|
|
73
81
|
isConnected: boolean;
|
|
74
82
|
constructor(configuration: HocuspocusProviderConfiguration);
|
|
75
83
|
boundBroadcastChannelSubscriber: (data: ArrayBuffer) => void;
|
|
76
|
-
|
|
84
|
+
boundPageUnload: () => void;
|
|
77
85
|
boundOnOpen: (event: Event) => Promise<void>;
|
|
78
86
|
boundOnMessage: (event: MessageEvent) => void;
|
|
79
87
|
boundOnClose: (event: CloseEvent) => void;
|
|
@@ -88,13 +96,20 @@ export declare class HocuspocusProvider extends EventEmitter {
|
|
|
88
96
|
get document(): Y.Doc;
|
|
89
97
|
get awareness(): Awareness;
|
|
90
98
|
get hasUnsyncedChanges(): boolean;
|
|
91
|
-
|
|
99
|
+
incrementUnsyncedChanges(): void;
|
|
100
|
+
decrementUnsyncedChanges(): void;
|
|
92
101
|
forceSync(): void;
|
|
93
|
-
|
|
102
|
+
pageUnload(): void;
|
|
94
103
|
registerEventListeners(): void;
|
|
95
104
|
sendStateless(payload: string): void;
|
|
96
105
|
documentUpdateHandler(update: Uint8Array, origin: any): void;
|
|
97
106
|
awarenessUpdateHandler({ added, updated, removed }: any, origin: any): void;
|
|
107
|
+
/**
|
|
108
|
+
* Indicates whether a first handshake with the server has been established
|
|
109
|
+
*
|
|
110
|
+
* Note: this does not mean all updates from the client have been persisted to the backend. For this,
|
|
111
|
+
* use `hasUnsyncedChanges`.
|
|
112
|
+
*/
|
|
98
113
|
get synced(): boolean;
|
|
99
114
|
set synced(state: boolean);
|
|
100
115
|
receiveStateless(payload: string): void;
|
|
@@ -2,8 +2,8 @@ import * as mutex from 'lib0/mutex';
|
|
|
2
2
|
import type { MessageEvent } from 'ws';
|
|
3
3
|
import { Event } from 'ws';
|
|
4
4
|
import EventEmitter from './EventEmitter.js';
|
|
5
|
-
import { onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters, WebSocketStatus, onAwarenessChangeParameters, onAwarenessUpdateParameters } from './types.js';
|
|
6
5
|
import { HocuspocusProvider } from './HocuspocusProvider.js';
|
|
6
|
+
import { WebSocketStatus, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters } from './types.js';
|
|
7
7
|
export type HocuspocusProviderWebsocketConfiguration = Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>> & Partial<CompleteHocuspocusProviderWebsocketConfiguration>;
|
|
8
8
|
export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
9
9
|
/**
|
|
@@ -76,6 +76,7 @@ export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
|
76
76
|
quiet: boolean;
|
|
77
77
|
}
|
|
78
78
|
export declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
79
|
+
private messageQueue;
|
|
79
80
|
configuration: CompleteHocuspocusProviderWebsocketConfiguration;
|
|
80
81
|
subscribedToBroadcastChannel: boolean;
|
|
81
82
|
webSocket: WebSocket | null;
|
|
@@ -5,8 +5,9 @@ export declare class MessageReceiver {
|
|
|
5
5
|
broadcasted: boolean;
|
|
6
6
|
constructor(message: IncomingMessage);
|
|
7
7
|
setBroadcasted(value: boolean): this;
|
|
8
|
-
apply(provider: HocuspocusProvider, emitSynced
|
|
8
|
+
apply(provider: HocuspocusProvider, emitSynced: boolean): void;
|
|
9
9
|
private applySyncMessage;
|
|
10
|
+
applySyncStatusMessage(provider: HocuspocusProvider, applied: boolean): void;
|
|
10
11
|
private applyAwarenessMessage;
|
|
11
12
|
private applyAuthMessage;
|
|
12
13
|
private applyQueryAwarenessMessage;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AbstractType, YArrayEvent } from 'yjs';
|
|
1
2
|
import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider.js';
|
|
2
3
|
import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js';
|
|
3
4
|
export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>);
|
|
@@ -8,6 +9,20 @@ export interface AdditionalTiptapCollabProviderConfiguration {
|
|
|
8
9
|
appId?: string;
|
|
9
10
|
websocketProvider?: TiptapCollabProviderWebsocket;
|
|
10
11
|
}
|
|
12
|
+
export type AuditHistoryVersion = {
|
|
13
|
+
name?: string;
|
|
14
|
+
version: number;
|
|
15
|
+
date: number;
|
|
16
|
+
};
|
|
11
17
|
export declare class TiptapCollabProvider extends HocuspocusProvider {
|
|
18
|
+
tiptapCollabConfigurationPrefix: string;
|
|
12
19
|
constructor(configuration: TiptapCollabProviderConfiguration);
|
|
20
|
+
createVersion(name?: string): void;
|
|
21
|
+
revertToVersion(targetVersion: number): void;
|
|
22
|
+
getVersions(): AuditHistoryVersion[];
|
|
23
|
+
watchVersions(callback: Parameters<AbstractType<YArrayEvent<AuditHistoryVersion>>['observe']>[0]): void;
|
|
24
|
+
unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<AuditHistoryVersion>>['unobserve']>[0]): void;
|
|
25
|
+
isAutoVersioning(): boolean;
|
|
26
|
+
enableAutoVersioning(): 1;
|
|
27
|
+
disableAutoVersioning(): 0;
|
|
13
28
|
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
import { Encoder } from 'lib0/encoding';
|
|
2
|
+
import type { CloseEvent, Event, MessageEvent } from 'ws';
|
|
1
3
|
import { Awareness } from 'y-protocols/awareness';
|
|
2
4
|
import * as Y from 'yjs';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
+
import { IncomingMessage } from './IncomingMessage.js';
|
|
6
|
+
import { OutgoingMessage } from './OutgoingMessage.js';
|
|
5
7
|
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage.js';
|
|
6
8
|
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage.js';
|
|
7
9
|
import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage.js';
|
|
8
10
|
import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage.js';
|
|
9
11
|
import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage.js';
|
|
10
12
|
import { UpdateMessage } from './OutgoingMessages/UpdateMessage.js';
|
|
11
|
-
import { IncomingMessage } from './IncomingMessage.js';
|
|
12
|
-
import { OutgoingMessage } from './OutgoingMessage.js';
|
|
13
13
|
export declare enum MessageType {
|
|
14
14
|
Sync = 0,
|
|
15
15
|
Awareness = 1,
|
|
16
16
|
Auth = 2,
|
|
17
17
|
QueryAwareness = 3,
|
|
18
18
|
Stateless = 5,
|
|
19
|
-
CLOSE = 7
|
|
19
|
+
CLOSE = 7,
|
|
20
|
+
SyncStatus = 8
|
|
20
21
|
}
|
|
21
22
|
export declare enum WebSocketStatus {
|
|
22
23
|
Connecting = "connecting",
|
|
@@ -5,7 +5,7 @@ import WebSocket, { AddressInfo, WebSocketServer } from 'ws';
|
|
|
5
5
|
import { Debugger } from './Debugger.js';
|
|
6
6
|
import { DirectConnection } from './DirectConnection.js';
|
|
7
7
|
import Document from './Document.js';
|
|
8
|
-
import { Configuration, ConnectionConfiguration, HookName,
|
|
8
|
+
import { Configuration, ConnectionConfiguration, HookName, HookPayloadByName, onListenPayload, onStoreDocumentPayload } from './types.js';
|
|
9
9
|
export declare const defaultConfiguration: {
|
|
10
10
|
name: null;
|
|
11
11
|
port: number;
|
|
@@ -91,7 +91,8 @@ export declare class Hocuspocus {
|
|
|
91
91
|
* Run the given hook on all configured extensions.
|
|
92
92
|
* Runs the given callback after each hook.
|
|
93
93
|
*/
|
|
94
|
-
hooks(name:
|
|
94
|
+
hooks<T extends HookName>(name: T, payload: HookPayloadByName[T], callback?: Function | null): Promise<any>;
|
|
95
|
+
unloadDocument(document: Document): void;
|
|
95
96
|
enableDebugging(): void;
|
|
96
97
|
enableMessageLogging(): void;
|
|
97
98
|
disableLogging(): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Connection from './Connection.js';
|
|
2
|
-
import { IncomingMessage } from './IncomingMessage.js';
|
|
3
2
|
import { Debugger } from './Debugger.js';
|
|
4
3
|
import Document from './Document.js';
|
|
4
|
+
import { IncomingMessage } from './IncomingMessage.js';
|
|
5
5
|
export declare class MessageReceiver {
|
|
6
6
|
message: IncomingMessage;
|
|
7
7
|
logger: Debugger;
|
|
@@ -16,5 +16,6 @@ export declare class OutgoingMessage {
|
|
|
16
16
|
writeUpdate(update: Uint8Array): OutgoingMessage;
|
|
17
17
|
writeStateless(payload: string): OutgoingMessage;
|
|
18
18
|
writeBroadcastStateless(payload: string): OutgoingMessage;
|
|
19
|
+
writeSyncStatus(updateSaved: boolean): OutgoingMessage;
|
|
19
20
|
toUint8Array(): Uint8Array;
|
|
20
21
|
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'http';
|
|
5
5
|
import { URLSearchParams } from 'url';
|
|
6
6
|
import { Awareness } from 'y-protocols/awareness';
|
|
7
|
+
import Connection from './Connection.js';
|
|
7
8
|
import Document from './Document.js';
|
|
8
9
|
import { Hocuspocus } from './Hocuspocus.js';
|
|
9
|
-
import Connection from './Connection.js';
|
|
10
10
|
export declare enum MessageType {
|
|
11
11
|
Unknown = -1,
|
|
12
12
|
Sync = 0,
|
|
@@ -16,7 +16,8 @@ export declare enum MessageType {
|
|
|
16
16
|
SyncReply = 4,
|
|
17
17
|
Stateless = 5,
|
|
18
18
|
BroadcastStateless = 6,
|
|
19
|
-
CLOSE = 7
|
|
19
|
+
CLOSE = 7,
|
|
20
|
+
SyncStatus = 8
|
|
20
21
|
}
|
|
21
22
|
export interface AwarenessUpdate {
|
|
22
23
|
added: Array<any>;
|
|
@@ -47,10 +48,31 @@ export interface Extension {
|
|
|
47
48
|
onAwarenessUpdate?(data: onAwarenessUpdatePayload): Promise<any>;
|
|
48
49
|
onRequest?(data: onRequestPayload): Promise<any>;
|
|
49
50
|
onDisconnect?(data: onDisconnectPayload): Promise<any>;
|
|
51
|
+
afterUnloadDocument?(data: onLoadDocumentPayload): Promise<any>;
|
|
50
52
|
onDestroy?(data: onDestroyPayload): Promise<any>;
|
|
51
53
|
}
|
|
52
|
-
export type HookName = 'onConfigure' | 'onListen' | 'onUpgrade' | 'onConnect' | 'connected' | 'onAuthenticate' | 'onLoadDocument' | 'afterLoadDocument' | 'beforeHandleMessage' | 'beforeBroadcastStateless' | 'onStateless' | 'onChange' | 'onStoreDocument' | 'afterStoreDocument' | 'onAwarenessUpdate' | 'onRequest' | 'onDisconnect' | 'onDestroy';
|
|
53
|
-
export type
|
|
54
|
+
export type HookName = 'onConfigure' | 'onListen' | 'onUpgrade' | 'onConnect' | 'connected' | 'onAuthenticate' | 'onLoadDocument' | 'afterLoadDocument' | 'beforeHandleMessage' | 'beforeBroadcastStateless' | 'onStateless' | 'onChange' | 'onStoreDocument' | 'afterStoreDocument' | 'onAwarenessUpdate' | 'onRequest' | 'onDisconnect' | 'afterUnloadDocument' | 'onDestroy';
|
|
55
|
+
export type HookPayloadByName = {
|
|
56
|
+
onConfigure: onConfigurePayload;
|
|
57
|
+
onListen: onListenPayload;
|
|
58
|
+
onUpgrade: onUpgradePayload;
|
|
59
|
+
onConnect: onConnectPayload;
|
|
60
|
+
connected: connectedPayload;
|
|
61
|
+
onAuthenticate: onAuthenticatePayload;
|
|
62
|
+
onLoadDocument: onLoadDocumentPayload;
|
|
63
|
+
afterLoadDocument: onLoadDocumentPayload;
|
|
64
|
+
beforeHandleMessage: beforeHandleMessagePayload;
|
|
65
|
+
beforeBroadcastStateless: beforeBroadcastStatelessPayload;
|
|
66
|
+
onStateless: onStatelessPayload;
|
|
67
|
+
onChange: onChangePayload;
|
|
68
|
+
onStoreDocument: onStoreDocumentPayload;
|
|
69
|
+
afterStoreDocument: afterStoreDocumentPayload;
|
|
70
|
+
onAwarenessUpdate: onAwarenessUpdatePayload;
|
|
71
|
+
onRequest: onRequestPayload;
|
|
72
|
+
onDisconnect: onDisconnectPayload;
|
|
73
|
+
afterUnloadDocument: afterUnloadDocumentPayload;
|
|
74
|
+
onDestroy: onDestroyPayload;
|
|
75
|
+
};
|
|
54
76
|
export interface Configuration extends Extension {
|
|
55
77
|
/**
|
|
56
78
|
* A name for the instance, used for logging.
|
|
@@ -198,14 +220,12 @@ export interface onStoreDocumentPayload {
|
|
|
198
220
|
export interface afterStoreDocumentPayload extends onStoreDocumentPayload {
|
|
199
221
|
}
|
|
200
222
|
export interface onAwarenessUpdatePayload {
|
|
201
|
-
clientsCount: number;
|
|
202
223
|
context: any;
|
|
203
224
|
document: Document;
|
|
204
225
|
documentName: string;
|
|
205
226
|
instance: Hocuspocus;
|
|
206
227
|
requestHeaders: IncomingHttpHeaders;
|
|
207
228
|
requestParameters: URLSearchParams;
|
|
208
|
-
update: Uint8Array;
|
|
209
229
|
socketId: string;
|
|
210
230
|
added: number[];
|
|
211
231
|
updated: number[];
|
|
@@ -264,6 +284,10 @@ export interface onConfigurePayload {
|
|
|
264
284
|
configuration: Configuration;
|
|
265
285
|
version: string;
|
|
266
286
|
}
|
|
287
|
+
export interface afterUnloadDocumentPayload {
|
|
288
|
+
instance: Hocuspocus;
|
|
289
|
+
documentName: string;
|
|
290
|
+
}
|
|
267
291
|
export interface DirectConnection {
|
|
268
292
|
transact(transaction: (document: Document) => void): Promise<void>;
|
|
269
293
|
disconnect(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hocuspocus/provider",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "hocuspocus provider",
|
|
5
5
|
"homepage": "https://hocuspocus.dev",
|
|
6
6
|
"keywords": [
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@hocuspocus/common": "^2.
|
|
32
|
+
"@hocuspocus/common": "^2.3.0",
|
|
33
33
|
"@lifeomic/attempt": "^3.0.2",
|
|
34
34
|
"lib0": "^0.2.47",
|
|
35
35
|
"ws": "^7.5.9"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"y-protocols": "^1.0.5",
|
|
39
|
-
"yjs": "^13.
|
|
39
|
+
"yjs": "^13.6.4"
|
|
40
40
|
},
|
|
41
41
|
"gitHead": "cd788b6a315f608ef531524409abdce1e6790726"
|
|
42
42
|
}
|
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { awarenessStatesToArray } from '@hocuspocus/common'
|
|
2
2
|
import * as bc from 'lib0/broadcastchannel'
|
|
3
|
-
import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
|
|
4
3
|
import * as mutex from 'lib0/mutex'
|
|
5
4
|
import type { CloseEvent, Event, MessageEvent } from 'ws'
|
|
6
|
-
import {
|
|
5
|
+
import { Awareness, removeAwarenessStates } from 'y-protocols/awareness'
|
|
6
|
+
import * as Y from 'yjs'
|
|
7
7
|
import EventEmitter from './EventEmitter.js'
|
|
8
|
+
import {
|
|
9
|
+
CompleteHocuspocusProviderWebsocketConfiguration,
|
|
10
|
+
HocuspocusProviderWebsocket,
|
|
11
|
+
} from './HocuspocusProviderWebsocket.js'
|
|
8
12
|
import { IncomingMessage } from './IncomingMessage.js'
|
|
9
13
|
import { MessageReceiver } from './MessageReceiver.js'
|
|
10
14
|
import { MessageSender } from './MessageSender.js'
|
|
11
|
-
import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage.js'
|
|
12
|
-
import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage.js'
|
|
13
|
-
import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage.js'
|
|
14
15
|
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage.js'
|
|
15
16
|
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage.js'
|
|
17
|
+
import { CloseMessage } from './OutgoingMessages/CloseMessage.js'
|
|
18
|
+
import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage.js'
|
|
19
|
+
import { StatelessMessage } from './OutgoingMessages/StatelessMessage.js'
|
|
20
|
+
import { SyncStepOneMessage } from './OutgoingMessages/SyncStepOneMessage.js'
|
|
21
|
+
import { SyncStepTwoMessage } from './OutgoingMessages/SyncStepTwoMessage.js'
|
|
16
22
|
import { UpdateMessage } from './OutgoingMessages/UpdateMessage.js'
|
|
17
23
|
import {
|
|
18
24
|
ConstructableOutgoingMessage,
|
|
25
|
+
WebSocketStatus,
|
|
19
26
|
onAuthenticationFailedParameters,
|
|
27
|
+
onAwarenessChangeParameters,
|
|
28
|
+
onAwarenessUpdateParameters,
|
|
20
29
|
onCloseParameters,
|
|
21
30
|
onDisconnectParameters,
|
|
22
31
|
onMessageParameters,
|
|
@@ -24,16 +33,7 @@ import {
|
|
|
24
33
|
onOutgoingMessageParameters, onStatelessParameters,
|
|
25
34
|
onStatusParameters,
|
|
26
35
|
onSyncedParameters,
|
|
27
|
-
WebSocketStatus,
|
|
28
|
-
onAwarenessChangeParameters,
|
|
29
|
-
onAwarenessUpdateParameters,
|
|
30
36
|
} from './types.js'
|
|
31
|
-
import {
|
|
32
|
-
CompleteHocuspocusProviderWebsocketConfiguration,
|
|
33
|
-
HocuspocusProviderWebsocket,
|
|
34
|
-
} from './HocuspocusProviderWebsocket.js'
|
|
35
|
-
import { StatelessMessage } from './OutgoingMessages/StatelessMessage.js'
|
|
36
|
-
import { CloseMessage } from './OutgoingMessages/CloseMessage.js'
|
|
37
37
|
|
|
38
38
|
export type HocuspocusProviderConfiguration =
|
|
39
39
|
Required<Pick<CompleteHocuspocusProviderConfiguration, 'name'>>
|
|
@@ -43,9 +43,9 @@ export type HocuspocusProviderConfiguration =
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
export interface CompleteHocuspocusProviderConfiguration {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
/**
|
|
47
|
+
* The identifier/name of your document
|
|
48
|
+
*/
|
|
49
49
|
name: string,
|
|
50
50
|
/**
|
|
51
51
|
* The actual Y.js document
|
|
@@ -96,6 +96,16 @@ export interface CompleteHocuspocusProviderConfiguration {
|
|
|
96
96
|
* Don’t output any warnings.
|
|
97
97
|
*/
|
|
98
98
|
quiet: boolean,
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Pass `false` to start the connection manually.
|
|
102
|
+
*/
|
|
103
|
+
connect: boolean,
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pass `false` to close the connection manually.
|
|
107
|
+
*/
|
|
108
|
+
preserveConnection: boolean,
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
export class HocuspocusProvider extends EventEmitter {
|
|
@@ -124,6 +134,8 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
124
134
|
onAwarenessChange: () => null,
|
|
125
135
|
onStateless: () => null,
|
|
126
136
|
quiet: false,
|
|
137
|
+
connect: true,
|
|
138
|
+
preserveConnection: true,
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
subscribedToBroadcastChannel = false
|
|
@@ -209,7 +221,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
209
221
|
|
|
210
222
|
boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
|
|
211
223
|
|
|
212
|
-
|
|
224
|
+
boundPageUnload = this.pageUnload.bind(this)
|
|
213
225
|
|
|
214
226
|
boundOnOpen = this.onOpen.bind(this)
|
|
215
227
|
|
|
@@ -242,6 +254,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
242
254
|
|
|
243
255
|
this.configuration.websocketProvider = new HocuspocusProviderWebsocket({
|
|
244
256
|
url: websocketProviderConfig.url,
|
|
257
|
+
connect: websocketProviderConfig.connect,
|
|
245
258
|
parameters: websocketProviderConfig.parameters,
|
|
246
259
|
})
|
|
247
260
|
}
|
|
@@ -261,8 +274,16 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
261
274
|
return this.unsyncedChanges > 0
|
|
262
275
|
}
|
|
263
276
|
|
|
264
|
-
|
|
265
|
-
this.unsyncedChanges +=
|
|
277
|
+
incrementUnsyncedChanges() {
|
|
278
|
+
this.unsyncedChanges += 1
|
|
279
|
+
this.emit('unsyncedChanges', this.unsyncedChanges)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
decrementUnsyncedChanges() {
|
|
283
|
+
this.unsyncedChanges -= 1
|
|
284
|
+
if (this.unsyncedChanges === 0) {
|
|
285
|
+
this.synced = true
|
|
286
|
+
}
|
|
266
287
|
this.emit('unsyncedChanges', this.unsyncedChanges)
|
|
267
288
|
}
|
|
268
289
|
|
|
@@ -270,7 +291,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
270
291
|
this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
|
|
271
292
|
}
|
|
272
293
|
|
|
273
|
-
|
|
294
|
+
pageUnload() {
|
|
274
295
|
removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload')
|
|
275
296
|
}
|
|
276
297
|
|
|
@@ -279,7 +300,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
279
300
|
return
|
|
280
301
|
}
|
|
281
302
|
|
|
282
|
-
window.addEventListener('
|
|
303
|
+
window.addEventListener('unload', this.boundPageUnload)
|
|
283
304
|
}
|
|
284
305
|
|
|
285
306
|
sendStateless(payload: string) {
|
|
@@ -291,7 +312,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
291
312
|
return
|
|
292
313
|
}
|
|
293
314
|
|
|
294
|
-
this.
|
|
315
|
+
this.incrementUnsyncedChanges()
|
|
295
316
|
this.send(UpdateMessage, { update, documentName: this.configuration.name }, true)
|
|
296
317
|
}
|
|
297
318
|
|
|
@@ -305,6 +326,12 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
305
326
|
}, true)
|
|
306
327
|
}
|
|
307
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Indicates whether a first handshake with the server has been established
|
|
331
|
+
*
|
|
332
|
+
* Note: this does not mean all updates from the client have been persisted to the backend. For this,
|
|
333
|
+
* use `hasUnsyncedChanges`.
|
|
334
|
+
*/
|
|
308
335
|
get synced(): boolean {
|
|
309
336
|
return this.isSynced
|
|
310
337
|
}
|
|
@@ -314,10 +341,6 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
314
341
|
return
|
|
315
342
|
}
|
|
316
343
|
|
|
317
|
-
if (state && this.unsyncedChanges > 0) {
|
|
318
|
-
this.updateUnsyncedChanges(-1 * this.unsyncedChanges)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
344
|
this.isSynced = state
|
|
322
345
|
this.emit('synced', { state })
|
|
323
346
|
this.emit('sync', { state })
|
|
@@ -339,6 +362,9 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
339
362
|
disconnect() {
|
|
340
363
|
this.disconnectBroadcastChannel()
|
|
341
364
|
this.configuration.websocketProvider.detach(this)
|
|
365
|
+
if (!this.configuration.preserveConnection) {
|
|
366
|
+
this.configuration.websocketProvider.disconnect()
|
|
367
|
+
}
|
|
342
368
|
}
|
|
343
369
|
|
|
344
370
|
async onOpen(event: Event) {
|
|
@@ -366,6 +392,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
366
392
|
}
|
|
367
393
|
|
|
368
394
|
startSync() {
|
|
395
|
+
this.incrementUnsyncedChanges()
|
|
369
396
|
this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name })
|
|
370
397
|
|
|
371
398
|
if (this.awareness.getLocalState() !== null) {
|
|
@@ -378,7 +405,9 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
378
405
|
}
|
|
379
406
|
|
|
380
407
|
send(message: ConstructableOutgoingMessage, args: any, broadcast = false) {
|
|
381
|
-
if (!this.isConnected)
|
|
408
|
+
if (!this.isConnected) {
|
|
409
|
+
return
|
|
410
|
+
}
|
|
382
411
|
|
|
383
412
|
if (broadcast) {
|
|
384
413
|
this.mux(() => { this.broadcast(message, args) })
|
|
@@ -403,7 +432,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
403
432
|
|
|
404
433
|
this.emit('message', { event, message: new IncomingMessage(event.data) })
|
|
405
434
|
|
|
406
|
-
new MessageReceiver(message).apply(this)
|
|
435
|
+
new MessageReceiver(message).apply(this, true)
|
|
407
436
|
}
|
|
408
437
|
|
|
409
438
|
onClose(event: CloseEvent) {
|
|
@@ -455,7 +484,7 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
455
484
|
return
|
|
456
485
|
}
|
|
457
486
|
|
|
458
|
-
window.removeEventListener('
|
|
487
|
+
window.removeEventListener('unload', this.boundPageUnload)
|
|
459
488
|
}
|
|
460
489
|
|
|
461
490
|
permissionDeniedHandler(reason: string) {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import * as time from 'lib0/time'
|
|
2
|
-
import * as mutex from 'lib0/mutex'
|
|
3
|
-
import * as url from 'lib0/url'
|
|
4
|
-
import type { MessageEvent } from 'ws'
|
|
5
|
-
import { retry } from '@lifeomic/attempt'
|
|
6
1
|
import {
|
|
7
2
|
Forbidden, MessageTooBig, Unauthorized, WsReadyStates,
|
|
8
3
|
} from '@hocuspocus/common'
|
|
4
|
+
import { retry } from '@lifeomic/attempt'
|
|
5
|
+
import * as mutex from 'lib0/mutex'
|
|
6
|
+
import * as time from 'lib0/time'
|
|
7
|
+
import * as url from 'lib0/url'
|
|
8
|
+
import type { MessageEvent } from 'ws'
|
|
9
9
|
import { Event } from 'ws'
|
|
10
10
|
import EventEmitter from './EventEmitter.js'
|
|
11
|
+
import { HocuspocusProvider } from './HocuspocusProvider.js'
|
|
11
12
|
import {
|
|
12
|
-
|
|
13
|
+
WebSocketStatus,
|
|
13
14
|
onAwarenessChangeParameters, onAwarenessUpdateParameters,
|
|
15
|
+
onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters,
|
|
14
16
|
} from './types.js'
|
|
15
|
-
import { HocuspocusProvider } from './HocuspocusProvider.js'
|
|
16
17
|
|
|
17
18
|
export type HocuspocusProviderWebsocketConfiguration =
|
|
18
19
|
Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>>
|
|
@@ -91,6 +92,8 @@ export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
95
|
+
private messageQueue: any[] = []
|
|
96
|
+
|
|
94
97
|
public configuration: CompleteHocuspocusProviderWebsocketConfiguration = {
|
|
95
98
|
url: '',
|
|
96
99
|
// @ts-ignore
|
|
@@ -208,6 +211,10 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
attach(provider: HocuspocusProvider) {
|
|
214
|
+
if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {
|
|
215
|
+
this.connect()
|
|
216
|
+
}
|
|
217
|
+
|
|
211
218
|
if (this.receivedOnOpenPayload) {
|
|
212
219
|
provider.onOpen(this.receivedOnOpenPayload)
|
|
213
220
|
}
|
|
@@ -241,6 +248,8 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
241
248
|
this.cancelWebsocketRetry = undefined
|
|
242
249
|
}
|
|
243
250
|
|
|
251
|
+
this.receivedOnOpenPayload = undefined
|
|
252
|
+
this.receivedOnStatusPayload = undefined
|
|
244
253
|
this.shouldConnect = true
|
|
245
254
|
|
|
246
255
|
const abortableRetry = () => {
|
|
@@ -285,6 +294,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
285
294
|
createWebSocketConnection() {
|
|
286
295
|
return new Promise((resolve, reject) => {
|
|
287
296
|
if (this.webSocket) {
|
|
297
|
+
this.messageQueue = []
|
|
288
298
|
this.webSocket.close()
|
|
289
299
|
this.webSocket = null
|
|
290
300
|
}
|
|
@@ -326,6 +336,8 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
326
336
|
this.status = WebSocketStatus.Connected
|
|
327
337
|
this.emit('status', { status: WebSocketStatus.Connected })
|
|
328
338
|
this.emit('connect')
|
|
339
|
+
this.messageQueue.forEach(message => this.send(message))
|
|
340
|
+
this.messageQueue = []
|
|
329
341
|
}
|
|
330
342
|
}
|
|
331
343
|
|
|
@@ -357,6 +369,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
357
369
|
// No message received in a long time, not even your own
|
|
358
370
|
// Awareness updates, which are updated every 15 seconds.
|
|
359
371
|
this.webSocket?.close()
|
|
372
|
+
this.messageQueue = []
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
registerEventListeners() {
|
|
@@ -391,6 +404,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
391
404
|
|
|
392
405
|
try {
|
|
393
406
|
this.webSocket.close()
|
|
407
|
+
this.messageQueue = []
|
|
394
408
|
} catch {
|
|
395
409
|
//
|
|
396
410
|
}
|
|
@@ -399,6 +413,8 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
399
413
|
send(message: any) {
|
|
400
414
|
if (this.webSocket?.readyState === WsReadyStates.Open) {
|
|
401
415
|
this.webSocket.send(message)
|
|
416
|
+
} else {
|
|
417
|
+
this.messageQueue.push(message)
|
|
402
418
|
}
|
|
403
419
|
}
|
|
404
420
|
|