@hocuspocus/provider 1.0.0-alpha.28 → 1.0.0-alpha.29

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.
@@ -0,0 +1,23 @@
1
+ export interface CloseEvent {
2
+ code: number;
3
+ reason: string;
4
+ }
5
+ /**
6
+ * The server successfully processed the request, asks that the requester reset
7
+ * its document view, and is not returning any content.
8
+ */
9
+ export declare const ResetConnection: CloseEvent;
10
+ /**
11
+ * Similar to Forbidden, but specifically for use when authentication is required and has
12
+ * failed or has not yet been provided.
13
+ */
14
+ export declare const Unauthorized: CloseEvent;
15
+ /**
16
+ * The request contained valid data and was understood by the server, but the server
17
+ * is refusing action.
18
+ */
19
+ export declare const Forbidden: CloseEvent;
20
+ /**
21
+ * The server timed out waiting for the request.
22
+ */
23
+ export declare const ConnectionTimeout: CloseEvent;
@@ -1 +1,2 @@
1
1
  export * from './auth';
2
+ export * from './CloseEvents';
@@ -1,10 +1,11 @@
1
- import { HocuspocusProvider, HocuspocusProviderOptions } from './HocuspocusProvider';
2
- export interface HocuspocusCloudProviderOptions extends HocuspocusProviderOptions {
1
+ import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider';
2
+ export declare type HocuspocusCloudProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & AdditionalHocuspocusCloudProviderConfiguration;
3
+ export interface AdditionalHocuspocusCloudProviderConfiguration {
3
4
  /**
4
5
  * A Hocuspocus Cloud key, get one here: https://hocuspocus.cloud/
5
6
  */
6
7
  key: string;
7
8
  }
8
9
  export declare class HocuspocusCloudProvider extends HocuspocusProvider {
9
- constructor(options: HocuspocusCloudProviderOptions);
10
+ constructor(configuration: HocuspocusCloudProviderConfiguration);
10
11
  }
@@ -10,7 +10,8 @@ export declare enum WebSocketStatus {
10
10
  Connected = "connected",
11
11
  Disconnected = "disconnected"
12
12
  }
13
- export interface HocuspocusProviderOptions {
13
+ export declare type HocuspocusProviderConfiguration = Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>> & Partial<CompleteHocuspocusProviderConfiguration>;
14
+ export interface CompleteHocuspocusProviderConfiguration {
14
15
  /**
15
16
  * URL of your @hocuspocus/server instance
16
17
  */
@@ -104,9 +105,13 @@ export interface HocuspocusProviderOptions {
104
105
  onDestroy: () => void;
105
106
  onAwarenessUpdate: (states: any) => void;
106
107
  onAwarenessChange: (states: any) => void;
108
+ /**
109
+ * Don’t output any warnings.
110
+ */
111
+ quiet: boolean;
107
112
  }
108
113
  export declare class HocuspocusProvider extends EventEmitter {
109
- options: HocuspocusProviderOptions;
114
+ configuration: CompleteHocuspocusProviderConfiguration;
110
115
  subscribedToBroadcastChannel: boolean;
111
116
  webSocket: WebSocket | null;
112
117
  shouldConnect: boolean;
@@ -120,8 +125,8 @@ export declare class HocuspocusProvider extends EventEmitter {
120
125
  resolve: (value?: any) => void;
121
126
  reject: (reason?: any) => void;
122
127
  } | null;
123
- constructor(options?: Partial<HocuspocusProviderOptions>);
124
- setOptions(options?: Partial<HocuspocusProviderOptions>): void;
128
+ constructor(configuration: HocuspocusProviderConfiguration);
129
+ setConfiguration(configuration?: Partial<HocuspocusProviderConfiguration>): void;
125
130
  connect(): Promise<void>;
126
131
  createWebSocketConnection(): Promise<unknown>;
127
132
  resolveConnectionAttempt(): void;
@@ -2,8 +2,8 @@
2
2
  import AsyncLock from 'async-lock';
3
3
  import WebSocket from 'ws';
4
4
  import { IncomingMessage as HTTPIncomingMessage } from 'http';
5
+ import { CloseEvent } from '@hocuspocus/common';
5
6
  import Document from './Document';
6
- import { CloseEvent } from './types';
7
7
  import { MessageLogger } from './Debugger';
8
8
  declare class Connection {
9
9
  webSocket: WebSocket;
@@ -23,7 +23,7 @@ export declare class Hocuspocus {
23
23
  * Configure the server
24
24
  */
25
25
  configure(configuration: Partial<Configuration>): Hocuspocus;
26
- get authenticationRequired(): boolean;
26
+ get requiresAuthentication(): boolean;
27
27
  /**
28
28
  * Start the server
29
29
  */
@@ -46,7 +46,14 @@ export declare class Hocuspocus {
46
46
  */
47
47
  destroy(): Promise<any>;
48
48
  /**
49
- * Handle the incoming WebSocket connection
49
+ * The `handleConnection` method receives incoming WebSocket connections,
50
+ * runs all hooks:
51
+ *
52
+ * - onConnect for all connections
53
+ * - onAuthenticate only if required
54
+ *
55
+ * … and if nothings fails it’ll fully establish the connection and
56
+ * load the Document then.
50
57
  */
51
58
  handleConnection(incoming: WebSocket, request: IncomingMessage, documentName: string, context?: any): void;
52
59
  /**
@@ -25,8 +25,9 @@ export interface AwarenessUpdate {
25
25
  updated: Array<any>;
26
26
  removed: Array<any>;
27
27
  }
28
- export interface ConnectionConfig {
28
+ export interface ConnectionConfiguration {
29
29
  readOnly: boolean;
30
+ requiresAuthentication: boolean;
30
31
  isAuthenticated: boolean;
31
32
  }
32
33
  export interface Extension {
@@ -87,7 +88,7 @@ export interface onAuthenticatePayload {
87
88
  requestParameters: URLSearchParams;
88
89
  socketId: string;
89
90
  token: string;
90
- connection: ConnectionConfig;
91
+ connection: ConnectionConfiguration;
91
92
  }
92
93
  export interface onConnectPayload {
93
94
  documentName: string;
@@ -96,7 +97,7 @@ export interface onConnectPayload {
96
97
  requestHeaders: IncomingHttpHeaders;
97
98
  requestParameters: URLSearchParams;
98
99
  socketId: string;
99
- connection: ConnectionConfig;
100
+ connection: ConnectionConfiguration;
100
101
  }
101
102
  export interface onLoadDocumentPayload {
102
103
  context: any;
@@ -106,7 +107,7 @@ export interface onLoadDocumentPayload {
106
107
  requestHeaders: IncomingHttpHeaders;
107
108
  requestParameters: URLSearchParams;
108
109
  socketId: string;
109
- connection: ConnectionConfig;
110
+ connection: ConnectionConfiguration;
110
111
  }
111
112
  export interface onChangePayload {
112
113
  clientsCount: number;
@@ -152,7 +153,3 @@ export interface onConfigurePayload {
152
153
  yjsVersion: string;
153
154
  instance: Hocuspocus;
154
155
  }
155
- export interface CloseEvent {
156
- code: number;
157
- reason: string;
158
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/provider",
3
- "version": "1.0.0-alpha.28",
3
+ "version": "1.0.0-alpha.29",
4
4
  "description": "hocuspocus provider",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -28,10 +28,11 @@
28
28
  "dist"
29
29
  ],
30
30
  "dependencies": {
31
+ "@hocuspocus/common": "^1.0.0-alpha.4",
31
32
  "@lifeomic/attempt": "^3.0.1",
32
33
  "lib0": "^0.2.43",
33
34
  "y-protocols": "^1.0.5",
34
35
  "yjs": "^13.5.22"
35
36
  },
36
- "gitHead": "8ffe8d8f4d10ba33f4f203806bd1d4415bef5dc3"
37
+ "gitHead": "87b715d1d28c603702879f4413c17d2c4a38c51a"
37
38
  }
@@ -1,6 +1,14 @@
1
- import { HocuspocusProvider, HocuspocusProviderOptions } from './HocuspocusProvider'
1
+ import {
2
+ HocuspocusProvider,
3
+ HocuspocusProviderConfiguration,
4
+ } from './HocuspocusProvider'
2
5
 
3
- export interface HocuspocusCloudProviderOptions extends HocuspocusProviderOptions {
6
+ export type HocuspocusCloudProviderConfiguration =
7
+ Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
8
+ Partial<HocuspocusProviderConfiguration> &
9
+ AdditionalHocuspocusCloudProviderConfiguration
10
+
11
+ export interface AdditionalHocuspocusCloudProviderConfiguration {
4
12
  /**
5
13
  * A Hocuspocus Cloud key, get one here: https://hocuspocus.cloud/
6
14
  */
@@ -8,19 +16,19 @@ export interface HocuspocusCloudProviderOptions extends HocuspocusProviderOption
8
16
  }
9
17
 
10
18
  export class HocuspocusCloudProvider extends HocuspocusProvider {
11
- constructor(options: HocuspocusCloudProviderOptions) {
12
- if (!options.url) {
13
- options.url = 'wss://connect.hocuspocus.cloud'
19
+ constructor(configuration: HocuspocusCloudProviderConfiguration) {
20
+ if (!configuration.url) {
21
+ configuration.url = 'wss://connect.hocuspocus.cloud'
14
22
  }
15
23
 
16
- if (options.key) {
17
- if (!options.parameters) {
18
- options.parameters = {}
24
+ if (configuration.key) {
25
+ if (!configuration.parameters) {
26
+ configuration.parameters = {}
19
27
  }
20
28
 
21
- options.parameters.key = options.key
29
+ configuration.parameters.key = configuration.key
22
30
  }
23
31
 
24
- super(options as HocuspocusProviderOptions)
32
+ super(configuration as HocuspocusProviderConfiguration)
25
33
  }
26
34
  }
@@ -6,6 +6,7 @@ import * as mutex from 'lib0/mutex'
6
6
  import * as url from 'lib0/url'
7
7
  import { Event, CloseEvent, MessageEvent } from 'ws'
8
8
  import { retry } from '@lifeomic/attempt'
9
+ import { Forbidden, Unauthorized } from '@hocuspocus/common'
9
10
  import EventEmitter from './EventEmitter'
10
11
  import { IncomingMessage } from './IncomingMessage'
11
12
  import { MessageReceiver } from './MessageReceiver'
@@ -26,15 +27,19 @@ export enum WebSocketStatus {
26
27
  Disconnected = 'disconnected',
27
28
  }
28
29
 
29
- export interface HocuspocusProviderOptions {
30
+ export type HocuspocusProviderConfiguration =
31
+ Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>>
32
+ & Partial<CompleteHocuspocusProviderConfiguration>
33
+
34
+ export interface CompleteHocuspocusProviderConfiguration {
30
35
  /**
31
36
  * URL of your @hocuspocus/server instance
32
37
  */
33
- url: string,
34
- /**
35
- * The identifier/name of your document
36
- */
37
- name: string,
38
+ url: string,
39
+ /**
40
+ * The identifier/name of your document
41
+ */
42
+ name: string,
38
43
  /**
39
44
  * The actual Y.js document
40
45
  */
@@ -116,17 +121,21 @@ export interface HocuspocusProviderOptions {
116
121
  onDestroy: () => void,
117
122
  onAwarenessUpdate: (states: any) => void,
118
123
  onAwarenessChange: (states: any) => void,
124
+ /**
125
+ * Don’t output any warnings.
126
+ */
127
+ quiet: boolean,
119
128
  }
120
129
 
121
130
  export class HocuspocusProvider extends EventEmitter {
122
- public options: HocuspocusProviderOptions = {
131
+ public configuration: CompleteHocuspocusProviderConfiguration = {
132
+ name: '',
133
+ url: '',
123
134
  // @ts-ignore
124
135
  document: undefined,
125
136
  // @ts-ignore
126
137
  awareness: undefined,
127
138
  WebSocketPolyfill: undefined,
128
- url: '',
129
- name: '',
130
139
  token: null,
131
140
  parameters: {},
132
141
  connect: true,
@@ -163,6 +172,7 @@ export class HocuspocusProvider extends EventEmitter {
163
172
  onDestroy: () => null,
164
173
  onAwarenessUpdate: () => null,
165
174
  onAwarenessChange: () => null,
175
+ quiet: false,
166
176
  }
167
177
 
168
178
  subscribedToBroadcastChannel = false
@@ -191,27 +201,27 @@ export class HocuspocusProvider extends EventEmitter {
191
201
  reject: (reason?: any) => void
192
202
  } | null = null
193
203
 
194
- constructor(options: Partial<HocuspocusProviderOptions> = {}) {
204
+ constructor(configuration: HocuspocusProviderConfiguration) {
195
205
  super()
196
- this.setOptions(options)
197
-
198
- this.options.document = options.document ? options.document : new Y.Doc()
199
- this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document)
200
- this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket
201
-
202
- this.on('open', this.options.onOpen)
203
- this.on('authenticated', this.options.onAuthenticated)
204
- this.on('authenticationFailed', this.options.onAuthenticationFailed)
205
- this.on('connect', this.options.onConnect)
206
- this.on('message', this.options.onMessage)
207
- this.on('outgoingMessage', this.options.onOutgoingMessage)
208
- this.on('synced', this.options.onSynced)
209
- this.on('status', this.options.onStatus)
210
- this.on('disconnect', this.options.onDisconnect)
211
- this.on('close', this.options.onClose)
212
- this.on('destroy', this.options.onDestroy)
213
- this.on('awarenessUpdate', this.options.onAwarenessUpdate)
214
- this.on('awarenessChange', this.options.onAwarenessChange)
206
+ this.setConfiguration(configuration)
207
+
208
+ this.configuration.document = configuration.document ? configuration.document : new Y.Doc()
209
+ this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document)
210
+ this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket
211
+
212
+ this.on('open', this.configuration.onOpen)
213
+ this.on('authenticated', this.configuration.onAuthenticated)
214
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed)
215
+ this.on('connect', this.configuration.onConnect)
216
+ this.on('message', this.configuration.onMessage)
217
+ this.on('outgoingMessage', this.configuration.onOutgoingMessage)
218
+ this.on('synced', this.configuration.onSynced)
219
+ this.on('status', this.configuration.onStatus)
220
+ this.on('disconnect', this.configuration.onDisconnect)
221
+ this.on('close', this.configuration.onClose)
222
+ this.on('destroy', this.configuration.onDestroy)
223
+ this.on('awarenessUpdate', this.configuration.onAwarenessUpdate)
224
+ this.on('awarenessChange', this.configuration.onAwarenessChange)
215
225
 
216
226
  this.awareness.on('update', () => {
217
227
  this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) })
@@ -227,18 +237,18 @@ export class HocuspocusProvider extends EventEmitter {
227
237
 
228
238
  this.intervals.connectionChecker = setInterval(
229
239
  this.checkConnection.bind(this),
230
- this.options.messageReconnectTimeout / 10,
240
+ this.configuration.messageReconnectTimeout / 10,
231
241
  )
232
242
 
233
- if (this.options.forceSyncInterval) {
243
+ if (this.configuration.forceSyncInterval) {
234
244
  this.intervals.forceSync = setInterval(
235
245
  this.forceSync.bind(this),
236
- this.options.forceSyncInterval,
246
+ this.configuration.forceSyncInterval,
237
247
  )
238
248
  }
239
249
 
240
- if (typeof options.connect !== 'undefined') {
241
- this.shouldConnect = options.connect
250
+ if (typeof configuration.connect !== 'undefined') {
251
+ this.shouldConnect = configuration.connect
242
252
  }
243
253
 
244
254
  if (!this.shouldConnect) {
@@ -248,8 +258,8 @@ export class HocuspocusProvider extends EventEmitter {
248
258
  this.connect()
249
259
  }
250
260
 
251
- public setOptions(options: Partial<HocuspocusProviderOptions> = {}): void {
252
- this.options = { ...this.options, ...options }
261
+ public setConfiguration(configuration: Partial<HocuspocusProviderConfiguration> = {}): void {
262
+ this.configuration = { ...this.configuration, ...configuration }
253
263
  }
254
264
 
255
265
  async connect() {
@@ -262,14 +272,14 @@ export class HocuspocusProvider extends EventEmitter {
262
272
 
263
273
  try {
264
274
  await retry(this.createWebSocketConnection.bind(this), {
265
- delay: this.options.delay,
266
- initialDelay: this.options.initialDelay,
267
- factor: this.options.factor,
268
- maxAttempts: this.options.maxAttempts,
269
- minDelay: this.options.minDelay,
270
- maxDelay: this.options.maxDelay,
271
- jitter: this.options.jitter,
272
- timeout: this.options.timeout,
275
+ delay: this.configuration.delay,
276
+ initialDelay: this.configuration.initialDelay,
277
+ factor: this.configuration.factor,
278
+ maxAttempts: this.configuration.maxAttempts,
279
+ minDelay: this.configuration.minDelay,
280
+ maxDelay: this.configuration.maxDelay,
281
+ jitter: this.configuration.jitter,
282
+ timeout: this.configuration.timeout,
273
283
  beforeAttempt: context => {
274
284
  if (!this.shouldConnect) {
275
285
  context.abort()
@@ -288,7 +298,7 @@ export class HocuspocusProvider extends EventEmitter {
288
298
  createWebSocketConnection() {
289
299
  return new Promise((resolve, reject) => {
290
300
  // Init the WebSocket connection
291
- const ws = new this.options.WebSocketPolyfill(this.url)
301
+ const ws = new this.configuration.WebSocketPolyfill(this.url)
292
302
  ws.binaryType = 'arraybuffer'
293
303
  ws.onmessage = this.onMessage.bind(this)
294
304
  ws.onclose = this.onClose.bind(this)
@@ -322,11 +332,11 @@ export class HocuspocusProvider extends EventEmitter {
322
332
  }
323
333
 
324
334
  get document() {
325
- return this.options.document
335
+ return this.configuration.document
326
336
  }
327
337
 
328
338
  get awareness() {
329
- return this.options.awareness
339
+ return this.configuration.awareness
330
340
  }
331
341
 
332
342
  checkConnection() {
@@ -341,7 +351,7 @@ export class HocuspocusProvider extends EventEmitter {
341
351
  }
342
352
 
343
353
  // Don’t close the connection when a message was received recently
344
- if (this.options.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
354
+ if (this.configuration.messageReconnectTimeout >= time.getUnixTime() - this.lastMessageReceived) {
345
355
  return
346
356
  }
347
357
 
@@ -401,17 +411,17 @@ export class HocuspocusProvider extends EventEmitter {
401
411
 
402
412
  // Ensure that the URL always ends with /
403
413
  get serverUrl() {
404
- while (this.options.url[this.options.url.length - 1] === '/') {
405
- return this.options.url.slice(0, this.options.url.length - 1)
414
+ while (this.configuration.url[this.configuration.url.length - 1] === '/') {
415
+ return this.configuration.url.slice(0, this.configuration.url.length - 1)
406
416
  }
407
417
 
408
- return this.options.url
418
+ return this.configuration.url
409
419
  }
410
420
 
411
421
  get url() {
412
- const encodedParams = url.encodeQueryParams(this.options.parameters)
422
+ const encodedParams = url.encodeQueryParams(this.configuration.parameters)
413
423
 
414
- return `${this.serverUrl}/${this.options.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
424
+ return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`
415
425
  }
416
426
 
417
427
  get synced(): boolean {
@@ -429,7 +439,7 @@ export class HocuspocusProvider extends EventEmitter {
429
439
  }
430
440
 
431
441
  get isAuthenticationRequired(): boolean {
432
- return !!this.options.token && !this.isAuthenticated
442
+ return !!this.configuration.token && !this.isAuthenticated
433
443
  }
434
444
 
435
445
  disconnect() {
@@ -456,12 +466,12 @@ export class HocuspocusProvider extends EventEmitter {
456
466
  }
457
467
 
458
468
  async getToken() {
459
- if (typeof this.options.token === 'function') {
460
- const token = await this.options.token()
469
+ if (typeof this.configuration.token === 'function') {
470
+ const token = await this.configuration.token()
461
471
  return token
462
472
  }
463
473
 
464
- return this.options.token
474
+ return this.configuration.token
465
475
  }
466
476
 
467
477
  async webSocketConnectionEstablished() {
@@ -535,15 +545,29 @@ export class HocuspocusProvider extends EventEmitter {
535
545
  this.emit('disconnect', { event })
536
546
  }
537
547
 
548
+ if (event.code === Unauthorized.code) {
549
+ if (!this.configuration.quiet) {
550
+ 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.')
551
+ }
552
+
553
+ this.shouldConnect = false
554
+ }
555
+
556
+ if (event.code === Forbidden.code) {
557
+ if (!this.configuration.quiet) {
558
+ console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.')
559
+ }
560
+ }
561
+
538
562
  if (this.connectionAttempt) {
539
- // Okay, that connection attempt failed
563
+ // That connection attempt failed.
540
564
  this.rejectConnectionAttempt()
541
565
  } else if (this.shouldConnect) {
542
- // The connection was closed by the server, so let’s just try again.
566
+ // The connection was closed by the server. Let’s just try again.
543
567
  this.connect()
544
568
  }
545
569
 
546
- // If we’ll reconnect anyway, we’re done for now.
570
+ // If we’ll reconnect, we’re done for now.
547
571
  if (this.shouldConnect) {
548
572
  return
549
573
  }
@@ -590,7 +614,7 @@ export class HocuspocusProvider extends EventEmitter {
590
614
  }
591
615
 
592
616
  get broadcastChannel() {
593
- return `${this.serverUrl}/${this.options.name}`
617
+ return `${this.serverUrl}/${this.configuration.name}`
594
618
  }
595
619
 
596
620
  broadcastChannelSubscriber(data: ArrayBuffer) {
@@ -631,7 +655,7 @@ export class HocuspocusProvider extends EventEmitter {
631
655
  }
632
656
 
633
657
  broadcast(Message: ConstructableOutgoingMessage, args?: any) {
634
- if (!this.options.broadcast) {
658
+ if (!this.configuration.broadcast) {
635
659
  return
636
660
  }
637
661
 
@@ -1,4 +0,0 @@
1
- import { CloseEvent } from './types';
2
- export declare const Forbidden: CloseEvent;
3
- export declare const ResetConnection: CloseEvent;
4
- export declare const CloseEvents: CloseEvent[];