@hocuspocus/provider 1.0.0-alpha.9 → 1.0.0-beta.3

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.
Files changed (134) hide show
  1. package/dist/hocuspocus-provider.cjs +374 -186
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +372 -182
  4. package/dist/hocuspocus-provider.esm.js.map +1 -1
  5. package/dist/packages/common/src/CloseEvents.d.ts +23 -0
  6. package/dist/packages/common/src/auth.d.ts +6 -0
  7. package/dist/packages/common/src/awarenessStatesToArray.d.ts +3 -0
  8. package/dist/packages/common/src/index.d.ts +4 -0
  9. package/dist/packages/common/src/types.d.ts +10 -0
  10. package/dist/packages/extension-database/src/Database.d.ts +30 -0
  11. package/dist/packages/extension-database/src/index.d.ts +1 -0
  12. package/dist/packages/extension-logger/src/Logger.d.ts +67 -0
  13. package/dist/packages/extension-logger/src/index.d.ts +1 -0
  14. package/dist/packages/{monitor → extension-monitor}/src/Collector.d.ts +4 -5
  15. package/dist/packages/{monitor → extension-monitor}/src/Dashboard.d.ts +2 -2
  16. package/dist/packages/{monitor → extension-monitor}/src/Storage.d.ts +0 -0
  17. package/dist/packages/{monitor → extension-monitor}/src/index.d.ts +3 -5
  18. package/dist/packages/extension-redis/src/Redis.d.ts +98 -0
  19. package/dist/packages/extension-redis/src/index.d.ts +1 -0
  20. package/dist/packages/extension-sqlite/src/SQLite.d.ts +26 -0
  21. package/dist/packages/extension-sqlite/src/index.d.ts +1 -0
  22. package/dist/packages/extension-throttle/src/index.d.ts +24 -0
  23. package/dist/packages/{webhook → extension-webhook}/src/index.d.ts +5 -11
  24. package/dist/packages/provider/src/EventEmitter.d.ts +1 -1
  25. package/dist/packages/provider/src/HocuspocusCloudProvider.d.ts +11 -0
  26. package/dist/packages/provider/src/HocuspocusProvider.d.ts +116 -33
  27. package/dist/packages/provider/src/IncomingMessage.d.ts +9 -6
  28. package/dist/packages/provider/src/MessageReceiver.d.ts +3 -2
  29. package/dist/packages/provider/src/MessageSender.d.ts +4 -9
  30. package/dist/packages/provider/src/OutgoingMessage.d.ts +5 -4
  31. package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +7 -0
  32. package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +1 -2
  33. package/dist/packages/provider/src/index.d.ts +1 -1
  34. package/dist/packages/provider/src/types.d.ts +54 -2
  35. package/dist/packages/server/src/Connection.d.ts +20 -7
  36. package/dist/packages/server/src/Debugger.d.ts +14 -0
  37. package/dist/packages/server/src/Document.d.ts +11 -5
  38. package/dist/packages/server/src/Hocuspocus.d.ts +63 -17
  39. package/dist/packages/server/src/IncomingMessage.d.ts +11 -7
  40. package/dist/packages/server/src/MessageReceiver.d.ts +13 -0
  41. package/dist/packages/server/src/OutgoingMessage.d.ts +6 -0
  42. package/dist/packages/server/src/index.d.ts +6 -0
  43. package/dist/packages/server/src/types.d.ts +180 -26
  44. package/dist/{demos/backend/src/create-document.d.ts → playground/backend/src/default.d.ts} +0 -0
  45. package/dist/{demos → playground}/backend/src/express.d.ts +0 -0
  46. package/dist/{demos → playground}/backend/src/koa.d.ts +0 -0
  47. package/dist/{demos/backend/src/minimal.d.ts → playground/backend/src/load-document.d.ts} +0 -0
  48. package/dist/{demos → playground}/backend/src/monitor.d.ts +0 -0
  49. package/dist/{demos → playground}/backend/src/redis.d.ts +0 -0
  50. package/dist/{demos → playground}/backend/src/slow.d.ts +0 -0
  51. package/dist/{demos → playground}/backend/src/webhook.d.ts +0 -0
  52. package/dist/tests/extension-database/fetch.d.ts +1 -0
  53. package/dist/tests/extension-logger/onListen.d.ts +1 -0
  54. package/dist/tests/extension-redis/closeConnections.d.ts +1 -0
  55. package/dist/tests/extension-redis/getConnectionCount.d.ts +1 -0
  56. package/dist/tests/extension-redis/getDocumentsCount.d.ts +1 -0
  57. package/dist/tests/extension-redis/onAwarenessChange.d.ts +1 -0
  58. package/dist/tests/extension-redis/onChange.d.ts +1 -0
  59. package/dist/tests/extension-redis/onStoreDocument.d.ts +1 -0
  60. package/dist/tests/extension-throttle/configuration.d.ts +1 -0
  61. package/dist/tests/provider/configuration.d.ts +1 -0
  62. package/dist/tests/provider/observe.d.ts +1 -0
  63. package/dist/tests/provider/observeDeep.d.ts +1 -0
  64. package/dist/tests/provider/onAuthenticated.d.ts +1 -0
  65. package/dist/tests/provider/onAuthenticationFailed.d.ts +1 -0
  66. package/dist/tests/provider/onAwarenessChange.d.ts +1 -0
  67. package/dist/tests/provider/onAwarenessUpdate.d.ts +1 -0
  68. package/dist/tests/provider/onClose.d.ts +1 -0
  69. package/dist/tests/provider/onConnect.d.ts +1 -0
  70. package/dist/tests/provider/onDisconnect.d.ts +1 -0
  71. package/dist/tests/provider/onMessage.d.ts +1 -0
  72. package/dist/tests/provider/onOpen.d.ts +1 -0
  73. package/dist/tests/provider/onSynced.d.ts +1 -0
  74. package/dist/tests/server/address.d.ts +1 -0
  75. package/dist/tests/server/afterStoreDocument.d.ts +1 -0
  76. package/dist/tests/server/beforeHandleMessage.d.ts +1 -0
  77. package/dist/tests/server/closeConnections.d.ts +1 -0
  78. package/dist/tests/server/getConnectionsCount.d.ts +1 -0
  79. package/dist/tests/server/getDocumentName.d.ts +1 -0
  80. package/dist/tests/server/getDocumentsCount.d.ts +1 -0
  81. package/dist/tests/server/getMessageLogs.d.ts +1 -0
  82. package/dist/tests/server/listen.d.ts +1 -0
  83. package/dist/tests/server/onAuthenticate.d.ts +1 -0
  84. package/dist/tests/server/onAwarenessUpdate.d.ts +1 -0
  85. package/dist/tests/server/onChange.d.ts +1 -0
  86. package/dist/tests/server/onConfigure.d.ts +1 -0
  87. package/dist/tests/server/onConnect.d.ts +1 -0
  88. package/dist/tests/server/onDestroy.d.ts +1 -0
  89. package/dist/tests/server/onDisconnect.d.ts +1 -0
  90. package/dist/tests/server/onListen.d.ts +1 -0
  91. package/dist/tests/server/onLoadDocument.d.ts +1 -0
  92. package/dist/tests/server/onRequest.d.ts +1 -0
  93. package/dist/tests/server/onStoreDocument.d.ts +1 -0
  94. package/dist/tests/server/onUpgrade.d.ts +1 -0
  95. package/dist/tests/server/requiresAuthentication.d.ts +1 -0
  96. package/dist/tests/server/websocketError.d.ts +1 -0
  97. package/dist/tests/transformer/TiptapTransformer.d.ts +1 -0
  98. package/dist/tests/utils/createDirectory.d.ts +1 -0
  99. package/dist/tests/utils/flushRedis.d.ts +1 -0
  100. package/dist/tests/utils/index.d.ts +8 -0
  101. package/dist/tests/utils/newHocuspocus.d.ts +2 -0
  102. package/dist/tests/utils/newHocuspocusProvider.d.ts +3 -0
  103. package/dist/tests/utils/randomInteger.d.ts +1 -0
  104. package/dist/tests/utils/redisConnectionSettings.d.ts +4 -0
  105. package/dist/tests/utils/removeDirectory.d.ts +1 -0
  106. package/dist/tests/utils/retryableAssertion.d.ts +2 -0
  107. package/dist/tests/utils/sleep.d.ts +1 -0
  108. package/package.json +16 -10
  109. package/src/EventEmitter.ts +1 -1
  110. package/src/HocuspocusCloudProvider.ts +34 -0
  111. package/src/HocuspocusProvider.ts +410 -162
  112. package/src/IncomingMessage.ts +35 -11
  113. package/src/MessageReceiver.ts +56 -24
  114. package/src/MessageSender.ts +5 -17
  115. package/src/OutgoingMessage.ts +9 -9
  116. package/src/OutgoingMessages/AuthenticationMessage.ts +21 -0
  117. package/src/OutgoingMessages/AwarenessMessage.ts +1 -1
  118. package/src/OutgoingMessages/SyncStepOneMessage.ts +0 -1
  119. package/src/OutgoingMessages/UpdateMessage.ts +4 -4
  120. package/src/index.ts +1 -1
  121. package/src/types.ts +70 -3
  122. package/CHANGELOG.md +0 -64
  123. package/LICENSE.md +0 -21
  124. package/dist/packages/logger/src/index.d.ts +0 -13
  125. package/dist/packages/provider/src/utils/awarenessStatesToArray.d.ts +0 -4
  126. package/dist/packages/provider/src/utils/index.d.ts +0 -1
  127. package/dist/packages/redis/src/Redis.d.ts +0 -22
  128. package/dist/packages/redis/src/RedisCluster.d.ts +0 -4
  129. package/dist/packages/redis/src/index.d.ts +0 -2
  130. package/dist/packages/rocksdb/src/index.d.ts +0 -30
  131. package/dist/packages/server/src/CloseEvents.d.ts +0 -3
  132. package/dist/packages/throttle/src/index.d.ts +0 -28
  133. package/src/utils/awarenessStatesToArray.ts +0 -8
  134. package/src/utils/index.ts +0 -1
@@ -1,4 +1,6 @@
1
1
  import * as Y from 'yjs';
2
+ import { retry } from '@lifeomic/attempt';
3
+ import { readAuthMessage, writeAuthentication, awarenessStatesToArray, WsReadyStates, Unauthorized, Forbidden } from '@hocuspocus/common';
2
4
 
3
5
  /**
4
6
  * Utility module to work with key-value stores.
@@ -258,8 +260,6 @@ hasConf('production');
258
260
  */
259
261
 
260
262
  const floor = Math.floor;
261
- const round = Math.round;
262
- const log10 = Math.log10;
263
263
 
264
264
  /**
265
265
  * @function
@@ -1372,23 +1372,26 @@ class EventEmitter {
1372
1372
  }
1373
1373
  }
1374
1374
 
1375
- var MessageType;
1376
- (function (MessageType) {
1377
- MessageType[MessageType["Sync"] = 0] = "Sync";
1378
- MessageType[MessageType["Awareness"] = 1] = "Awareness";
1379
- MessageType[MessageType["Auth"] = 2] = "Auth";
1380
- MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1381
- })(MessageType || (MessageType = {}));
1382
-
1383
1375
  class IncomingMessage {
1384
1376
  constructor(data) {
1385
1377
  this.data = data;
1386
1378
  this.encoder = createEncoder();
1387
1379
  this.decoder = createDecoder(new Uint8Array(this.data));
1388
- this.type = readVarUint(this.decoder);
1389
1380
  }
1390
- get name() {
1391
- return MessageType[this.type];
1381
+ readVarUint() {
1382
+ return readVarUint(this.decoder);
1383
+ }
1384
+ readVarUint8Array() {
1385
+ return readVarUint8Array(this.decoder);
1386
+ }
1387
+ writeVarUint(type) {
1388
+ return writeVarUint(this.encoder, type);
1389
+ }
1390
+ writeVarUint8Array(data) {
1391
+ return writeVarUint8Array(this.encoder, data);
1392
+ }
1393
+ length() {
1394
+ return length$1(this.encoder);
1392
1395
  }
1393
1396
  }
1394
1397
 
@@ -1519,32 +1522,45 @@ const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
1519
1522
  return messageType
1520
1523
  };
1521
1524
 
1522
- const messagePermissionDenied = 0;
1523
-
1524
- /**
1525
- * @callback PermissionDeniedHandler
1526
- * @param {any} y
1527
- * @param {string} reason
1528
- */
1525
+ var MessageType;
1526
+ (function (MessageType) {
1527
+ MessageType[MessageType["Sync"] = 0] = "Sync";
1528
+ MessageType[MessageType["Awareness"] = 1] = "Awareness";
1529
+ MessageType[MessageType["Auth"] = 2] = "Auth";
1530
+ MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1531
+ })(MessageType || (MessageType = {}));
1532
+ var WebSocketStatus;
1533
+ (function (WebSocketStatus) {
1534
+ WebSocketStatus["Connecting"] = "connecting";
1535
+ WebSocketStatus["Connected"] = "connected";
1536
+ WebSocketStatus["Disconnected"] = "disconnected";
1537
+ })(WebSocketStatus || (WebSocketStatus = {}));
1529
1538
 
1530
- /**
1531
- *
1532
- * @param {decoding.Decoder} decoder
1533
- * @param {Y.Doc} y
1534
- * @param {PermissionDeniedHandler} permissionDeniedHandler
1535
- */
1536
- const readAuthMessage = (decoder, y, permissionDeniedHandler) => {
1537
- switch (readVarUint(decoder)) {
1538
- case messagePermissionDenied: permissionDeniedHandler(y, readVarString(decoder));
1539
- }
1540
- };
1539
+ class OutgoingMessage {
1540
+ constructor() {
1541
+ this.encoder = createEncoder();
1542
+ }
1543
+ get(args) {
1544
+ return args.encoder;
1545
+ }
1546
+ toUint8Array() {
1547
+ return toUint8Array(this.encoder);
1548
+ }
1549
+ }
1541
1550
 
1542
1551
  class MessageReceiver {
1543
1552
  constructor(message) {
1553
+ this.broadcasted = false;
1544
1554
  this.message = message;
1545
1555
  }
1556
+ setBroadcasted(value) {
1557
+ this.broadcasted = value;
1558
+ return this;
1559
+ }
1546
1560
  apply(provider, emitSynced = true) {
1547
- switch (this.message.type) {
1561
+ const { message } = this;
1562
+ const type = message.readVarUint();
1563
+ switch (type) {
1548
1564
  case MessageType.Sync:
1549
1565
  this.applySyncMessage(provider, emitSynced);
1550
1566
  break;
@@ -1558,31 +1574,49 @@ class MessageReceiver {
1558
1574
  this.applyQueryAwarenessMessage(provider);
1559
1575
  break;
1560
1576
  default:
1561
- throw new Error(`Can’t apply unknown type of message: ${this.message.type}`);
1577
+ throw new Error(`Can’t apply message of unknown type: ${type}`);
1578
+ }
1579
+ // Reply
1580
+ if (message.length() > 1) {
1581
+ if (this.broadcasted) {
1582
+ // TODO: Some weird TypeScript error
1583
+ // @ts-ignore
1584
+ provider.broadcast(OutgoingMessage, { encoder: message.encoder });
1585
+ }
1586
+ else {
1587
+ // TODO: Some weird TypeScript error
1588
+ // @ts-ignore
1589
+ provider.send(OutgoingMessage, { encoder: message.encoder });
1590
+ }
1562
1591
  }
1563
- return this.message.encoder;
1564
1592
  }
1565
1593
  applySyncMessage(provider, emitSynced) {
1566
- writeVarUint(this.message.encoder, MessageType.Sync);
1567
- const syncMessageType = readSyncMessage(this.message.decoder, this.message.encoder, provider.document, provider);
1568
- if (emitSynced && syncMessageType === messageYjsSyncStep2) {
1594
+ const { message } = this;
1595
+ message.writeVarUint(MessageType.Sync);
1596
+ // Apply update
1597
+ const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
1598
+ // Synced once we receive Step2
1599
+ if (emitSynced && (syncMessageType === messageYjsSyncStep2)) {
1569
1600
  provider.synced = true;
1570
1601
  }
1602
+ if (syncMessageType === messageYjsUpdate || syncMessageType === messageYjsSyncStep2) {
1603
+ if (provider.unsyncedChanges > 0) {
1604
+ provider.unsyncedChanges -= 1;
1605
+ }
1606
+ }
1571
1607
  }
1572
1608
  applyAwarenessMessage(provider) {
1573
- applyAwarenessUpdate(provider.awareness, readVarUint8Array(this.message.decoder), provider);
1609
+ const { message } = this;
1610
+ applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
1574
1611
  }
1575
- // TODO: This isn’t really used. Needs to be implemented in the server, or removed here.
1576
1612
  applyAuthMessage(provider) {
1577
- readAuthMessage(this.message.decoder, provider.document,
1578
- // TODO: Add a configureable hook
1579
- (provider, reason) => {
1580
- console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
1581
- });
1613
+ const { message } = this;
1614
+ readAuthMessage(message.decoder, provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
1582
1615
  }
1583
1616
  applyQueryAwarenessMessage(provider) {
1584
- writeVarUint(this.message.encoder, MessageType.Awareness);
1585
- writeVarUint8Array(this.message.encoder, encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
1617
+ const { message } = this;
1618
+ message.writeVarUint(MessageType.Awareness);
1619
+ message.writeVarUint8Array(encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
1586
1620
  }
1587
1621
  }
1588
1622
 
@@ -1602,18 +1636,6 @@ class MessageSender {
1602
1636
  }
1603
1637
  }
1604
1638
 
1605
- class OutgoingMessage {
1606
- constructor() {
1607
- this.encoder = createEncoder();
1608
- }
1609
- get name() {
1610
- if (typeof this.type === 'number') {
1611
- return MessageType[this.type];
1612
- }
1613
- throw new Error('Type for outgoing message not set.');
1614
- }
1615
- }
1616
-
1617
1639
  class SyncStepOneMessage extends OutgoingMessage {
1618
1640
  constructor() {
1619
1641
  super(...arguments);
@@ -1658,6 +1680,22 @@ class QueryAwarenessMessage extends OutgoingMessage {
1658
1680
  }
1659
1681
  }
1660
1682
 
1683
+ class AuthenticationMessage extends OutgoingMessage {
1684
+ constructor() {
1685
+ super(...arguments);
1686
+ this.type = MessageType.Auth;
1687
+ this.description = 'Authentication';
1688
+ }
1689
+ get(args) {
1690
+ if (typeof args.token === 'undefined') {
1691
+ throw new Error('The authentication message requires `token` as an argument.');
1692
+ }
1693
+ writeVarUint(this.encoder, this.type);
1694
+ writeAuthentication(this.encoder, args.token);
1695
+ return this.encoder;
1696
+ }
1697
+ }
1698
+
1661
1699
  class AwarenessMessage extends OutgoingMessage {
1662
1700
  constructor() {
1663
1701
  super(...arguments);
@@ -1697,37 +1735,42 @@ class UpdateMessage extends OutgoingMessage {
1697
1735
  }
1698
1736
  }
1699
1737
 
1700
- var awarenessStatesToArray = (states) => {
1701
- return Array.from(states.entries()).map(([key, value]) => {
1702
- return {
1703
- clientId: key,
1704
- ...value,
1705
- };
1706
- });
1707
- };
1708
-
1709
- // @ts-nocheck
1710
- var WebSocketStatus;
1711
- (function (WebSocketStatus) {
1712
- WebSocketStatus["Connecting"] = "connecting";
1713
- WebSocketStatus["Connected"] = "connected";
1714
- WebSocketStatus["Disconnected"] = "disconnected";
1715
- })(WebSocketStatus || (WebSocketStatus = {}));
1716
1738
  class HocuspocusProvider extends EventEmitter {
1717
- constructor(options = {}) {
1739
+ constructor(configuration) {
1718
1740
  super();
1719
- this.options = {
1720
- url: '',
1741
+ this.configuration = {
1721
1742
  name: '',
1743
+ url: '',
1744
+ // @ts-ignore
1745
+ document: undefined,
1746
+ // @ts-ignore
1747
+ awareness: undefined,
1748
+ WebSocketPolyfill: undefined,
1749
+ token: null,
1722
1750
  parameters: {},
1723
- debug: false,
1724
1751
  connect: true,
1725
1752
  broadcast: true,
1726
1753
  forceSyncInterval: false,
1727
- reconnectTimeoutBase: 1200,
1728
- maxReconnectTimeout: 2500,
1729
1754
  // TODO: this should depend on awareness.outdatedTime
1730
1755
  messageReconnectTimeout: 30000,
1756
+ // 1 second
1757
+ delay: 1000,
1758
+ // instant
1759
+ initialDelay: 0,
1760
+ // double the delay each time
1761
+ factor: 2,
1762
+ // unlimited retries
1763
+ maxAttempts: 0,
1764
+ // wait at least 1 second
1765
+ minDelay: 1000,
1766
+ // at least every 30 seconds
1767
+ maxDelay: 30000,
1768
+ // randomize
1769
+ jitter: true,
1770
+ // retry forever
1771
+ timeout: 0,
1772
+ onAuthenticated: () => null,
1773
+ onAuthenticationFailed: () => null,
1731
1774
  onOpen: () => null,
1732
1775
  onConnect: () => null,
1733
1776
  onMessage: () => null,
@@ -1737,70 +1780,183 @@ class HocuspocusProvider extends EventEmitter {
1737
1780
  onDisconnect: () => null,
1738
1781
  onClose: () => null,
1739
1782
  onDestroy: () => null,
1783
+ onAwarenessUpdate: () => null,
1740
1784
  onAwarenessChange: () => null,
1785
+ quiet: false,
1741
1786
  };
1742
1787
  this.subscribedToBroadcastChannel = false;
1743
1788
  this.webSocket = null;
1744
1789
  this.shouldConnect = true;
1745
1790
  this.status = WebSocketStatus.Disconnected;
1746
- this.failedConnectionAttempts = 0;
1747
1791
  this.isSynced = false;
1792
+ this.unsyncedChanges = 0;
1793
+ this.isAuthenticated = false;
1748
1794
  this.lastMessageReceived = 0;
1749
1795
  this.mux = createMutex();
1750
1796
  this.intervals = {
1751
1797
  forceSync: null,
1752
1798
  connectionChecker: null,
1753
1799
  };
1754
- this.setOptions(options);
1755
- this.options.document = options.document ? options.document : new Y.Doc();
1756
- this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
1757
- this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
1758
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
1759
- this.on('open', this.options.onOpen);
1760
- this.on('connect', this.options.onConnect);
1761
- this.on('message', this.options.onMessage);
1762
- this.on('outgoingMessage', this.options.onOutgoingMessage);
1763
- this.on('synced', this.options.onSynced);
1764
- this.on('status', this.options.onStatus);
1765
- this.on('disconnect', this.options.onDisconnect);
1766
- this.on('close', this.options.onClose);
1767
- this.on('destroy', this.options.onDestroy);
1768
- this.on('awarenessChange', this.options.onAwarenessChange);
1800
+ this.connectionAttempt = null;
1801
+ this.boundConnect = this.connect.bind(this);
1802
+ this.boundBeforeUnload = this.beforeUnload.bind(this);
1803
+ this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
1804
+ this.setConfiguration(configuration);
1805
+ this.configuration.document = configuration.document ? configuration.document : new Y.Doc();
1806
+ this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document);
1807
+ this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket;
1808
+ this.on('open', this.configuration.onOpen);
1809
+ this.on('authenticated', this.configuration.onAuthenticated);
1810
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
1811
+ this.on('connect', this.configuration.onConnect);
1812
+ this.on('message', this.configuration.onMessage);
1813
+ this.on('outgoingMessage', this.configuration.onOutgoingMessage);
1814
+ this.on('synced', this.configuration.onSynced);
1815
+ this.on('status', this.configuration.onStatus);
1816
+ this.on('disconnect', this.configuration.onDisconnect);
1817
+ this.on('close', this.configuration.onClose);
1818
+ this.on('destroy', this.configuration.onDestroy);
1819
+ this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
1820
+ this.on('awarenessChange', this.configuration.onAwarenessChange);
1821
+ this.awareness.on('update', () => {
1822
+ this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) });
1823
+ });
1769
1824
  this.awareness.on('change', () => {
1770
- this.emit('awarenessChange', {
1771
- states: awarenessStatesToArray(this.awareness.getStates()),
1772
- });
1825
+ this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
1773
1826
  });
1774
- this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1775
1827
  this.document.on('update', this.documentUpdateHandler.bind(this));
1776
1828
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1777
- this.registerBeforeUnloadEventListener();
1778
- if (this.options.forceSyncInterval) {
1779
- this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.options.forceSyncInterval);
1829
+ this.registerEventListeners();
1830
+ this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
1831
+ if (this.configuration.forceSyncInterval) {
1832
+ this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
1780
1833
  }
1781
- if (this.options.connect) {
1782
- this.connect();
1834
+ if (typeof configuration.connect !== 'undefined') {
1835
+ this.shouldConnect = configuration.connect;
1836
+ }
1837
+ if (!this.shouldConnect) {
1838
+ return;
1783
1839
  }
1840
+ this.connect();
1784
1841
  }
1785
- setOptions(options = {}) {
1786
- this.options = { ...this.options, ...options };
1842
+ setConfiguration(configuration = {}) {
1843
+ this.configuration = { ...this.configuration, ...configuration };
1844
+ }
1845
+ async connect() {
1846
+ if (this.status === WebSocketStatus.Connected) {
1847
+ return;
1848
+ }
1849
+ // Always cancel any previously initiated connection retryer instances
1850
+ if (this.cancelWebsocketRetry) {
1851
+ this.cancelWebsocketRetry();
1852
+ this.cancelWebsocketRetry = undefined;
1853
+ }
1854
+ this.unsyncedChanges = 0; // set to 0 in case we got reconnected
1855
+ this.shouldConnect = true;
1856
+ this.subscribeToBroadcastChannel();
1857
+ const abortableRetry = () => {
1858
+ let cancelAttempt = false;
1859
+ const retryPromise = retry(this.createWebSocketConnection.bind(this), {
1860
+ delay: this.configuration.delay,
1861
+ initialDelay: this.configuration.initialDelay,
1862
+ factor: this.configuration.factor,
1863
+ maxAttempts: this.configuration.maxAttempts,
1864
+ minDelay: this.configuration.minDelay,
1865
+ maxDelay: this.configuration.maxDelay,
1866
+ jitter: this.configuration.jitter,
1867
+ timeout: this.configuration.timeout,
1868
+ beforeAttempt: context => {
1869
+ if (!this.shouldConnect || cancelAttempt) {
1870
+ context.abort();
1871
+ }
1872
+ },
1873
+ }).catch((error) => {
1874
+ // If we aborted the connection attempt then don’t throw an error
1875
+ // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
1876
+ if (error && error.code !== 'ATTEMPT_ABORTED') {
1877
+ throw error;
1878
+ }
1879
+ });
1880
+ return {
1881
+ retryPromise,
1882
+ cancelFunc: () => {
1883
+ cancelAttempt = true;
1884
+ },
1885
+ };
1886
+ };
1887
+ const { retryPromise, cancelFunc } = abortableRetry();
1888
+ this.cancelWebsocketRetry = cancelFunc;
1889
+ return retryPromise;
1890
+ }
1891
+ createWebSocketConnection() {
1892
+ return new Promise((resolve, reject) => {
1893
+ if (this.webSocket) {
1894
+ this.webSocket.close();
1895
+ this.webSocket = null;
1896
+ }
1897
+ // Init the WebSocket connection
1898
+ const ws = new this.configuration.WebSocketPolyfill(this.url);
1899
+ ws.binaryType = 'arraybuffer';
1900
+ ws.onmessage = this.onMessage.bind(this);
1901
+ ws.onclose = this.onClose.bind(this);
1902
+ ws.onopen = this.onOpen.bind(this);
1903
+ ws.onerror = (err) => {
1904
+ reject(err);
1905
+ };
1906
+ this.webSocket = ws;
1907
+ // Reset the status
1908
+ this.synced = false;
1909
+ this.status = WebSocketStatus.Connecting;
1910
+ this.emit('status', { status: WebSocketStatus.Connecting });
1911
+ // Store resolve/reject for later use
1912
+ this.connectionAttempt = {
1913
+ resolve,
1914
+ reject,
1915
+ };
1916
+ });
1917
+ }
1918
+ resolveConnectionAttempt() {
1919
+ var _a;
1920
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1921
+ this.connectionAttempt = null;
1922
+ this.status = WebSocketStatus.Connected;
1923
+ this.emit('status', { status: WebSocketStatus.Connected });
1924
+ this.emit('connect');
1925
+ }
1926
+ stopConnectionAttempt() {
1927
+ this.connectionAttempt = null;
1928
+ }
1929
+ rejectConnectionAttempt() {
1930
+ var _a;
1931
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1932
+ this.connectionAttempt = null;
1787
1933
  }
1788
1934
  get document() {
1789
- return this.options.document;
1935
+ return this.configuration.document;
1790
1936
  }
1791
1937
  get awareness() {
1792
- return this.options.awareness;
1938
+ return this.configuration.awareness;
1939
+ }
1940
+ get hasUnsyncedChanges() {
1941
+ return this.unsyncedChanges > 0;
1793
1942
  }
1794
1943
  checkConnection() {
1944
+ var _a;
1945
+ // Don’t check the connection when it’s not even established
1795
1946
  if (this.status !== WebSocketStatus.Connected) {
1796
1947
  return;
1797
1948
  }
1798
- if (this.options.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1949
+ // Don’t close then connection while waiting for the first message
1950
+ if (!this.lastMessageReceived) {
1951
+ return;
1952
+ }
1953
+ // Don’t close the connection when a message was received recently
1954
+ if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1799
1955
  return;
1800
1956
  }
1801
1957
  // No message received in a long time, not even your own
1802
1958
  // Awareness updates, which are updated every 15 seconds.
1803
- this.webSocket.close();
1959
+ (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
1804
1960
  }
1805
1961
  forceSync() {
1806
1962
  if (!this.webSocket) {
@@ -1808,18 +1964,21 @@ class HocuspocusProvider extends EventEmitter {
1808
1964
  }
1809
1965
  this.send(SyncStepOneMessage, { document: this.document });
1810
1966
  }
1811
- registerBeforeUnloadEventListener() {
1967
+ beforeUnload() {
1968
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1969
+ }
1970
+ registerEventListeners() {
1812
1971
  if (typeof window === 'undefined') {
1813
1972
  return;
1814
1973
  }
1815
- window.addEventListener('beforeunload', () => {
1816
- removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1817
- });
1974
+ window.addEventListener('online', this.boundConnect);
1975
+ window.addEventListener('beforeunload', this.boundBeforeUnload);
1818
1976
  }
1819
1977
  documentUpdateHandler(update, origin) {
1820
1978
  if (origin === this) {
1821
1979
  return;
1822
1980
  }
1981
+ this.unsyncedChanges += 1;
1823
1982
  this.send(UpdateMessage, { update }, true);
1824
1983
  }
1825
1984
  awarenessUpdateHandler({ added, updated, removed }, origin) {
@@ -1829,16 +1988,26 @@ class HocuspocusProvider extends EventEmitter {
1829
1988
  clients: changedClients,
1830
1989
  }, true);
1831
1990
  }
1991
+ permissionDeniedHandler(reason) {
1992
+ this.emit('authenticationFailed', { reason });
1993
+ this.isAuthenticated = false;
1994
+ this.shouldConnect = false;
1995
+ }
1996
+ authenticatedHandler() {
1997
+ this.isAuthenticated = true;
1998
+ this.emit('authenticated');
1999
+ this.startSync();
2000
+ }
1832
2001
  // Ensure that the URL always ends with /
1833
2002
  get serverUrl() {
1834
- while (this.options.url[this.options.url.length - 1] === '/') {
1835
- return this.options.url.slice(0, this.options.url.length - 1);
2003
+ while (this.configuration.url[this.configuration.url.length - 1] === '/') {
2004
+ return this.configuration.url.slice(0, this.configuration.url.length - 1);
1836
2005
  }
1837
- return this.options.url;
2006
+ return this.configuration.url;
1838
2007
  }
1839
2008
  get url() {
1840
- const encodedParams = encodeQueryParams(this.options.parameters);
1841
- return `${this.serverUrl}/${this.options.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
2009
+ const encodedParams = encodeQueryParams(this.configuration.parameters);
2010
+ return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
1842
2011
  }
1843
2012
  get synced() {
1844
2013
  return this.isSynced;
@@ -1851,12 +2020,8 @@ class HocuspocusProvider extends EventEmitter {
1851
2020
  this.emit('synced', { state });
1852
2021
  this.emit('sync', { state });
1853
2022
  }
1854
- connect() {
1855
- this.shouldConnect = true;
1856
- if (this.status !== WebSocketStatus.Connected) {
1857
- this.createWebSocketConnection();
1858
- this.subscribeToBroadcastChannel();
1859
- }
2023
+ get isAuthenticationRequired() {
2024
+ return !!this.configuration.token && !this.isAuthenticated;
1860
2025
  }
1861
2026
  disconnect() {
1862
2027
  this.shouldConnect = false;
@@ -1871,27 +2036,24 @@ class HocuspocusProvider extends EventEmitter {
1871
2036
  //
1872
2037
  }
1873
2038
  }
1874
- createWebSocketConnection() {
1875
- if (this.webSocket !== null) {
2039
+ async onOpen(event) {
2040
+ this.emit('open', { event });
2041
+ if (this.isAuthenticationRequired) {
2042
+ this.send(AuthenticationMessage, {
2043
+ token: await this.getToken(),
2044
+ });
1876
2045
  return;
1877
2046
  }
1878
- this.webSocket = new this.options.WebSocketPolyfill(this.url);
1879
- this.webSocket.binaryType = 'arraybuffer';
1880
- this.status = WebSocketStatus.Connecting;
1881
- this.synced = false;
1882
- this.webSocket.onmessage = this.onMessage.bind(this);
1883
- this.webSocket.onclose = this.onClose.bind(this);
1884
- this.webSocket.onopen = this.onOpen.bind(this);
1885
- this.emit('status', { status: 'connecting' });
2047
+ this.startSync();
1886
2048
  }
1887
- onOpen(event) {
1888
- this.emit('open', { event });
2049
+ async getToken() {
2050
+ if (typeof this.configuration.token === 'function') {
2051
+ const token = await this.configuration.token();
2052
+ return token;
2053
+ }
2054
+ return this.configuration.token;
1889
2055
  }
1890
- webSocketConnectionEstablished() {
1891
- this.failedConnectionAttempts = 0;
1892
- this.status = WebSocketStatus.Connected;
1893
- this.emit('status', { status: 'connected' });
1894
- this.emit('connect');
2056
+ startSync() {
1895
2057
  this.send(SyncStepOneMessage, { document: this.document });
1896
2058
  if (this.awareness.getLocalState() !== null) {
1897
2059
  this.send(AwarenessMessage, {
@@ -1901,55 +2063,66 @@ class HocuspocusProvider extends EventEmitter {
1901
2063
  }
1902
2064
  }
1903
2065
  send(Message, args, broadcast = false) {
2066
+ var _a;
1904
2067
  if (broadcast) {
1905
- this.mux(() => {
1906
- this.broadcast(Message, args);
1907
- });
2068
+ this.mux(() => { this.broadcast(Message, args); });
1908
2069
  }
1909
- if (this.status === WebSocketStatus.Connected) {
2070
+ if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === WsReadyStates.Open) {
1910
2071
  const messageSender = new MessageSender(Message, args);
1911
2072
  this.emit('outgoingMessage', { message: messageSender.message });
1912
2073
  messageSender.send(this.webSocket);
1913
2074
  }
1914
2075
  }
1915
2076
  onMessage(event) {
1916
- if (this.status !== WebSocketStatus.Connected) {
1917
- this.webSocketConnectionEstablished();
1918
- }
2077
+ this.resolveConnectionAttempt();
1919
2078
  this.lastMessageReceived = getUnixTime();
1920
2079
  const message = new IncomingMessage(event.data);
1921
2080
  this.emit('message', { event, message });
1922
- new MessageReceiver(message, this).apply(this);
1923
- // TODO: What’s that doing?
1924
- // if (encoding.length(encoder) > 1) {
1925
- // this.send(encoding.toUint8Array(encoder))
1926
- // }
2081
+ new MessageReceiver(message).apply(this);
1927
2082
  }
1928
2083
  onClose(event) {
1929
2084
  this.emit('close', { event });
1930
2085
  this.webSocket = null;
2086
+ this.isAuthenticated = false;
2087
+ this.synced = false;
1931
2088
  if (this.status === WebSocketStatus.Connected) {
1932
- this.synced = false;
1933
2089
  // update awareness (all users except local left)
1934
2090
  removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
1935
2091
  this.status = WebSocketStatus.Disconnected;
1936
- this.emit('status', { status: 'disconnected' });
2092
+ this.emit('status', { status: WebSocketStatus.Disconnected });
1937
2093
  this.emit('disconnect', { event });
1938
2094
  }
1939
- else {
1940
- this.failedConnectionAttempts += 1;
2095
+ if (event.code === Unauthorized.code) {
2096
+ if (!this.configuration.quiet) {
2097
+ 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.');
2098
+ }
2099
+ this.shouldConnect = false;
2100
+ }
2101
+ if (event.code === Forbidden.code) {
2102
+ if (!this.configuration.quiet) {
2103
+ console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2104
+ }
2105
+ }
2106
+ if (this.connectionAttempt) {
2107
+ // That connection attempt failed.
2108
+ this.rejectConnectionAttempt();
1941
2109
  }
2110
+ else if (this.shouldConnect) {
2111
+ // The connection was closed by the server. Let’s just try again.
2112
+ this.connect();
2113
+ }
2114
+ // If we’ll reconnect, we’re done for now.
1942
2115
  if (this.shouldConnect) {
1943
- const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
1944
- this.log(`[close] Reconnecting in ${wait}ms …`);
1945
- setTimeout(this.createWebSocketConnection.bind(this), wait);
1946
2116
  return;
1947
2117
  }
1948
- if (this.status !== WebSocketStatus.Disconnected) {
1949
- this.status = WebSocketStatus.Disconnected;
1950
- this.emit('status', { status: 'disconnected' });
1951
- this.emit('disconnect', { event });
2118
+ // The status is set correctly already.
2119
+ if (this.status === WebSocketStatus.Disconnected) {
2120
+ return;
1952
2121
  }
2122
+ // Let’s update the connection status.
2123
+ this.status = WebSocketStatus.Disconnected;
2124
+ this.emit('status', { status: WebSocketStatus.Disconnected });
2125
+ this.emit('disconnect', { event });
1953
2126
  }
1954
2127
  destroy() {
1955
2128
  this.emit('destroy');
@@ -1957,27 +2130,35 @@ class HocuspocusProvider extends EventEmitter {
1957
2130
  clearInterval(this.intervals.forceSync);
1958
2131
  }
1959
2132
  clearInterval(this.intervals.connectionChecker);
2133
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2134
+ // If there is still a connection attempt outstanding then we should stop
2135
+ // it before calling disconnect, otherwise it will be rejected in the onClose
2136
+ // handler and trigger a retry
2137
+ this.stopConnectionAttempt();
1960
2138
  this.disconnect();
1961
2139
  this.awareness.off('update', this.awarenessUpdateHandler);
1962
2140
  this.document.off('update', this.documentUpdateHandler);
1963
2141
  this.removeAllListeners();
2142
+ if (typeof window === 'undefined') {
2143
+ return;
2144
+ }
2145
+ window.removeEventListener('online', this.boundConnect);
2146
+ window.removeEventListener('beforeunload', this.boundBeforeUnload);
1964
2147
  }
1965
2148
  get broadcastChannel() {
1966
- return `${this.serverUrl}/${this.options.name}`;
2149
+ return `${this.serverUrl}/${this.configuration.name}`;
1967
2150
  }
1968
2151
  broadcastChannelSubscriber(data) {
1969
2152
  this.mux(() => {
1970
2153
  const message = new IncomingMessage(data);
1971
- new MessageReceiver(message, this).apply(this, false);
1972
- // TODO: What’s that doing?
1973
- // if (encoding.length(encoder) > 1) {
1974
- // this.broadcast(encoding.toUint8Array(encoder))
1975
- // }
2154
+ new MessageReceiver(message)
2155
+ .setBroadcasted(true)
2156
+ .apply(this, false);
1976
2157
  });
1977
2158
  }
1978
2159
  subscribeToBroadcastChannel() {
1979
2160
  if (!this.subscribedToBroadcastChannel) {
1980
- subscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2161
+ subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
1981
2162
  this.subscribedToBroadcastChannel = true;
1982
2163
  }
1983
2164
  this.mux(() => {
@@ -1995,12 +2176,12 @@ class HocuspocusProvider extends EventEmitter {
1995
2176
  states: new Map(),
1996
2177
  }, true);
1997
2178
  if (this.subscribedToBroadcastChannel) {
1998
- unsubscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2179
+ unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
1999
2180
  this.subscribedToBroadcastChannel = false;
2000
2181
  }
2001
2182
  }
2002
2183
  broadcast(Message, args) {
2003
- if (!this.options.broadcast) {
2184
+ if (!this.configuration.broadcast) {
2004
2185
  return;
2005
2186
  }
2006
2187
  if (!this.subscribedToBroadcastChannel) {
@@ -2008,16 +2189,25 @@ class HocuspocusProvider extends EventEmitter {
2008
2189
  }
2009
2190
  new MessageSender(Message, args).broadcast(this.broadcastChannel);
2010
2191
  }
2011
- log(message) {
2012
- if (!this.options.debug) {
2013
- return;
2014
- }
2015
- console.log(message);
2016
- }
2017
2192
  setAwarenessField(key, value) {
2018
2193
  this.awareness.setLocalStateField(key, value);
2019
2194
  }
2020
2195
  }
2021
2196
 
2022
- export { HocuspocusProvider, MessageType, WebSocketStatus, awarenessStatesToArray };
2197
+ class HocuspocusCloudProvider extends HocuspocusProvider {
2198
+ constructor(configuration) {
2199
+ if (!configuration.url) {
2200
+ configuration.url = 'wss://connect.hocuspocus.cloud';
2201
+ }
2202
+ if (configuration.key) {
2203
+ if (!configuration.parameters) {
2204
+ configuration.parameters = {};
2205
+ }
2206
+ configuration.parameters.key = configuration.key;
2207
+ }
2208
+ super(configuration);
2209
+ }
2210
+ }
2211
+
2212
+ export { HocuspocusCloudProvider, HocuspocusProvider, MessageType, WebSocketStatus };
2023
2213
  //# sourceMappingURL=hocuspocus-provider.esm.js.map