@hocuspocus/provider 2.5.0-rc.0 → 2.6.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 +68 -31
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +68 -31
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +6 -1
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +10 -2
- package/dist/packages/provider/src/TiptapCollabProvider.d.ts +19 -8
- package/dist/packages/provider/src/types.d.ts +45 -0
- package/dist/packages/server/src/Hocuspocus.d.ts +1 -9
- package/dist/packages/server/src/MessageReceiver.d.ts +2 -1
- package/dist/packages/server/src/index.d.ts +1 -0
- package/dist/packages/server/src/types.d.ts +4 -12
- package/dist/packages/server/src/util/debounce.d.ts +1 -0
- package/package.json +2 -2
- package/src/HocuspocusProvider.ts +4 -0
- package/src/HocuspocusProviderWebsocket.ts +78 -37
- package/src/TiptapCollabProvider.ts +26 -21
- package/src/types.ts +63 -0
- package/dist/tests/extension-redis/closeConnections.d.ts +0 -1
- package/dist/tests/extension-redis/getConnectionCount.d.ts +0 -1
- package/dist/tests/extension-redis/getDocumentsCount.d.ts +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import RedisClient, { ClusterNode, ClusterOptions, RedisOptions } from 'ioredis';
|
|
2
3
|
import Redlock from 'redlock';
|
|
3
4
|
import { Extension, afterLoadDocumentPayload, afterStoreDocumentPayload, onDisconnectPayload, onStoreDocumentPayload, onAwarenessUpdatePayload, onChangePayload, Debugger, onConfigurePayload, beforeBroadcastStatelessPayload, Hocuspocus } from '@hocuspocus/server';
|
|
@@ -56,18 +57,22 @@ export declare class Redis implements Extension {
|
|
|
56
57
|
*/
|
|
57
58
|
priority: number;
|
|
58
59
|
configuration: Configuration;
|
|
60
|
+
redisTransactionOrigin: string;
|
|
59
61
|
pub: RedisInstance;
|
|
60
62
|
sub: RedisInstance;
|
|
61
63
|
instance: Hocuspocus;
|
|
62
64
|
redlock: Redlock;
|
|
63
65
|
locks: Map<string, Redlock.Lock>;
|
|
64
66
|
logger: Debugger;
|
|
67
|
+
messagePrefix: Buffer;
|
|
65
68
|
constructor(configuration: Partial<Configuration>);
|
|
66
69
|
onConfigure({ instance }: onConfigurePayload): Promise<void>;
|
|
67
70
|
private getKey;
|
|
68
71
|
private pubKey;
|
|
69
72
|
private subKey;
|
|
70
73
|
private lockKey;
|
|
74
|
+
private encodeMessage;
|
|
75
|
+
private decodeMessage;
|
|
71
76
|
/**
|
|
72
77
|
* Once a document is loaded, subscribe to the channel in Redis.
|
|
73
78
|
*/
|
|
@@ -94,7 +99,7 @@ export declare class Redis implements Extension {
|
|
|
94
99
|
*/
|
|
95
100
|
onAwarenessUpdate({ documentName, awareness, added, updated, removed, }: onAwarenessUpdatePayload): Promise<number>;
|
|
96
101
|
/**
|
|
97
|
-
* Handle incoming messages published on
|
|
102
|
+
* Handle incoming messages published on subscribed document channels.
|
|
98
103
|
* Note that this will also include messages from ourselves as it is not possible
|
|
99
104
|
* in Redis to filter these.
|
|
100
105
|
*/
|
|
@@ -4,6 +4,9 @@ import { Event } from 'ws';
|
|
|
4
4
|
import EventEmitter from './EventEmitter.js';
|
|
5
5
|
import { HocuspocusProvider } from './HocuspocusProvider.js';
|
|
6
6
|
import { WebSocketStatus, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters } from './types.js';
|
|
7
|
+
export type HocusPocusWebSocket = WebSocket & {
|
|
8
|
+
identifier: string;
|
|
9
|
+
};
|
|
7
10
|
export type HocuspocusProviderWebsocketConfiguration = Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>> & Partial<CompleteHocuspocusProviderWebsocketConfiguration>;
|
|
8
11
|
export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
9
12
|
/**
|
|
@@ -79,10 +82,14 @@ export declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
79
82
|
private messageQueue;
|
|
80
83
|
configuration: CompleteHocuspocusProviderWebsocketConfiguration;
|
|
81
84
|
subscribedToBroadcastChannel: boolean;
|
|
82
|
-
webSocket:
|
|
85
|
+
webSocket: HocusPocusWebSocket | null;
|
|
86
|
+
webSocketHandlers: {
|
|
87
|
+
[key: string]: any;
|
|
88
|
+
};
|
|
83
89
|
shouldConnect: boolean;
|
|
84
90
|
status: WebSocketStatus;
|
|
85
91
|
lastMessageReceived: number;
|
|
92
|
+
identifier: number;
|
|
86
93
|
mux: mutex.mutex;
|
|
87
94
|
intervals: any;
|
|
88
95
|
connectionAttempt: {
|
|
@@ -100,6 +107,8 @@ export declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
100
107
|
boundConnect: () => Promise<unknown>;
|
|
101
108
|
cancelWebsocketRetry?: () => void;
|
|
102
109
|
connect(): Promise<unknown>;
|
|
110
|
+
attachWebSocketListeners(ws: HocusPocusWebSocket, reject: Function): void;
|
|
111
|
+
cleanupWebSocket(): void;
|
|
103
112
|
createWebSocketConnection(): Promise<unknown>;
|
|
104
113
|
onMessage(event: MessageEvent): void;
|
|
105
114
|
resolveConnectionAttempt(): void;
|
|
@@ -107,7 +116,6 @@ export declare class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
107
116
|
rejectConnectionAttempt(): void;
|
|
108
117
|
closeTries: number;
|
|
109
118
|
checkConnection(): void;
|
|
110
|
-
registerEventListeners(): void;
|
|
111
119
|
get serverUrl(): string;
|
|
112
120
|
get url(): string;
|
|
113
121
|
disconnect(): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AbstractType, YArrayEvent } from 'yjs';
|
|
2
2
|
import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider.js';
|
|
3
3
|
import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js';
|
|
4
|
+
import type { THistoryVersion } from './types';
|
|
4
5
|
export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>);
|
|
5
6
|
export interface AdditionalTiptapCollabProviderConfiguration {
|
|
6
7
|
/**
|
|
@@ -9,19 +10,29 @@ export interface AdditionalTiptapCollabProviderConfiguration {
|
|
|
9
10
|
appId?: string;
|
|
10
11
|
websocketProvider?: TiptapCollabProviderWebsocket;
|
|
11
12
|
}
|
|
12
|
-
export type AuditHistoryVersion = {
|
|
13
|
-
name?: string;
|
|
14
|
-
version: number;
|
|
15
|
-
date: number;
|
|
16
|
-
};
|
|
17
13
|
export declare class TiptapCollabProvider extends HocuspocusProvider {
|
|
18
14
|
tiptapCollabConfigurationPrefix: string;
|
|
19
15
|
constructor(configuration: TiptapCollabProviderConfiguration);
|
|
16
|
+
/**
|
|
17
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
18
|
+
*/
|
|
20
19
|
createVersion(name?: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
22
|
+
*/
|
|
21
23
|
revertToVersion(targetVersion: number): void;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
26
|
+
*
|
|
27
|
+
* The server will reply with a stateless message (THistoryVersionPreviewEvent)
|
|
28
|
+
*/
|
|
29
|
+
previewVersion(targetVersion: number): void;
|
|
30
|
+
/**
|
|
31
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
32
|
+
*/
|
|
33
|
+
getVersions(): THistoryVersion[];
|
|
34
|
+
watchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['observe']>[0]): void;
|
|
35
|
+
unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['unobserve']>[0]): void;
|
|
25
36
|
isAutoVersioning(): boolean;
|
|
26
37
|
enableAutoVersioning(): 1;
|
|
27
38
|
disableAutoVersioning(): 0;
|
|
@@ -84,3 +84,48 @@ export type StatesArray = {
|
|
|
84
84
|
clientId: number;
|
|
85
85
|
[key: string | number]: any;
|
|
86
86
|
}[];
|
|
87
|
+
export type THistoryVersion = {
|
|
88
|
+
name?: string;
|
|
89
|
+
version: number;
|
|
90
|
+
date: number;
|
|
91
|
+
};
|
|
92
|
+
export type THistoryConfiguration = {
|
|
93
|
+
autoVersioning: boolean;
|
|
94
|
+
currentVersion: number;
|
|
95
|
+
stateCaptured: number;
|
|
96
|
+
};
|
|
97
|
+
export type THistoryAction = THistoryDocumentRevertAction | THistoryVersionCreateAction | THistoryVersionPreviewAction;
|
|
98
|
+
export type THistoryDocumentRevertAction = {
|
|
99
|
+
action: 'document.revert';
|
|
100
|
+
/**
|
|
101
|
+
* if changes havent been persisted to a version yet, we'll create one with the specified name,
|
|
102
|
+
* expect when `false` is passed.
|
|
103
|
+
*/
|
|
104
|
+
currentVersionName?: string | false;
|
|
105
|
+
/**
|
|
106
|
+
* Name of the version that is created after the revert. Pass `false` to avoid generating a new version.
|
|
107
|
+
*/
|
|
108
|
+
newVersionName?: string | false;
|
|
109
|
+
};
|
|
110
|
+
export type THistoryVersionCreateAction = {
|
|
111
|
+
action: 'version.create';
|
|
112
|
+
name?: string;
|
|
113
|
+
};
|
|
114
|
+
export type THistoryVersionPreviewAction = {
|
|
115
|
+
action: 'version.preview';
|
|
116
|
+
version: number;
|
|
117
|
+
};
|
|
118
|
+
export type THistoryEvent = THistoryVersionPreviewEvent | THistoryVersionCreatedEvent | THistoryDocumentRevertedEvent;
|
|
119
|
+
export type THistoryVersionCreatedEvent = {
|
|
120
|
+
event: 'version.created';
|
|
121
|
+
version: number;
|
|
122
|
+
};
|
|
123
|
+
export type THistoryVersionPreviewEvent = {
|
|
124
|
+
event: 'version.preview';
|
|
125
|
+
version: number;
|
|
126
|
+
ydoc: string;
|
|
127
|
+
};
|
|
128
|
+
export type THistoryDocumentRevertedEvent = {
|
|
129
|
+
event: 'document.reverted';
|
|
130
|
+
version: number;
|
|
131
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
2
|
import { IncomingMessage } from 'http';
|
|
4
3
|
import WebSocket, { AddressInfo } from 'ws';
|
|
5
4
|
import { Server as HocuspocusServer } from './Server';
|
|
@@ -29,6 +28,7 @@ export declare class Hocuspocus {
|
|
|
29
28
|
documents: Map<string, Document>;
|
|
30
29
|
server?: HocuspocusServer;
|
|
31
30
|
debugger: Debugger;
|
|
31
|
+
debounce: (id: string, func: Function, debounce: number, maxDebounce: number) => void;
|
|
32
32
|
constructor(configuration?: Partial<Configuration>);
|
|
33
33
|
/**
|
|
34
34
|
* Configure the server
|
|
@@ -78,14 +78,6 @@ export declare class Hocuspocus {
|
|
|
78
78
|
* the update is incoming from the provider, but can be anything if the updates is originated from an extension.
|
|
79
79
|
*/
|
|
80
80
|
private handleDocumentUpdate;
|
|
81
|
-
timers: Map<string, {
|
|
82
|
-
timeout: NodeJS.Timeout;
|
|
83
|
-
start: number;
|
|
84
|
-
}>;
|
|
85
|
-
/**
|
|
86
|
-
* debounce the given function, using the given identifier
|
|
87
|
-
*/
|
|
88
|
-
debounce(id: string, func: Function, immediately?: boolean): void;
|
|
89
81
|
/**
|
|
90
82
|
* Create a new document by the given request
|
|
91
83
|
*/
|
|
@@ -5,7 +5,8 @@ import { IncomingMessage } from './IncomingMessage.js';
|
|
|
5
5
|
export declare class MessageReceiver {
|
|
6
6
|
message: IncomingMessage;
|
|
7
7
|
logger: Debugger;
|
|
8
|
-
|
|
8
|
+
defaultTransactionOrigin?: string;
|
|
9
|
+
constructor(message: IncomingMessage, logger: Debugger, defaultTransactionOrigin?: string);
|
|
9
10
|
apply(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): void;
|
|
10
11
|
readSyncMessage(message: IncomingMessage, document: Document, connection?: Connection, reply?: (message: Uint8Array) => void, requestFirstSync?: boolean): 0 | 1 | 2;
|
|
11
12
|
applyQueryAwarenessMessage(document: Document, reply?: (message: Uint8Array) => void): void;
|
|
@@ -39,7 +39,7 @@ export interface Extension {
|
|
|
39
39
|
connected?(data: connectedPayload): Promise<any>;
|
|
40
40
|
onAuthenticate?(data: onAuthenticatePayload): Promise<any>;
|
|
41
41
|
onLoadDocument?(data: onLoadDocumentPayload): Promise<any>;
|
|
42
|
-
afterLoadDocument?(data:
|
|
42
|
+
afterLoadDocument?(data: afterLoadDocumentPayload): Promise<any>;
|
|
43
43
|
beforeHandleMessage?(data: beforeHandleMessagePayload): Promise<any>;
|
|
44
44
|
beforeBroadcastStateless?(data: beforeBroadcastStatelessPayload): Promise<any>;
|
|
45
45
|
onStateless?(payload: onStatelessPayload): Promise<any>;
|
|
@@ -49,7 +49,7 @@ export interface Extension {
|
|
|
49
49
|
onAwarenessUpdate?(data: onAwarenessUpdatePayload): Promise<any>;
|
|
50
50
|
onRequest?(data: onRequestPayload): Promise<any>;
|
|
51
51
|
onDisconnect?(data: onDisconnectPayload): Promise<any>;
|
|
52
|
-
afterUnloadDocument?(data:
|
|
52
|
+
afterUnloadDocument?(data: afterUnloadDocumentPayload): Promise<any>;
|
|
53
53
|
onDestroy?(data: onDestroyPayload): Promise<any>;
|
|
54
54
|
}
|
|
55
55
|
export type HookName = 'onConfigure' | 'onListen' | 'onUpgrade' | 'onConnect' | 'connected' | 'onAuthenticate' | 'onLoadDocument' | 'afterLoadDocument' | 'beforeHandleMessage' | 'beforeBroadcastStateless' | 'onStateless' | 'onChange' | 'onStoreDocument' | 'afterStoreDocument' | 'onAwarenessUpdate' | 'onRequest' | 'onDisconnect' | 'afterUnloadDocument' | 'onDestroy';
|
|
@@ -61,7 +61,7 @@ export type HookPayloadByName = {
|
|
|
61
61
|
connected: connectedPayload;
|
|
62
62
|
onAuthenticate: onAuthenticatePayload;
|
|
63
63
|
onLoadDocument: onLoadDocumentPayload;
|
|
64
|
-
afterLoadDocument:
|
|
64
|
+
afterLoadDocument: afterLoadDocumentPayload;
|
|
65
65
|
beforeHandleMessage: beforeHandleMessagePayload;
|
|
66
66
|
beforeBroadcastStateless: beforeBroadcastStatelessPayload;
|
|
67
67
|
onStateless: onStatelessPayload;
|
|
@@ -123,15 +123,6 @@ export interface Configuration extends Extension {
|
|
|
123
123
|
gc: boolean;
|
|
124
124
|
gcFilter: () => boolean;
|
|
125
125
|
};
|
|
126
|
-
/**
|
|
127
|
-
* Function which returns the (customized) document name based on the request
|
|
128
|
-
*/
|
|
129
|
-
getDocumentName?(data: getDocumentNamePayload): string | Promise<string>;
|
|
130
|
-
}
|
|
131
|
-
export interface getDocumentNamePayload {
|
|
132
|
-
documentName: string;
|
|
133
|
-
request: IncomingMessage;
|
|
134
|
-
requestParameters: URLSearchParams;
|
|
135
126
|
}
|
|
136
127
|
export interface onStatelessPayload {
|
|
137
128
|
connection: Connection;
|
|
@@ -149,6 +140,7 @@ export interface onAuthenticatePayload {
|
|
|
149
140
|
connection: ConnectionConfiguration;
|
|
150
141
|
}
|
|
151
142
|
export interface onConnectPayload {
|
|
143
|
+
context: any;
|
|
152
144
|
documentName: string;
|
|
153
145
|
instance: Hocuspocus;
|
|
154
146
|
request: IncomingMessage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useDebounce: () => (id: string, func: Function, debounce: number, maxDebounce: number) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hocuspocus/provider",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
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": "^2.
|
|
32
|
+
"@hocuspocus/common": "^2.6.0",
|
|
33
33
|
"@lifeomic/attempt": "^3.0.2",
|
|
34
34
|
"lib0": "^0.2.47",
|
|
35
35
|
"ws": "^7.5.9"
|
|
@@ -367,6 +367,10 @@ export class HocuspocusProvider extends EventEmitter {
|
|
|
367
367
|
|
|
368
368
|
// not needed, but provides backward compatibility with e.g. lexicla/yjs
|
|
369
369
|
async connect() {
|
|
370
|
+
if (this.configuration.broadcast) {
|
|
371
|
+
this.subscribeToBroadcastChannel()
|
|
372
|
+
}
|
|
373
|
+
|
|
370
374
|
return this.configuration.websocketProvider.connect()
|
|
371
375
|
}
|
|
372
376
|
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters,
|
|
16
16
|
} from './types.js'
|
|
17
17
|
|
|
18
|
+
export type HocusPocusWebSocket = WebSocket & { identifier: string };
|
|
19
|
+
|
|
18
20
|
export type HocuspocusProviderWebsocketConfiguration =
|
|
19
21
|
Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>>
|
|
20
22
|
& Partial<CompleteHocuspocusProviderWebsocketConfiguration>
|
|
@@ -136,7 +138,9 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
136
138
|
|
|
137
139
|
subscribedToBroadcastChannel = false
|
|
138
140
|
|
|
139
|
-
webSocket:
|
|
141
|
+
webSocket: HocusPocusWebSocket | null = null
|
|
142
|
+
|
|
143
|
+
webSocketHandlers: { [key: string]: any } = {}
|
|
140
144
|
|
|
141
145
|
shouldConnect = true
|
|
142
146
|
|
|
@@ -144,6 +148,8 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
144
148
|
|
|
145
149
|
lastMessageReceived = 0
|
|
146
150
|
|
|
151
|
+
identifier = 0
|
|
152
|
+
|
|
147
153
|
mux = mutex.createMutex()
|
|
148
154
|
|
|
149
155
|
intervals: any = {
|
|
@@ -152,15 +158,17 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
connectionAttempt: {
|
|
155
|
-
resolve: (value?: any) => void
|
|
156
|
-
reject: (reason?: any) => void
|
|
161
|
+
resolve: (value?: any) => void;
|
|
162
|
+
reject: (reason?: any) => void;
|
|
157
163
|
} | null = null
|
|
158
164
|
|
|
159
165
|
constructor(configuration: HocuspocusProviderWebsocketConfiguration) {
|
|
160
166
|
super()
|
|
161
167
|
this.setConfiguration(configuration)
|
|
162
168
|
|
|
163
|
-
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill
|
|
169
|
+
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill
|
|
170
|
+
? configuration.WebSocketPolyfill
|
|
171
|
+
: WebSocket
|
|
164
172
|
|
|
165
173
|
this.on('open', this.configuration.onOpen)
|
|
166
174
|
this.on('open', this.onOpen.bind(this))
|
|
@@ -178,8 +186,6 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
178
186
|
this.on('close', this.onClose.bind(this))
|
|
179
187
|
this.on('message', this.onMessage.bind(this))
|
|
180
188
|
|
|
181
|
-
this.registerEventListeners()
|
|
182
|
-
|
|
183
189
|
this.intervals.connectionChecker = setInterval(
|
|
184
190
|
this.checkConnection.bind(this),
|
|
185
191
|
this.configuration.messageReconnectTimeout / 10,
|
|
@@ -224,10 +230,11 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
224
230
|
|
|
225
231
|
detach(provider: HocuspocusProvider) {
|
|
226
232
|
// tell the server to remove the listener
|
|
227
|
-
|
|
228
233
|
}
|
|
229
234
|
|
|
230
|
-
public setConfiguration(
|
|
235
|
+
public setConfiguration(
|
|
236
|
+
configuration: Partial<HocuspocusProviderWebsocketConfiguration> = {},
|
|
237
|
+
): void {
|
|
231
238
|
this.configuration = { ...this.configuration, ...configuration }
|
|
232
239
|
}
|
|
233
240
|
|
|
@@ -289,23 +296,60 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
289
296
|
return retryPromise
|
|
290
297
|
}
|
|
291
298
|
|
|
299
|
+
attachWebSocketListeners(ws: HocusPocusWebSocket, reject: Function) {
|
|
300
|
+
const { identifier } = ws
|
|
301
|
+
const onMessageHandler = (payload: any) => this.emit('message', payload)
|
|
302
|
+
const onCloseHandler = (payload: any) => this.emit('close', { event: payload })
|
|
303
|
+
const onOpenHandler = (payload: any) => this.emit('open', payload)
|
|
304
|
+
const onErrorHandler = (err: any) => {
|
|
305
|
+
reject(err)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.webSocketHandlers[identifier] = {
|
|
309
|
+
message: onMessageHandler,
|
|
310
|
+
close: onCloseHandler,
|
|
311
|
+
open: onOpenHandler,
|
|
312
|
+
error: onErrorHandler,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const handlers = this.webSocketHandlers[ws.identifier]
|
|
316
|
+
|
|
317
|
+
Object.keys(handlers).forEach(name => {
|
|
318
|
+
ws.addEventListener(name, handlers[name])
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
cleanupWebSocket() {
|
|
323
|
+
if (!this.webSocket) {
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
const { identifier } = this.webSocket
|
|
327
|
+
const handlers = this.webSocketHandlers[identifier]
|
|
328
|
+
|
|
329
|
+
Object.keys(handlers).forEach(name => {
|
|
330
|
+
this.webSocket?.removeEventListener(name, handlers[name])
|
|
331
|
+
delete this.webSocketHandlers[identifier]
|
|
332
|
+
})
|
|
333
|
+
this.webSocket.close()
|
|
334
|
+
this.webSocket = null
|
|
335
|
+
}
|
|
336
|
+
|
|
292
337
|
createWebSocketConnection() {
|
|
293
338
|
return new Promise((resolve, reject) => {
|
|
294
339
|
if (this.webSocket) {
|
|
295
340
|
this.messageQueue = []
|
|
296
|
-
this.
|
|
297
|
-
this.webSocket = null
|
|
341
|
+
this.cleanupWebSocket()
|
|
298
342
|
}
|
|
343
|
+
this.lastMessageReceived = 0
|
|
344
|
+
this.identifier += 1
|
|
299
345
|
|
|
300
346
|
// Init the WebSocket connection
|
|
301
347
|
const ws = new this.configuration.WebSocketPolyfill(this.url)
|
|
302
348
|
ws.binaryType = 'arraybuffer'
|
|
303
|
-
ws.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
reject(err)
|
|
308
|
-
}
|
|
349
|
+
ws.identifier = this.identifier
|
|
350
|
+
|
|
351
|
+
this.attachWebSocketListeners(ws, reject)
|
|
352
|
+
|
|
309
353
|
this.webSocket = ws
|
|
310
354
|
|
|
311
355
|
// Reset the status
|
|
@@ -362,7 +406,10 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
362
406
|
}
|
|
363
407
|
|
|
364
408
|
// Don’t close the connection when a message was received recently
|
|
365
|
-
if (
|
|
409
|
+
if (
|
|
410
|
+
this.configuration.messageReconnectTimeout
|
|
411
|
+
>= time.getUnixTime() - this.lastMessageReceived
|
|
412
|
+
) {
|
|
366
413
|
return
|
|
367
414
|
}
|
|
368
415
|
|
|
@@ -383,15 +430,6 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
383
430
|
this.webSocket?.close()
|
|
384
431
|
this.messageQueue = []
|
|
385
432
|
}
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
registerEventListeners() {
|
|
390
|
-
if (typeof window === 'undefined') {
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
window.addEventListener('online', this.boundConnect)
|
|
395
433
|
}
|
|
396
434
|
|
|
397
435
|
// Ensure that the URL always ends with /
|
|
@@ -434,7 +472,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
434
472
|
|
|
435
473
|
onClose({ event }: onCloseParameters) {
|
|
436
474
|
this.closeTries = 0
|
|
437
|
-
this.
|
|
475
|
+
this.cleanupWebSocket()
|
|
438
476
|
|
|
439
477
|
if (this.status === WebSocketStatus.Connected) {
|
|
440
478
|
this.status = WebSocketStatus.Disconnected
|
|
@@ -444,9 +482,13 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
444
482
|
|
|
445
483
|
if (event.code === Unauthorized.code) {
|
|
446
484
|
if (event.reason === Unauthorized.reason) {
|
|
447
|
-
console.warn(
|
|
485
|
+
console.warn(
|
|
486
|
+
'[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.',
|
|
487
|
+
)
|
|
448
488
|
} else {
|
|
449
|
-
console.warn(
|
|
489
|
+
console.warn(
|
|
490
|
+
`[HocuspocusProvider] Connection closed with status Unauthorized: ${event.reason}`,
|
|
491
|
+
)
|
|
450
492
|
}
|
|
451
493
|
|
|
452
494
|
this.shouldConnect = false
|
|
@@ -454,13 +496,17 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
454
496
|
|
|
455
497
|
if (event.code === Forbidden.code) {
|
|
456
498
|
if (!this.configuration.quiet) {
|
|
457
|
-
console.warn(
|
|
499
|
+
console.warn(
|
|
500
|
+
'[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.',
|
|
501
|
+
)
|
|
458
502
|
return // TODO REMOVE ME
|
|
459
503
|
}
|
|
460
504
|
}
|
|
461
505
|
|
|
462
506
|
if (event.code === MessageTooBig.code) {
|
|
463
|
-
console.warn(
|
|
507
|
+
console.warn(
|
|
508
|
+
`[HocuspocusProvider] Connection closed with status MessageTooBig: ${event.reason}`,
|
|
509
|
+
)
|
|
464
510
|
this.shouldConnect = false
|
|
465
511
|
}
|
|
466
512
|
|
|
@@ -506,11 +552,6 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
506
552
|
|
|
507
553
|
this.removeAllListeners()
|
|
508
554
|
|
|
509
|
-
|
|
510
|
-
return
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
window.removeEventListener('online', this.boundConnect)
|
|
555
|
+
this.cleanupWebSocket()
|
|
514
556
|
}
|
|
515
|
-
|
|
516
557
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from './HocuspocusProvider.js'
|
|
6
6
|
|
|
7
7
|
import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'
|
|
8
|
+
import type { THistoryVersion } from './types'
|
|
8
9
|
|
|
9
10
|
export type TiptapCollabProviderConfiguration =
|
|
10
11
|
Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
|
|
@@ -21,12 +22,6 @@ export interface AdditionalTiptapCollabProviderConfiguration {
|
|
|
21
22
|
websocketProvider?: TiptapCollabProviderWebsocket
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export type AuditHistoryVersion = {
|
|
25
|
-
name?: string;
|
|
26
|
-
version: number;
|
|
27
|
-
date: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
25
|
export class TiptapCollabProvider extends HocuspocusProvider {
|
|
31
26
|
tiptapCollabConfigurationPrefix = '__tiptapcollab__'
|
|
32
27
|
|
|
@@ -42,43 +37,53 @@ export class TiptapCollabProvider extends HocuspocusProvider {
|
|
|
42
37
|
super(configuration as HocuspocusProviderConfiguration)
|
|
43
38
|
}
|
|
44
39
|
|
|
40
|
+
/**
|
|
41
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
42
|
+
*/
|
|
45
43
|
createVersion(name?: string) {
|
|
46
|
-
console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
|
|
47
44
|
return this.sendStateless(JSON.stringify({ action: 'version.create', name }))
|
|
48
45
|
}
|
|
49
46
|
|
|
47
|
+
/**
|
|
48
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
49
|
+
*/
|
|
50
50
|
revertToVersion(targetVersion: number) {
|
|
51
|
-
|
|
52
|
-
return this.sendStateless(JSON.stringify({ action: 'version.revert', version: targetVersion }))
|
|
51
|
+
return this.sendStateless(JSON.stringify({ action: 'document.revert', version: targetVersion }))
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
/**
|
|
55
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
56
|
+
*
|
|
57
|
+
* The server will reply with a stateless message (THistoryVersionPreviewEvent)
|
|
58
|
+
*/
|
|
59
|
+
previewVersion(targetVersion: number) {
|
|
60
|
+
return this.sendStateless(JSON.stringify({ action: 'version.preview', version: targetVersion }))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
65
|
+
*/
|
|
66
|
+
getVersions(): THistoryVersion[] {
|
|
67
|
+
return this.configuration.document.getArray<THistoryVersion>(`${this.tiptapCollabConfigurationPrefix}versions`).toArray()
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
watchVersions(callback: Parameters<AbstractType<YArrayEvent<
|
|
61
|
-
|
|
62
|
-
return this.configuration.document.getArray<AuditHistoryVersion>('__tiptapcollab__versions').observe(callback)
|
|
70
|
+
watchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['observe']>[0]) {
|
|
71
|
+
return this.configuration.document.getArray<THistoryVersion>('__tiptapcollab__versions').observe(callback)
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<
|
|
66
|
-
|
|
67
|
-
return this.configuration.document.getArray<AuditHistoryVersion>('__tiptapcollab__versions').unobserve(callback)
|
|
74
|
+
unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['unobserve']>[0]) {
|
|
75
|
+
return this.configuration.document.getArray<THistoryVersion>('__tiptapcollab__versions').unobserve(callback)
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
isAutoVersioning(): boolean {
|
|
71
|
-
console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
|
|
72
79
|
return !!this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).get('autoVersioning')
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
enableAutoVersioning() {
|
|
76
|
-
console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
|
|
77
83
|
return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 1)
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
disableAutoVersioning() {
|
|
81
|
-
console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
|
|
82
87
|
return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0)
|
|
83
88
|
}
|
|
84
89
|
|