@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
@@ -3,6 +3,8 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var Y = require('yjs');
6
+ var attempt = require('@lifeomic/attempt');
7
+ var common = require('@hocuspocus/common');
6
8
 
7
9
  function _interopNamespace(e) {
8
10
  if (e && e.__esModule) return e;
@@ -13,14 +15,12 @@ function _interopNamespace(e) {
13
15
  var d = Object.getOwnPropertyDescriptor(e, k);
14
16
  Object.defineProperty(n, k, d.get ? d : {
15
17
  enumerable: true,
16
- get: function () {
17
- return e[k];
18
- }
18
+ get: function () { return e[k]; }
19
19
  });
20
20
  }
21
21
  });
22
22
  }
23
- n['default'] = e;
23
+ n["default"] = e;
24
24
  return Object.freeze(n);
25
25
  }
26
26
 
@@ -284,8 +284,6 @@ hasConf('production');
284
284
  */
285
285
 
286
286
  const floor = Math.floor;
287
- const round = Math.round;
288
- const log10 = Math.log10;
289
287
 
290
288
  /**
291
289
  * @function
@@ -1398,23 +1396,26 @@ class EventEmitter {
1398
1396
  }
1399
1397
  }
1400
1398
 
1401
- exports.MessageType = void 0;
1402
- (function (MessageType) {
1403
- MessageType[MessageType["Sync"] = 0] = "Sync";
1404
- MessageType[MessageType["Awareness"] = 1] = "Awareness";
1405
- MessageType[MessageType["Auth"] = 2] = "Auth";
1406
- MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1407
- })(exports.MessageType || (exports.MessageType = {}));
1408
-
1409
1399
  class IncomingMessage {
1410
1400
  constructor(data) {
1411
1401
  this.data = data;
1412
1402
  this.encoder = createEncoder();
1413
1403
  this.decoder = createDecoder(new Uint8Array(this.data));
1414
- this.type = readVarUint(this.decoder);
1415
1404
  }
1416
- get name() {
1417
- return exports.MessageType[this.type];
1405
+ readVarUint() {
1406
+ return readVarUint(this.decoder);
1407
+ }
1408
+ readVarUint8Array() {
1409
+ return readVarUint8Array(this.decoder);
1410
+ }
1411
+ writeVarUint(type) {
1412
+ return writeVarUint(this.encoder, type);
1413
+ }
1414
+ writeVarUint8Array(data) {
1415
+ return writeVarUint8Array(this.encoder, data);
1416
+ }
1417
+ length() {
1418
+ return length$1(this.encoder);
1418
1419
  }
1419
1420
  }
1420
1421
 
@@ -1545,32 +1546,45 @@ const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
1545
1546
  return messageType
1546
1547
  };
1547
1548
 
1548
- const messagePermissionDenied = 0;
1549
-
1550
- /**
1551
- * @callback PermissionDeniedHandler
1552
- * @param {any} y
1553
- * @param {string} reason
1554
- */
1549
+ exports.MessageType = void 0;
1550
+ (function (MessageType) {
1551
+ MessageType[MessageType["Sync"] = 0] = "Sync";
1552
+ MessageType[MessageType["Awareness"] = 1] = "Awareness";
1553
+ MessageType[MessageType["Auth"] = 2] = "Auth";
1554
+ MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1555
+ })(exports.MessageType || (exports.MessageType = {}));
1556
+ exports.WebSocketStatus = void 0;
1557
+ (function (WebSocketStatus) {
1558
+ WebSocketStatus["Connecting"] = "connecting";
1559
+ WebSocketStatus["Connected"] = "connected";
1560
+ WebSocketStatus["Disconnected"] = "disconnected";
1561
+ })(exports.WebSocketStatus || (exports.WebSocketStatus = {}));
1555
1562
 
1556
- /**
1557
- *
1558
- * @param {decoding.Decoder} decoder
1559
- * @param {Y.Doc} y
1560
- * @param {PermissionDeniedHandler} permissionDeniedHandler
1561
- */
1562
- const readAuthMessage = (decoder, y, permissionDeniedHandler) => {
1563
- switch (readVarUint(decoder)) {
1564
- case messagePermissionDenied: permissionDeniedHandler(y, readVarString(decoder));
1565
- }
1566
- };
1563
+ class OutgoingMessage {
1564
+ constructor() {
1565
+ this.encoder = createEncoder();
1566
+ }
1567
+ get(args) {
1568
+ return args.encoder;
1569
+ }
1570
+ toUint8Array() {
1571
+ return toUint8Array(this.encoder);
1572
+ }
1573
+ }
1567
1574
 
1568
1575
  class MessageReceiver {
1569
1576
  constructor(message) {
1577
+ this.broadcasted = false;
1570
1578
  this.message = message;
1571
1579
  }
1580
+ setBroadcasted(value) {
1581
+ this.broadcasted = value;
1582
+ return this;
1583
+ }
1572
1584
  apply(provider, emitSynced = true) {
1573
- switch (this.message.type) {
1585
+ const { message } = this;
1586
+ const type = message.readVarUint();
1587
+ switch (type) {
1574
1588
  case exports.MessageType.Sync:
1575
1589
  this.applySyncMessage(provider, emitSynced);
1576
1590
  break;
@@ -1584,31 +1598,49 @@ class MessageReceiver {
1584
1598
  this.applyQueryAwarenessMessage(provider);
1585
1599
  break;
1586
1600
  default:
1587
- throw new Error(`Can’t apply unknown type of message: ${this.message.type}`);
1601
+ throw new Error(`Can’t apply message of unknown type: ${type}`);
1602
+ }
1603
+ // Reply
1604
+ if (message.length() > 1) {
1605
+ if (this.broadcasted) {
1606
+ // TODO: Some weird TypeScript error
1607
+ // @ts-ignore
1608
+ provider.broadcast(OutgoingMessage, { encoder: message.encoder });
1609
+ }
1610
+ else {
1611
+ // TODO: Some weird TypeScript error
1612
+ // @ts-ignore
1613
+ provider.send(OutgoingMessage, { encoder: message.encoder });
1614
+ }
1588
1615
  }
1589
- return this.message.encoder;
1590
1616
  }
1591
1617
  applySyncMessage(provider, emitSynced) {
1592
- writeVarUint(this.message.encoder, exports.MessageType.Sync);
1593
- const syncMessageType = readSyncMessage(this.message.decoder, this.message.encoder, provider.document, provider);
1594
- if (emitSynced && syncMessageType === messageYjsSyncStep2) {
1618
+ const { message } = this;
1619
+ message.writeVarUint(exports.MessageType.Sync);
1620
+ // Apply update
1621
+ const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
1622
+ // Synced once we receive Step2
1623
+ if (emitSynced && (syncMessageType === messageYjsSyncStep2)) {
1595
1624
  provider.synced = true;
1596
1625
  }
1626
+ if (syncMessageType === messageYjsUpdate || syncMessageType === messageYjsSyncStep2) {
1627
+ if (provider.unsyncedChanges > 0) {
1628
+ provider.unsyncedChanges -= 1;
1629
+ }
1630
+ }
1597
1631
  }
1598
1632
  applyAwarenessMessage(provider) {
1599
- applyAwarenessUpdate(provider.awareness, readVarUint8Array(this.message.decoder), provider);
1633
+ const { message } = this;
1634
+ applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
1600
1635
  }
1601
- // TODO: This isn’t really used. Needs to be implemented in the server, or removed here.
1602
1636
  applyAuthMessage(provider) {
1603
- readAuthMessage(this.message.decoder, provider.document,
1604
- // TODO: Add a configureable hook
1605
- (provider, reason) => {
1606
- console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
1607
- });
1637
+ const { message } = this;
1638
+ common.readAuthMessage(message.decoder, provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
1608
1639
  }
1609
1640
  applyQueryAwarenessMessage(provider) {
1610
- writeVarUint(this.message.encoder, exports.MessageType.Awareness);
1611
- writeVarUint8Array(this.message.encoder, encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
1641
+ const { message } = this;
1642
+ message.writeVarUint(exports.MessageType.Awareness);
1643
+ message.writeVarUint8Array(encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
1612
1644
  }
1613
1645
  }
1614
1646
 
@@ -1628,18 +1660,6 @@ class MessageSender {
1628
1660
  }
1629
1661
  }
1630
1662
 
1631
- class OutgoingMessage {
1632
- constructor() {
1633
- this.encoder = createEncoder();
1634
- }
1635
- get name() {
1636
- if (typeof this.type === 'number') {
1637
- return exports.MessageType[this.type];
1638
- }
1639
- throw new Error('Type for outgoing message not set.');
1640
- }
1641
- }
1642
-
1643
1663
  class SyncStepOneMessage extends OutgoingMessage {
1644
1664
  constructor() {
1645
1665
  super(...arguments);
@@ -1684,6 +1704,22 @@ class QueryAwarenessMessage extends OutgoingMessage {
1684
1704
  }
1685
1705
  }
1686
1706
 
1707
+ class AuthenticationMessage extends OutgoingMessage {
1708
+ constructor() {
1709
+ super(...arguments);
1710
+ this.type = exports.MessageType.Auth;
1711
+ this.description = 'Authentication';
1712
+ }
1713
+ get(args) {
1714
+ if (typeof args.token === 'undefined') {
1715
+ throw new Error('The authentication message requires `token` as an argument.');
1716
+ }
1717
+ writeVarUint(this.encoder, this.type);
1718
+ common.writeAuthentication(this.encoder, args.token);
1719
+ return this.encoder;
1720
+ }
1721
+ }
1722
+
1687
1723
  class AwarenessMessage extends OutgoingMessage {
1688
1724
  constructor() {
1689
1725
  super(...arguments);
@@ -1723,37 +1759,42 @@ class UpdateMessage extends OutgoingMessage {
1723
1759
  }
1724
1760
  }
1725
1761
 
1726
- var awarenessStatesToArray = (states) => {
1727
- return Array.from(states.entries()).map(([key, value]) => {
1728
- return {
1729
- clientId: key,
1730
- ...value,
1731
- };
1732
- });
1733
- };
1734
-
1735
- // @ts-nocheck
1736
- exports.WebSocketStatus = void 0;
1737
- (function (WebSocketStatus) {
1738
- WebSocketStatus["Connecting"] = "connecting";
1739
- WebSocketStatus["Connected"] = "connected";
1740
- WebSocketStatus["Disconnected"] = "disconnected";
1741
- })(exports.WebSocketStatus || (exports.WebSocketStatus = {}));
1742
1762
  class HocuspocusProvider extends EventEmitter {
1743
- constructor(options = {}) {
1763
+ constructor(configuration) {
1744
1764
  super();
1745
- this.options = {
1746
- url: '',
1765
+ this.configuration = {
1747
1766
  name: '',
1767
+ url: '',
1768
+ // @ts-ignore
1769
+ document: undefined,
1770
+ // @ts-ignore
1771
+ awareness: undefined,
1772
+ WebSocketPolyfill: undefined,
1773
+ token: null,
1748
1774
  parameters: {},
1749
- debug: false,
1750
1775
  connect: true,
1751
1776
  broadcast: true,
1752
1777
  forceSyncInterval: false,
1753
- reconnectTimeoutBase: 1200,
1754
- maxReconnectTimeout: 2500,
1755
1778
  // TODO: this should depend on awareness.outdatedTime
1756
1779
  messageReconnectTimeout: 30000,
1780
+ // 1 second
1781
+ delay: 1000,
1782
+ // instant
1783
+ initialDelay: 0,
1784
+ // double the delay each time
1785
+ factor: 2,
1786
+ // unlimited retries
1787
+ maxAttempts: 0,
1788
+ // wait at least 1 second
1789
+ minDelay: 1000,
1790
+ // at least every 30 seconds
1791
+ maxDelay: 30000,
1792
+ // randomize
1793
+ jitter: true,
1794
+ // retry forever
1795
+ timeout: 0,
1796
+ onAuthenticated: () => null,
1797
+ onAuthenticationFailed: () => null,
1757
1798
  onOpen: () => null,
1758
1799
  onConnect: () => null,
1759
1800
  onMessage: () => null,
@@ -1763,70 +1804,183 @@ class HocuspocusProvider extends EventEmitter {
1763
1804
  onDisconnect: () => null,
1764
1805
  onClose: () => null,
1765
1806
  onDestroy: () => null,
1807
+ onAwarenessUpdate: () => null,
1766
1808
  onAwarenessChange: () => null,
1809
+ quiet: false,
1767
1810
  };
1768
1811
  this.subscribedToBroadcastChannel = false;
1769
1812
  this.webSocket = null;
1770
1813
  this.shouldConnect = true;
1771
1814
  this.status = exports.WebSocketStatus.Disconnected;
1772
- this.failedConnectionAttempts = 0;
1773
1815
  this.isSynced = false;
1816
+ this.unsyncedChanges = 0;
1817
+ this.isAuthenticated = false;
1774
1818
  this.lastMessageReceived = 0;
1775
1819
  this.mux = createMutex();
1776
1820
  this.intervals = {
1777
1821
  forceSync: null,
1778
1822
  connectionChecker: null,
1779
1823
  };
1780
- this.setOptions(options);
1781
- this.options.document = options.document ? options.document : new Y__namespace.Doc();
1782
- this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
1783
- this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
1784
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
1785
- this.on('open', this.options.onOpen);
1786
- this.on('connect', this.options.onConnect);
1787
- this.on('message', this.options.onMessage);
1788
- this.on('outgoingMessage', this.options.onOutgoingMessage);
1789
- this.on('synced', this.options.onSynced);
1790
- this.on('status', this.options.onStatus);
1791
- this.on('disconnect', this.options.onDisconnect);
1792
- this.on('close', this.options.onClose);
1793
- this.on('destroy', this.options.onDestroy);
1794
- this.on('awarenessChange', this.options.onAwarenessChange);
1824
+ this.connectionAttempt = null;
1825
+ this.boundConnect = this.connect.bind(this);
1826
+ this.boundBeforeUnload = this.beforeUnload.bind(this);
1827
+ this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
1828
+ this.setConfiguration(configuration);
1829
+ this.configuration.document = configuration.document ? configuration.document : new Y__namespace.Doc();
1830
+ this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document);
1831
+ this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket;
1832
+ this.on('open', this.configuration.onOpen);
1833
+ this.on('authenticated', this.configuration.onAuthenticated);
1834
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
1835
+ this.on('connect', this.configuration.onConnect);
1836
+ this.on('message', this.configuration.onMessage);
1837
+ this.on('outgoingMessage', this.configuration.onOutgoingMessage);
1838
+ this.on('synced', this.configuration.onSynced);
1839
+ this.on('status', this.configuration.onStatus);
1840
+ this.on('disconnect', this.configuration.onDisconnect);
1841
+ this.on('close', this.configuration.onClose);
1842
+ this.on('destroy', this.configuration.onDestroy);
1843
+ this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
1844
+ this.on('awarenessChange', this.configuration.onAwarenessChange);
1845
+ this.awareness.on('update', () => {
1846
+ this.emit('awarenessUpdate', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
1847
+ });
1795
1848
  this.awareness.on('change', () => {
1796
- this.emit('awarenessChange', {
1797
- states: awarenessStatesToArray(this.awareness.getStates()),
1798
- });
1849
+ this.emit('awarenessChange', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
1799
1850
  });
1800
- this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1801
1851
  this.document.on('update', this.documentUpdateHandler.bind(this));
1802
1852
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1803
- this.registerBeforeUnloadEventListener();
1804
- if (this.options.forceSyncInterval) {
1805
- this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.options.forceSyncInterval);
1853
+ this.registerEventListeners();
1854
+ this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
1855
+ if (this.configuration.forceSyncInterval) {
1856
+ this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
1806
1857
  }
1807
- if (this.options.connect) {
1808
- this.connect();
1858
+ if (typeof configuration.connect !== 'undefined') {
1859
+ this.shouldConnect = configuration.connect;
1860
+ }
1861
+ if (!this.shouldConnect) {
1862
+ return;
1809
1863
  }
1864
+ this.connect();
1810
1865
  }
1811
- setOptions(options = {}) {
1812
- this.options = { ...this.options, ...options };
1866
+ setConfiguration(configuration = {}) {
1867
+ this.configuration = { ...this.configuration, ...configuration };
1868
+ }
1869
+ async connect() {
1870
+ if (this.status === exports.WebSocketStatus.Connected) {
1871
+ return;
1872
+ }
1873
+ // Always cancel any previously initiated connection retryer instances
1874
+ if (this.cancelWebsocketRetry) {
1875
+ this.cancelWebsocketRetry();
1876
+ this.cancelWebsocketRetry = undefined;
1877
+ }
1878
+ this.unsyncedChanges = 0; // set to 0 in case we got reconnected
1879
+ this.shouldConnect = true;
1880
+ this.subscribeToBroadcastChannel();
1881
+ const abortableRetry = () => {
1882
+ let cancelAttempt = false;
1883
+ const retryPromise = attempt.retry(this.createWebSocketConnection.bind(this), {
1884
+ delay: this.configuration.delay,
1885
+ initialDelay: this.configuration.initialDelay,
1886
+ factor: this.configuration.factor,
1887
+ maxAttempts: this.configuration.maxAttempts,
1888
+ minDelay: this.configuration.minDelay,
1889
+ maxDelay: this.configuration.maxDelay,
1890
+ jitter: this.configuration.jitter,
1891
+ timeout: this.configuration.timeout,
1892
+ beforeAttempt: context => {
1893
+ if (!this.shouldConnect || cancelAttempt) {
1894
+ context.abort();
1895
+ }
1896
+ },
1897
+ }).catch((error) => {
1898
+ // If we aborted the connection attempt then don’t throw an error
1899
+ // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
1900
+ if (error && error.code !== 'ATTEMPT_ABORTED') {
1901
+ throw error;
1902
+ }
1903
+ });
1904
+ return {
1905
+ retryPromise,
1906
+ cancelFunc: () => {
1907
+ cancelAttempt = true;
1908
+ },
1909
+ };
1910
+ };
1911
+ const { retryPromise, cancelFunc } = abortableRetry();
1912
+ this.cancelWebsocketRetry = cancelFunc;
1913
+ return retryPromise;
1914
+ }
1915
+ createWebSocketConnection() {
1916
+ return new Promise((resolve, reject) => {
1917
+ if (this.webSocket) {
1918
+ this.webSocket.close();
1919
+ this.webSocket = null;
1920
+ }
1921
+ // Init the WebSocket connection
1922
+ const ws = new this.configuration.WebSocketPolyfill(this.url);
1923
+ ws.binaryType = 'arraybuffer';
1924
+ ws.onmessage = this.onMessage.bind(this);
1925
+ ws.onclose = this.onClose.bind(this);
1926
+ ws.onopen = this.onOpen.bind(this);
1927
+ ws.onerror = (err) => {
1928
+ reject(err);
1929
+ };
1930
+ this.webSocket = ws;
1931
+ // Reset the status
1932
+ this.synced = false;
1933
+ this.status = exports.WebSocketStatus.Connecting;
1934
+ this.emit('status', { status: exports.WebSocketStatus.Connecting });
1935
+ // Store resolve/reject for later use
1936
+ this.connectionAttempt = {
1937
+ resolve,
1938
+ reject,
1939
+ };
1940
+ });
1941
+ }
1942
+ resolveConnectionAttempt() {
1943
+ var _a;
1944
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1945
+ this.connectionAttempt = null;
1946
+ this.status = exports.WebSocketStatus.Connected;
1947
+ this.emit('status', { status: exports.WebSocketStatus.Connected });
1948
+ this.emit('connect');
1949
+ }
1950
+ stopConnectionAttempt() {
1951
+ this.connectionAttempt = null;
1952
+ }
1953
+ rejectConnectionAttempt() {
1954
+ var _a;
1955
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1956
+ this.connectionAttempt = null;
1813
1957
  }
1814
1958
  get document() {
1815
- return this.options.document;
1959
+ return this.configuration.document;
1816
1960
  }
1817
1961
  get awareness() {
1818
- return this.options.awareness;
1962
+ return this.configuration.awareness;
1963
+ }
1964
+ get hasUnsyncedChanges() {
1965
+ return this.unsyncedChanges > 0;
1819
1966
  }
1820
1967
  checkConnection() {
1968
+ var _a;
1969
+ // Don’t check the connection when it’s not even established
1821
1970
  if (this.status !== exports.WebSocketStatus.Connected) {
1822
1971
  return;
1823
1972
  }
1824
- if (this.options.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1973
+ // Don’t close then connection while waiting for the first message
1974
+ if (!this.lastMessageReceived) {
1975
+ return;
1976
+ }
1977
+ // Don’t close the connection when a message was received recently
1978
+ if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1825
1979
  return;
1826
1980
  }
1827
1981
  // No message received in a long time, not even your own
1828
1982
  // Awareness updates, which are updated every 15 seconds.
1829
- this.webSocket.close();
1983
+ (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
1830
1984
  }
1831
1985
  forceSync() {
1832
1986
  if (!this.webSocket) {
@@ -1834,18 +1988,21 @@ class HocuspocusProvider extends EventEmitter {
1834
1988
  }
1835
1989
  this.send(SyncStepOneMessage, { document: this.document });
1836
1990
  }
1837
- registerBeforeUnloadEventListener() {
1991
+ beforeUnload() {
1992
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1993
+ }
1994
+ registerEventListeners() {
1838
1995
  if (typeof window === 'undefined') {
1839
1996
  return;
1840
1997
  }
1841
- window.addEventListener('beforeunload', () => {
1842
- removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1843
- });
1998
+ window.addEventListener('online', this.boundConnect);
1999
+ window.addEventListener('beforeunload', this.boundBeforeUnload);
1844
2000
  }
1845
2001
  documentUpdateHandler(update, origin) {
1846
2002
  if (origin === this) {
1847
2003
  return;
1848
2004
  }
2005
+ this.unsyncedChanges += 1;
1849
2006
  this.send(UpdateMessage, { update }, true);
1850
2007
  }
1851
2008
  awarenessUpdateHandler({ added, updated, removed }, origin) {
@@ -1855,16 +2012,26 @@ class HocuspocusProvider extends EventEmitter {
1855
2012
  clients: changedClients,
1856
2013
  }, true);
1857
2014
  }
2015
+ permissionDeniedHandler(reason) {
2016
+ this.emit('authenticationFailed', { reason });
2017
+ this.isAuthenticated = false;
2018
+ this.shouldConnect = false;
2019
+ }
2020
+ authenticatedHandler() {
2021
+ this.isAuthenticated = true;
2022
+ this.emit('authenticated');
2023
+ this.startSync();
2024
+ }
1858
2025
  // Ensure that the URL always ends with /
1859
2026
  get serverUrl() {
1860
- while (this.options.url[this.options.url.length - 1] === '/') {
1861
- return this.options.url.slice(0, this.options.url.length - 1);
2027
+ while (this.configuration.url[this.configuration.url.length - 1] === '/') {
2028
+ return this.configuration.url.slice(0, this.configuration.url.length - 1);
1862
2029
  }
1863
- return this.options.url;
2030
+ return this.configuration.url;
1864
2031
  }
1865
2032
  get url() {
1866
- const encodedParams = encodeQueryParams(this.options.parameters);
1867
- return `${this.serverUrl}/${this.options.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
2033
+ const encodedParams = encodeQueryParams(this.configuration.parameters);
2034
+ return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
1868
2035
  }
1869
2036
  get synced() {
1870
2037
  return this.isSynced;
@@ -1877,12 +2044,8 @@ class HocuspocusProvider extends EventEmitter {
1877
2044
  this.emit('synced', { state });
1878
2045
  this.emit('sync', { state });
1879
2046
  }
1880
- connect() {
1881
- this.shouldConnect = true;
1882
- if (this.status !== exports.WebSocketStatus.Connected) {
1883
- this.createWebSocketConnection();
1884
- this.subscribeToBroadcastChannel();
1885
- }
2047
+ get isAuthenticationRequired() {
2048
+ return !!this.configuration.token && !this.isAuthenticated;
1886
2049
  }
1887
2050
  disconnect() {
1888
2051
  this.shouldConnect = false;
@@ -1897,27 +2060,24 @@ class HocuspocusProvider extends EventEmitter {
1897
2060
  //
1898
2061
  }
1899
2062
  }
1900
- createWebSocketConnection() {
1901
- if (this.webSocket !== null) {
2063
+ async onOpen(event) {
2064
+ this.emit('open', { event });
2065
+ if (this.isAuthenticationRequired) {
2066
+ this.send(AuthenticationMessage, {
2067
+ token: await this.getToken(),
2068
+ });
1902
2069
  return;
1903
2070
  }
1904
- this.webSocket = new this.options.WebSocketPolyfill(this.url);
1905
- this.webSocket.binaryType = 'arraybuffer';
1906
- this.status = exports.WebSocketStatus.Connecting;
1907
- this.synced = false;
1908
- this.webSocket.onmessage = this.onMessage.bind(this);
1909
- this.webSocket.onclose = this.onClose.bind(this);
1910
- this.webSocket.onopen = this.onOpen.bind(this);
1911
- this.emit('status', { status: 'connecting' });
2071
+ this.startSync();
1912
2072
  }
1913
- onOpen(event) {
1914
- this.emit('open', { event });
2073
+ async getToken() {
2074
+ if (typeof this.configuration.token === 'function') {
2075
+ const token = await this.configuration.token();
2076
+ return token;
2077
+ }
2078
+ return this.configuration.token;
1915
2079
  }
1916
- webSocketConnectionEstablished() {
1917
- this.failedConnectionAttempts = 0;
1918
- this.status = exports.WebSocketStatus.Connected;
1919
- this.emit('status', { status: 'connected' });
1920
- this.emit('connect');
2080
+ startSync() {
1921
2081
  this.send(SyncStepOneMessage, { document: this.document });
1922
2082
  if (this.awareness.getLocalState() !== null) {
1923
2083
  this.send(AwarenessMessage, {
@@ -1927,55 +2087,66 @@ class HocuspocusProvider extends EventEmitter {
1927
2087
  }
1928
2088
  }
1929
2089
  send(Message, args, broadcast = false) {
2090
+ var _a;
1930
2091
  if (broadcast) {
1931
- this.mux(() => {
1932
- this.broadcast(Message, args);
1933
- });
2092
+ this.mux(() => { this.broadcast(Message, args); });
1934
2093
  }
1935
- if (this.status === exports.WebSocketStatus.Connected) {
2094
+ if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === common.WsReadyStates.Open) {
1936
2095
  const messageSender = new MessageSender(Message, args);
1937
2096
  this.emit('outgoingMessage', { message: messageSender.message });
1938
2097
  messageSender.send(this.webSocket);
1939
2098
  }
1940
2099
  }
1941
2100
  onMessage(event) {
1942
- if (this.status !== exports.WebSocketStatus.Connected) {
1943
- this.webSocketConnectionEstablished();
1944
- }
2101
+ this.resolveConnectionAttempt();
1945
2102
  this.lastMessageReceived = getUnixTime();
1946
2103
  const message = new IncomingMessage(event.data);
1947
2104
  this.emit('message', { event, message });
1948
- new MessageReceiver(message, this).apply(this);
1949
- // TODO: What’s that doing?
1950
- // if (encoding.length(encoder) > 1) {
1951
- // this.send(encoding.toUint8Array(encoder))
1952
- // }
2105
+ new MessageReceiver(message).apply(this);
1953
2106
  }
1954
2107
  onClose(event) {
1955
2108
  this.emit('close', { event });
1956
2109
  this.webSocket = null;
2110
+ this.isAuthenticated = false;
2111
+ this.synced = false;
1957
2112
  if (this.status === exports.WebSocketStatus.Connected) {
1958
- this.synced = false;
1959
2113
  // update awareness (all users except local left)
1960
2114
  removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
1961
2115
  this.status = exports.WebSocketStatus.Disconnected;
1962
- this.emit('status', { status: 'disconnected' });
2116
+ this.emit('status', { status: exports.WebSocketStatus.Disconnected });
1963
2117
  this.emit('disconnect', { event });
1964
2118
  }
1965
- else {
1966
- this.failedConnectionAttempts += 1;
2119
+ if (event.code === common.Unauthorized.code) {
2120
+ if (!this.configuration.quiet) {
2121
+ 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.');
2122
+ }
2123
+ this.shouldConnect = false;
2124
+ }
2125
+ if (event.code === common.Forbidden.code) {
2126
+ if (!this.configuration.quiet) {
2127
+ console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2128
+ }
2129
+ }
2130
+ if (this.connectionAttempt) {
2131
+ // That connection attempt failed.
2132
+ this.rejectConnectionAttempt();
1967
2133
  }
2134
+ else if (this.shouldConnect) {
2135
+ // The connection was closed by the server. Let’s just try again.
2136
+ this.connect();
2137
+ }
2138
+ // If we’ll reconnect, we’re done for now.
1968
2139
  if (this.shouldConnect) {
1969
- const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
1970
- this.log(`[close] Reconnecting in ${wait}ms …`);
1971
- setTimeout(this.createWebSocketConnection.bind(this), wait);
1972
2140
  return;
1973
2141
  }
1974
- if (this.status !== exports.WebSocketStatus.Disconnected) {
1975
- this.status = exports.WebSocketStatus.Disconnected;
1976
- this.emit('status', { status: 'disconnected' });
1977
- this.emit('disconnect', { event });
2142
+ // The status is set correctly already.
2143
+ if (this.status === exports.WebSocketStatus.Disconnected) {
2144
+ return;
1978
2145
  }
2146
+ // Let’s update the connection status.
2147
+ this.status = exports.WebSocketStatus.Disconnected;
2148
+ this.emit('status', { status: exports.WebSocketStatus.Disconnected });
2149
+ this.emit('disconnect', { event });
1979
2150
  }
1980
2151
  destroy() {
1981
2152
  this.emit('destroy');
@@ -1983,27 +2154,35 @@ class HocuspocusProvider extends EventEmitter {
1983
2154
  clearInterval(this.intervals.forceSync);
1984
2155
  }
1985
2156
  clearInterval(this.intervals.connectionChecker);
2157
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2158
+ // If there is still a connection attempt outstanding then we should stop
2159
+ // it before calling disconnect, otherwise it will be rejected in the onClose
2160
+ // handler and trigger a retry
2161
+ this.stopConnectionAttempt();
1986
2162
  this.disconnect();
1987
2163
  this.awareness.off('update', this.awarenessUpdateHandler);
1988
2164
  this.document.off('update', this.documentUpdateHandler);
1989
2165
  this.removeAllListeners();
2166
+ if (typeof window === 'undefined') {
2167
+ return;
2168
+ }
2169
+ window.removeEventListener('online', this.boundConnect);
2170
+ window.removeEventListener('beforeunload', this.boundBeforeUnload);
1990
2171
  }
1991
2172
  get broadcastChannel() {
1992
- return `${this.serverUrl}/${this.options.name}`;
2173
+ return `${this.serverUrl}/${this.configuration.name}`;
1993
2174
  }
1994
2175
  broadcastChannelSubscriber(data) {
1995
2176
  this.mux(() => {
1996
2177
  const message = new IncomingMessage(data);
1997
- new MessageReceiver(message, this).apply(this, false);
1998
- // TODO: What’s that doing?
1999
- // if (encoding.length(encoder) > 1) {
2000
- // this.broadcast(encoding.toUint8Array(encoder))
2001
- // }
2178
+ new MessageReceiver(message)
2179
+ .setBroadcasted(true)
2180
+ .apply(this, false);
2002
2181
  });
2003
2182
  }
2004
2183
  subscribeToBroadcastChannel() {
2005
2184
  if (!this.subscribedToBroadcastChannel) {
2006
- subscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2185
+ subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2007
2186
  this.subscribedToBroadcastChannel = true;
2008
2187
  }
2009
2188
  this.mux(() => {
@@ -2021,12 +2200,12 @@ class HocuspocusProvider extends EventEmitter {
2021
2200
  states: new Map(),
2022
2201
  }, true);
2023
2202
  if (this.subscribedToBroadcastChannel) {
2024
- unsubscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2203
+ unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2025
2204
  this.subscribedToBroadcastChannel = false;
2026
2205
  }
2027
2206
  }
2028
2207
  broadcast(Message, args) {
2029
- if (!this.options.broadcast) {
2208
+ if (!this.configuration.broadcast) {
2030
2209
  return;
2031
2210
  }
2032
2211
  if (!this.subscribedToBroadcastChannel) {
@@ -2034,17 +2213,26 @@ class HocuspocusProvider extends EventEmitter {
2034
2213
  }
2035
2214
  new MessageSender(Message, args).broadcast(this.broadcastChannel);
2036
2215
  }
2037
- log(message) {
2038
- if (!this.options.debug) {
2039
- return;
2040
- }
2041
- console.log(message);
2042
- }
2043
2216
  setAwarenessField(key, value) {
2044
2217
  this.awareness.setLocalStateField(key, value);
2045
2218
  }
2046
2219
  }
2047
2220
 
2221
+ class HocuspocusCloudProvider extends HocuspocusProvider {
2222
+ constructor(configuration) {
2223
+ if (!configuration.url) {
2224
+ configuration.url = 'wss://connect.hocuspocus.cloud';
2225
+ }
2226
+ if (configuration.key) {
2227
+ if (!configuration.parameters) {
2228
+ configuration.parameters = {};
2229
+ }
2230
+ configuration.parameters.key = configuration.key;
2231
+ }
2232
+ super(configuration);
2233
+ }
2234
+ }
2235
+
2236
+ exports.HocuspocusCloudProvider = HocuspocusCloudProvider;
2048
2237
  exports.HocuspocusProvider = HocuspocusProvider;
2049
- exports.awarenessStatesToArray = awarenessStatesToArray;
2050
2238
  //# sourceMappingURL=hocuspocus-provider.cjs.map