@hocuspocus/provider 1.0.0-alpha.4 → 1.0.0-alpha.40

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 (132) hide show
  1. package/dist/{hocuspocus-provider.js → hocuspocus-provider.cjs} +362 -186
  2. package/dist/hocuspocus-provider.cjs.map +1 -0
  3. package/dist/hocuspocus-provider.esm.js +359 -181
  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 -32
  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 +16 -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 +168 -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/backend/src/webhook.d.ts → playground/backend/src/redis.d.ts} +0 -0
  50. package/dist/playground/backend/src/slow.d.ts +1 -0
  51. package/dist/playground/backend/src/webhook.d.ts +1 -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/closeConnections.d.ts +1 -0
  77. package/dist/tests/server/getConnectionsCount.d.ts +1 -0
  78. package/dist/tests/server/getDocumentName.d.ts +1 -0
  79. package/dist/tests/server/getDocumentsCount.d.ts +1 -0
  80. package/dist/tests/server/getMessageLogs.d.ts +1 -0
  81. package/dist/tests/server/listen.d.ts +1 -0
  82. package/dist/tests/server/onAuthenticate.d.ts +1 -0
  83. package/dist/tests/server/onAwarenessUpdate.d.ts +1 -0
  84. package/dist/tests/server/onChange.d.ts +1 -0
  85. package/dist/tests/server/onConfigure.d.ts +1 -0
  86. package/dist/tests/server/onConnect.d.ts +1 -0
  87. package/dist/tests/server/onDestroy.d.ts +1 -0
  88. package/dist/tests/server/onDisconnect.d.ts +1 -0
  89. package/dist/tests/server/onListen.d.ts +1 -0
  90. package/dist/tests/server/onLoadDocument.d.ts +1 -0
  91. package/dist/tests/server/onRequest.d.ts +1 -0
  92. package/dist/tests/server/onStoreDocument.d.ts +1 -0
  93. package/dist/tests/server/onUpgrade.d.ts +1 -0
  94. package/dist/tests/server/requiresAuthentication.d.ts +1 -0
  95. package/dist/tests/transformer/TiptapTransformer.d.ts +1 -0
  96. package/dist/tests/utils/createDirectory.d.ts +1 -0
  97. package/dist/tests/utils/flushRedis.d.ts +1 -0
  98. package/dist/tests/utils/index.d.ts +8 -0
  99. package/dist/tests/utils/newHocuspocus.d.ts +2 -0
  100. package/dist/tests/utils/newHocuspocusProvider.d.ts +3 -0
  101. package/dist/tests/utils/randomInteger.d.ts +1 -0
  102. package/dist/tests/utils/redisConnectionSettings.d.ts +4 -0
  103. package/dist/tests/utils/removeDirectory.d.ts +1 -0
  104. package/dist/tests/utils/retryableAssertion.d.ts +2 -0
  105. package/dist/tests/utils/sleep.d.ts +1 -0
  106. package/package.json +15 -11
  107. package/src/EventEmitter.ts +1 -1
  108. package/src/HocuspocusCloudProvider.ts +34 -0
  109. package/src/HocuspocusProvider.ts +389 -159
  110. package/src/IncomingMessage.ts +35 -11
  111. package/src/MessageReceiver.ts +56 -24
  112. package/src/MessageSender.ts +5 -17
  113. package/src/OutgoingMessage.ts +9 -9
  114. package/src/OutgoingMessages/AuthenticationMessage.ts +21 -0
  115. package/src/OutgoingMessages/AwarenessMessage.ts +1 -1
  116. package/src/OutgoingMessages/SyncStepOneMessage.ts +0 -1
  117. package/src/OutgoingMessages/UpdateMessage.ts +4 -4
  118. package/src/index.ts +1 -1
  119. package/src/types.ts +70 -3
  120. package/CHANGELOG.md +0 -24
  121. package/dist/hocuspocus-provider.js.map +0 -1
  122. package/dist/packages/logger/src/index.d.ts +0 -13
  123. package/dist/packages/provider/src/utils/awarenessStatesToArray.d.ts +0 -4
  124. package/dist/packages/provider/src/utils/index.d.ts +0 -1
  125. package/dist/packages/redis/src/Redis.d.ts +0 -22
  126. package/dist/packages/redis/src/RedisCluster.d.ts +0 -4
  127. package/dist/packages/redis/src/index.d.ts +0 -2
  128. package/dist/packages/rocksdb/src/index.d.ts +0 -30
  129. package/dist/packages/server/src/CloseEvents.d.ts +0 -3
  130. package/dist/packages/throttle/src/index.d.ts +0 -28
  131. package/src/utils/awarenessStatesToArray.ts +0 -8
  132. 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,107 +1759,210 @@ 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,
1776
+ broadcast: true,
1751
1777
  forceSyncInterval: false,
1752
- reconnectTimeoutBase: 1200,
1753
- maxReconnectTimeout: 2500,
1754
1778
  // TODO: this should depend on awareness.outdatedTime
1755
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,
1756
1798
  onOpen: () => null,
1757
1799
  onConnect: () => null,
1758
1800
  onMessage: () => null,
1759
1801
  onOutgoingMessage: () => null,
1802
+ onStatus: () => null,
1760
1803
  onSynced: () => null,
1761
1804
  onDisconnect: () => null,
1762
1805
  onClose: () => null,
1763
1806
  onDestroy: () => null,
1807
+ onAwarenessUpdate: () => null,
1764
1808
  onAwarenessChange: () => null,
1809
+ quiet: false,
1765
1810
  };
1766
1811
  this.subscribedToBroadcastChannel = false;
1767
1812
  this.webSocket = null;
1768
1813
  this.shouldConnect = true;
1769
1814
  this.status = exports.WebSocketStatus.Disconnected;
1770
- this.failedConnectionAttempts = 0;
1771
1815
  this.isSynced = false;
1816
+ this.unsyncedChanges = 0;
1817
+ this.isAuthenticated = false;
1772
1818
  this.lastMessageReceived = 0;
1773
1819
  this.mux = createMutex();
1774
1820
  this.intervals = {
1775
1821
  forceSync: null,
1776
1822
  connectionChecker: null,
1777
1823
  };
1778
- this.setOptions(options);
1779
- this.options.document = options.document ? options.document : new Y__namespace.Doc();
1780
- this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
1781
- this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
1782
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
1783
- this.on('open', this.options.onOpen);
1784
- this.on('connect', this.options.onConnect);
1785
- this.on('message', this.options.onMessage);
1786
- this.on('outgoingMessage', this.options.onOutgoingMessage);
1787
- this.on('synced', this.options.onSynced);
1788
- this.on('disconnect', this.options.onDisconnect);
1789
- this.on('close', this.options.onClose);
1790
- this.on('destroy', this.options.onDestroy);
1791
- 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
+ });
1792
1848
  this.awareness.on('change', () => {
1793
- this.emit('awarenessChange', {
1794
- states: awarenessStatesToArray(this.awareness.getStates()),
1795
- });
1849
+ this.emit('awarenessChange', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
1796
1850
  });
1797
- this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1798
1851
  this.document.on('update', this.documentUpdateHandler.bind(this));
1799
1852
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1800
- this.registerBeforeUnloadEventListener();
1801
- if (this.options.forceSyncInterval) {
1802
- 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);
1803
1857
  }
1804
- if (this.options.connect) {
1805
- this.connect();
1858
+ if (typeof configuration.connect !== 'undefined') {
1859
+ this.shouldConnect = configuration.connect;
1806
1860
  }
1861
+ if (!this.shouldConnect) {
1862
+ return;
1863
+ }
1864
+ this.connect();
1865
+ }
1866
+ setConfiguration(configuration = {}) {
1867
+ this.configuration = { ...this.configuration, ...configuration };
1868
+ }
1869
+ async connect() {
1870
+ if (this.status === exports.WebSocketStatus.Connected) {
1871
+ return;
1872
+ }
1873
+ this.unsyncedChanges = 0; // set to 0 in case we got reconnected
1874
+ this.shouldConnect = true;
1875
+ this.subscribeToBroadcastChannel();
1876
+ try {
1877
+ await attempt.retry(this.createWebSocketConnection.bind(this), {
1878
+ delay: this.configuration.delay,
1879
+ initialDelay: this.configuration.initialDelay,
1880
+ factor: this.configuration.factor,
1881
+ maxAttempts: this.configuration.maxAttempts,
1882
+ minDelay: this.configuration.minDelay,
1883
+ maxDelay: this.configuration.maxDelay,
1884
+ jitter: this.configuration.jitter,
1885
+ timeout: this.configuration.timeout,
1886
+ beforeAttempt: context => {
1887
+ if (!this.shouldConnect) {
1888
+ context.abort();
1889
+ }
1890
+ },
1891
+ });
1892
+ }
1893
+ catch (error) {
1894
+ // If we aborted the connection attempt then don’t throw an error
1895
+ // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
1896
+ if (error && error.code !== 'ATTEMPT_ABORTED') {
1897
+ throw error;
1898
+ }
1899
+ }
1900
+ }
1901
+ createWebSocketConnection() {
1902
+ return new Promise((resolve, reject) => {
1903
+ // Init the WebSocket connection
1904
+ const ws = new this.configuration.WebSocketPolyfill(this.url);
1905
+ ws.binaryType = 'arraybuffer';
1906
+ ws.onmessage = this.onMessage.bind(this);
1907
+ ws.onclose = this.onClose.bind(this);
1908
+ ws.onopen = this.onOpen.bind(this);
1909
+ ws.onerror = (err) => {
1910
+ reject(err);
1911
+ };
1912
+ this.webSocket = ws;
1913
+ // Reset the status
1914
+ this.synced = false;
1915
+ this.status = exports.WebSocketStatus.Connecting;
1916
+ this.emit('status', { status: exports.WebSocketStatus.Connecting });
1917
+ // Store resolve/reject for later use
1918
+ this.connectionAttempt = {
1919
+ resolve,
1920
+ reject,
1921
+ };
1922
+ });
1923
+ }
1924
+ resolveConnectionAttempt() {
1925
+ var _a;
1926
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1927
+ this.connectionAttempt = null;
1928
+ this.status = exports.WebSocketStatus.Connected;
1929
+ this.emit('status', { status: exports.WebSocketStatus.Connected });
1930
+ this.emit('connect');
1931
+ }
1932
+ stopConnectionAttempt() {
1933
+ this.connectionAttempt = null;
1807
1934
  }
1808
- setOptions(options = {}) {
1809
- this.options = { ...this.options, ...options };
1935
+ rejectConnectionAttempt() {
1936
+ var _a;
1937
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1938
+ this.connectionAttempt = null;
1810
1939
  }
1811
1940
  get document() {
1812
- return this.options.document;
1941
+ return this.configuration.document;
1813
1942
  }
1814
1943
  get awareness() {
1815
- return this.options.awareness;
1944
+ return this.configuration.awareness;
1945
+ }
1946
+ get hasUnsyncedChanges() {
1947
+ return this.unsyncedChanges > 0;
1816
1948
  }
1817
1949
  checkConnection() {
1950
+ var _a;
1951
+ // Don’t check the connection when it’s not even established
1818
1952
  if (this.status !== exports.WebSocketStatus.Connected) {
1819
1953
  return;
1820
1954
  }
1821
- if (this.options.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1955
+ // Don’t close then connection while waiting for the first message
1956
+ if (!this.lastMessageReceived) {
1957
+ return;
1958
+ }
1959
+ // Don’t close the connection when a message was received recently
1960
+ if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1822
1961
  return;
1823
1962
  }
1824
1963
  // No message received in a long time, not even your own
1825
1964
  // Awareness updates, which are updated every 15 seconds.
1826
- this.webSocket.close();
1965
+ (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
1827
1966
  }
1828
1967
  forceSync() {
1829
1968
  if (!this.webSocket) {
@@ -1831,18 +1970,21 @@ class HocuspocusProvider extends EventEmitter {
1831
1970
  }
1832
1971
  this.send(SyncStepOneMessage, { document: this.document });
1833
1972
  }
1834
- registerBeforeUnloadEventListener() {
1973
+ beforeUnload() {
1974
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1975
+ }
1976
+ registerEventListeners() {
1835
1977
  if (typeof window === 'undefined') {
1836
1978
  return;
1837
1979
  }
1838
- window.addEventListener('beforeunload', () => {
1839
- removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1840
- });
1980
+ window.addEventListener('online', this.boundConnect);
1981
+ window.addEventListener('beforeunload', this.boundBeforeUnload);
1841
1982
  }
1842
1983
  documentUpdateHandler(update, origin) {
1843
1984
  if (origin === this) {
1844
1985
  return;
1845
1986
  }
1987
+ this.unsyncedChanges += 1;
1846
1988
  this.send(UpdateMessage, { update }, true);
1847
1989
  }
1848
1990
  awarenessUpdateHandler({ added, updated, removed }, origin) {
@@ -1852,16 +1994,26 @@ class HocuspocusProvider extends EventEmitter {
1852
1994
  clients: changedClients,
1853
1995
  }, true);
1854
1996
  }
1997
+ permissionDeniedHandler(reason) {
1998
+ this.emit('authenticationFailed', { reason });
1999
+ this.isAuthenticated = false;
2000
+ this.shouldConnect = false;
2001
+ }
2002
+ authenticatedHandler() {
2003
+ this.isAuthenticated = true;
2004
+ this.emit('authenticated');
2005
+ this.startSync();
2006
+ }
1855
2007
  // Ensure that the URL always ends with /
1856
2008
  get serverUrl() {
1857
- while (this.options.url[this.options.url.length - 1] === '/') {
1858
- return this.options.url.slice(0, this.options.url.length - 1);
2009
+ while (this.configuration.url[this.configuration.url.length - 1] === '/') {
2010
+ return this.configuration.url.slice(0, this.configuration.url.length - 1);
1859
2011
  }
1860
- return this.options.url;
2012
+ return this.configuration.url;
1861
2013
  }
1862
2014
  get url() {
1863
- const encodedParams = encodeQueryParams(this.options.parameters);
1864
- return `${this.serverUrl}/${this.options.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
2015
+ const encodedParams = encodeQueryParams(this.configuration.parameters);
2016
+ return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
1865
2017
  }
1866
2018
  get synced() {
1867
2019
  return this.isSynced;
@@ -1874,12 +2026,8 @@ class HocuspocusProvider extends EventEmitter {
1874
2026
  this.emit('synced', { state });
1875
2027
  this.emit('sync', { state });
1876
2028
  }
1877
- connect() {
1878
- this.shouldConnect = true;
1879
- if (this.status !== exports.WebSocketStatus.Connected) {
1880
- this.createWebSocketConnection();
1881
- this.subscribeToBroadcastChannel();
1882
- }
2029
+ get isAuthenticationRequired() {
2030
+ return !!this.configuration.token && !this.isAuthenticated;
1883
2031
  }
1884
2032
  disconnect() {
1885
2033
  this.shouldConnect = false;
@@ -1894,27 +2042,24 @@ class HocuspocusProvider extends EventEmitter {
1894
2042
  //
1895
2043
  }
1896
2044
  }
1897
- createWebSocketConnection() {
1898
- if (this.webSocket !== null) {
2045
+ async onOpen(event) {
2046
+ this.emit('open', { event });
2047
+ if (this.isAuthenticationRequired) {
2048
+ this.send(AuthenticationMessage, {
2049
+ token: await this.getToken(),
2050
+ });
1899
2051
  return;
1900
2052
  }
1901
- this.webSocket = new this.options.WebSocketPolyfill(this.url);
1902
- this.webSocket.binaryType = 'arraybuffer';
1903
- this.status = exports.WebSocketStatus.Connecting;
1904
- this.synced = false;
1905
- this.webSocket.onmessage = this.onMessage.bind(this);
1906
- this.webSocket.onclose = this.onClose.bind(this);
1907
- this.webSocket.onopen = this.onOpen.bind(this);
1908
- this.emit('status', { status: 'connecting' });
2053
+ this.startSync();
1909
2054
  }
1910
- onOpen(event) {
1911
- this.emit('open', { event });
2055
+ async getToken() {
2056
+ if (typeof this.configuration.token === 'function') {
2057
+ const token = await this.configuration.token();
2058
+ return token;
2059
+ }
2060
+ return this.configuration.token;
1912
2061
  }
1913
- webSocketConnectionEstablished() {
1914
- this.failedConnectionAttempts = 0;
1915
- this.status = exports.WebSocketStatus.Connected;
1916
- this.emit('status', { status: 'connected' });
1917
- this.emit('connect');
2062
+ startSync() {
1918
2063
  this.send(SyncStepOneMessage, { document: this.document });
1919
2064
  if (this.awareness.getLocalState() !== null) {
1920
2065
  this.send(AwarenessMessage, {
@@ -1924,56 +2069,66 @@ class HocuspocusProvider extends EventEmitter {
1924
2069
  }
1925
2070
  }
1926
2071
  send(Message, args, broadcast = false) {
1927
- new Message();
2072
+ var _a;
1928
2073
  if (broadcast) {
1929
- this.mux(() => {
1930
- this.broadcast(Message, args);
1931
- });
2074
+ this.mux(() => { this.broadcast(Message, args); });
1932
2075
  }
1933
- if (this.status === exports.WebSocketStatus.Connected) {
2076
+ if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === common.WsReadyStates.Open) {
1934
2077
  const messageSender = new MessageSender(Message, args);
1935
2078
  this.emit('outgoingMessage', { message: messageSender.message });
1936
2079
  messageSender.send(this.webSocket);
1937
2080
  }
1938
2081
  }
1939
2082
  onMessage(event) {
1940
- if (this.status !== exports.WebSocketStatus.Connected) {
1941
- this.webSocketConnectionEstablished();
1942
- }
2083
+ this.resolveConnectionAttempt();
1943
2084
  this.lastMessageReceived = getUnixTime();
1944
2085
  const message = new IncomingMessage(event.data);
1945
2086
  this.emit('message', { event, message });
1946
- new MessageReceiver(message, this).apply(this);
1947
- // TODO: What’s that doing?
1948
- // if (encoding.length(encoder) > 1) {
1949
- // this.send(encoding.toUint8Array(encoder))
1950
- // }
2087
+ new MessageReceiver(message).apply(this);
1951
2088
  }
1952
2089
  onClose(event) {
1953
2090
  this.emit('close', { event });
1954
2091
  this.webSocket = null;
2092
+ this.isAuthenticated = false;
2093
+ this.synced = false;
1955
2094
  if (this.status === exports.WebSocketStatus.Connected) {
1956
- this.synced = false;
1957
2095
  // update awareness (all users except local left)
1958
2096
  removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
1959
2097
  this.status = exports.WebSocketStatus.Disconnected;
1960
- this.emit('status', { status: 'disconnected' });
2098
+ this.emit('status', { status: exports.WebSocketStatus.Disconnected });
1961
2099
  this.emit('disconnect', { event });
1962
2100
  }
1963
- else {
1964
- this.failedConnectionAttempts += 1;
2101
+ if (event.code === common.Unauthorized.code) {
2102
+ if (!this.configuration.quiet) {
2103
+ 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.');
2104
+ }
2105
+ this.shouldConnect = false;
1965
2106
  }
2107
+ if (event.code === common.Forbidden.code) {
2108
+ if (!this.configuration.quiet) {
2109
+ console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2110
+ }
2111
+ }
2112
+ if (this.connectionAttempt) {
2113
+ // That connection attempt failed.
2114
+ this.rejectConnectionAttempt();
2115
+ }
2116
+ else if (this.shouldConnect) {
2117
+ // The connection was closed by the server. Let’s just try again.
2118
+ this.connect();
2119
+ }
2120
+ // If we’ll reconnect, we’re done for now.
1966
2121
  if (this.shouldConnect) {
1967
- const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
1968
- this.log(`[close] Reconnecting in ${wait}ms …`);
1969
- setTimeout(this.createWebSocketConnection.bind(this), wait);
1970
2122
  return;
1971
2123
  }
1972
- if (this.status !== exports.WebSocketStatus.Disconnected) {
1973
- this.status = exports.WebSocketStatus.Disconnected;
1974
- this.emit('status', { status: 'disconnected' });
1975
- this.emit('disconnect', { event });
2124
+ // The status is set correctly already.
2125
+ if (this.status === exports.WebSocketStatus.Disconnected) {
2126
+ return;
1976
2127
  }
2128
+ // Let’s update the connection status.
2129
+ this.status = exports.WebSocketStatus.Disconnected;
2130
+ this.emit('status', { status: exports.WebSocketStatus.Disconnected });
2131
+ this.emit('disconnect', { event });
1977
2132
  }
1978
2133
  destroy() {
1979
2134
  this.emit('destroy');
@@ -1981,27 +2136,35 @@ class HocuspocusProvider extends EventEmitter {
1981
2136
  clearInterval(this.intervals.forceSync);
1982
2137
  }
1983
2138
  clearInterval(this.intervals.connectionChecker);
2139
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2140
+ // If there is still a connection attempt outstanding then we should stop
2141
+ // it before calling disconnect, otherwise it will be rejected in the onClose
2142
+ // handler and trigger a retry
2143
+ this.stopConnectionAttempt();
1984
2144
  this.disconnect();
1985
2145
  this.awareness.off('update', this.awarenessUpdateHandler);
1986
2146
  this.document.off('update', this.documentUpdateHandler);
1987
2147
  this.removeAllListeners();
2148
+ if (typeof window === 'undefined') {
2149
+ return;
2150
+ }
2151
+ window.removeEventListener('online', this.boundConnect);
2152
+ window.removeEventListener('beforeunload', this.boundBeforeUnload);
1988
2153
  }
1989
2154
  get broadcastChannel() {
1990
- return `${this.serverUrl}/${this.options.name}`;
2155
+ return `${this.serverUrl}/${this.configuration.name}`;
1991
2156
  }
1992
2157
  broadcastChannelSubscriber(data) {
1993
2158
  this.mux(() => {
1994
2159
  const message = new IncomingMessage(data);
1995
- new MessageReceiver(message, this).apply(this, false);
1996
- // TODO: What’s that doing?
1997
- // if (encoding.length(encoder) > 1) {
1998
- // this.broadcast(encoding.toUint8Array(encoder))
1999
- // }
2160
+ new MessageReceiver(message)
2161
+ .setBroadcasted(true)
2162
+ .apply(this, false);
2000
2163
  });
2001
2164
  }
2002
2165
  subscribeToBroadcastChannel() {
2003
2166
  if (!this.subscribedToBroadcastChannel) {
2004
- subscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2167
+ subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2005
2168
  this.subscribedToBroadcastChannel = true;
2006
2169
  }
2007
2170
  this.mux(() => {
@@ -2019,26 +2182,39 @@ class HocuspocusProvider extends EventEmitter {
2019
2182
  states: new Map(),
2020
2183
  }, true);
2021
2184
  if (this.subscribedToBroadcastChannel) {
2022
- unsubscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2185
+ unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2023
2186
  this.subscribedToBroadcastChannel = false;
2024
2187
  }
2025
2188
  }
2026
2189
  broadcast(Message, args) {
2027
- if (this.subscribedToBroadcastChannel) {
2028
- new MessageSender(Message, args).broadcast(this.broadcastChannel);
2190
+ if (!this.configuration.broadcast) {
2191
+ return;
2029
2192
  }
2030
- }
2031
- log(message) {
2032
- if (!this.options.debug) {
2193
+ if (!this.subscribedToBroadcastChannel) {
2033
2194
  return;
2034
2195
  }
2035
- console.log(message);
2196
+ new MessageSender(Message, args).broadcast(this.broadcastChannel);
2036
2197
  }
2037
2198
  setAwarenessField(key, value) {
2038
2199
  this.awareness.setLocalStateField(key, value);
2039
2200
  }
2040
2201
  }
2041
2202
 
2203
+ class HocuspocusCloudProvider extends HocuspocusProvider {
2204
+ constructor(configuration) {
2205
+ if (!configuration.url) {
2206
+ configuration.url = 'wss://connect.hocuspocus.cloud';
2207
+ }
2208
+ if (configuration.key) {
2209
+ if (!configuration.parameters) {
2210
+ configuration.parameters = {};
2211
+ }
2212
+ configuration.parameters.key = configuration.key;
2213
+ }
2214
+ super(configuration);
2215
+ }
2216
+ }
2217
+
2218
+ exports.HocuspocusCloudProvider = HocuspocusCloudProvider;
2042
2219
  exports.HocuspocusProvider = HocuspocusProvider;
2043
- exports.awarenessStatesToArray = awarenessStatesToArray;
2044
- //# sourceMappingURL=hocuspocus-provider.js.map
2220
+ //# sourceMappingURL=hocuspocus-provider.cjs.map