@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.
@@ -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 all subscribed document channels.
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: WebSocket | null;
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
- getVersions(): AuditHistoryVersion[];
23
- watchVersions(callback: Parameters<AbstractType<YArrayEvent<AuditHistoryVersion>>['observe']>[0]): void;
24
- unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<AuditHistoryVersion>>['unobserve']>[0]): void;
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
- constructor(message: IncomingMessage, logger: Debugger);
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;
@@ -6,3 +6,4 @@ export * from './IncomingMessage.js';
6
6
  export * from './MessageReceiver.js';
7
7
  export * from './OutgoingMessage.js';
8
8
  export * from './types.js';
9
+ export * from './util/debounce.js';
@@ -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: onLoadDocumentPayload): Promise<any>;
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: onLoadDocumentPayload): Promise<any>;
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: onLoadDocumentPayload;
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.5.0-rc.0",
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.5.0-rc.0",
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: WebSocket | null = null
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 ? configuration.WebSocketPolyfill : WebSocket
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(configuration: Partial<HocuspocusProviderWebsocketConfiguration> = {}): void {
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.webSocket.close()
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.onmessage = (payload: any) => this.emit('message', payload)
304
- ws.onclose = (payload: any) => this.emit('close', { event: payload })
305
- ws.onopen = (payload: any) => this.emit('open', payload)
306
- ws.onerror = (err: any) => {
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 (this.configuration.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
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.webSocket = null
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('[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.')
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(`[HocuspocusProvider] Connection closed with status Unauthorized: ${event.reason}`)
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('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.')
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(`[HocuspocusProvider] Connection closed with status MessageTooBig: ${event.reason}`)
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
- if (typeof window === 'undefined') {
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
- console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
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
- getVersions(): AuditHistoryVersion[] {
56
- console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
57
- return this.configuration.document.getArray<AuditHistoryVersion>(`${this.tiptapCollabConfigurationPrefix}versions`).toArray()
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<AuditHistoryVersion>>['observe']>[0]) {
61
- console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
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<AuditHistoryVersion>>['unobserve']>[0]) {
66
- console.error('This doesnt work yet! If you want to join as a beta tester, send an email to humans@tiptap.dev')
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