@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
@@ -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,107 +1735,210 @@ 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,
1752
+ broadcast: true,
1725
1753
  forceSyncInterval: false,
1726
- reconnectTimeoutBase: 1200,
1727
- maxReconnectTimeout: 2500,
1728
1754
  // TODO: this should depend on awareness.outdatedTime
1729
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,
1730
1774
  onOpen: () => null,
1731
1775
  onConnect: () => null,
1732
1776
  onMessage: () => null,
1733
1777
  onOutgoingMessage: () => null,
1778
+ onStatus: () => null,
1734
1779
  onSynced: () => null,
1735
1780
  onDisconnect: () => null,
1736
1781
  onClose: () => null,
1737
1782
  onDestroy: () => null,
1783
+ onAwarenessUpdate: () => null,
1738
1784
  onAwarenessChange: () => null,
1785
+ quiet: false,
1739
1786
  };
1740
1787
  this.subscribedToBroadcastChannel = false;
1741
1788
  this.webSocket = null;
1742
1789
  this.shouldConnect = true;
1743
1790
  this.status = WebSocketStatus.Disconnected;
1744
- this.failedConnectionAttempts = 0;
1745
1791
  this.isSynced = false;
1792
+ this.unsyncedChanges = 0;
1793
+ this.isAuthenticated = false;
1746
1794
  this.lastMessageReceived = 0;
1747
1795
  this.mux = createMutex();
1748
1796
  this.intervals = {
1749
1797
  forceSync: null,
1750
1798
  connectionChecker: null,
1751
1799
  };
1752
- this.setOptions(options);
1753
- this.options.document = options.document ? options.document : new Y.Doc();
1754
- this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
1755
- this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
1756
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
1757
- this.on('open', this.options.onOpen);
1758
- this.on('connect', this.options.onConnect);
1759
- this.on('message', this.options.onMessage);
1760
- this.on('outgoingMessage', this.options.onOutgoingMessage);
1761
- this.on('synced', this.options.onSynced);
1762
- this.on('disconnect', this.options.onDisconnect);
1763
- this.on('close', this.options.onClose);
1764
- this.on('destroy', this.options.onDestroy);
1765
- 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
+ });
1766
1824
  this.awareness.on('change', () => {
1767
- this.emit('awarenessChange', {
1768
- states: awarenessStatesToArray(this.awareness.getStates()),
1769
- });
1825
+ this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
1770
1826
  });
1771
- this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1772
1827
  this.document.on('update', this.documentUpdateHandler.bind(this));
1773
1828
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1774
- this.registerBeforeUnloadEventListener();
1775
- if (this.options.forceSyncInterval) {
1776
- 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);
1777
1833
  }
1778
- if (this.options.connect) {
1779
- this.connect();
1834
+ if (typeof configuration.connect !== 'undefined') {
1835
+ this.shouldConnect = configuration.connect;
1780
1836
  }
1837
+ if (!this.shouldConnect) {
1838
+ return;
1839
+ }
1840
+ this.connect();
1841
+ }
1842
+ setConfiguration(configuration = {}) {
1843
+ this.configuration = { ...this.configuration, ...configuration };
1844
+ }
1845
+ async connect() {
1846
+ if (this.status === WebSocketStatus.Connected) {
1847
+ return;
1848
+ }
1849
+ this.unsyncedChanges = 0; // set to 0 in case we got reconnected
1850
+ this.shouldConnect = true;
1851
+ this.subscribeToBroadcastChannel();
1852
+ try {
1853
+ await retry(this.createWebSocketConnection.bind(this), {
1854
+ delay: this.configuration.delay,
1855
+ initialDelay: this.configuration.initialDelay,
1856
+ factor: this.configuration.factor,
1857
+ maxAttempts: this.configuration.maxAttempts,
1858
+ minDelay: this.configuration.minDelay,
1859
+ maxDelay: this.configuration.maxDelay,
1860
+ jitter: this.configuration.jitter,
1861
+ timeout: this.configuration.timeout,
1862
+ beforeAttempt: context => {
1863
+ if (!this.shouldConnect) {
1864
+ context.abort();
1865
+ }
1866
+ },
1867
+ });
1868
+ }
1869
+ catch (error) {
1870
+ // If we aborted the connection attempt then don’t throw an error
1871
+ // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
1872
+ if (error && error.code !== 'ATTEMPT_ABORTED') {
1873
+ throw error;
1874
+ }
1875
+ }
1876
+ }
1877
+ createWebSocketConnection() {
1878
+ return new Promise((resolve, reject) => {
1879
+ // Init the WebSocket connection
1880
+ const ws = new this.configuration.WebSocketPolyfill(this.url);
1881
+ ws.binaryType = 'arraybuffer';
1882
+ ws.onmessage = this.onMessage.bind(this);
1883
+ ws.onclose = this.onClose.bind(this);
1884
+ ws.onopen = this.onOpen.bind(this);
1885
+ ws.onerror = (err) => {
1886
+ reject(err);
1887
+ };
1888
+ this.webSocket = ws;
1889
+ // Reset the status
1890
+ this.synced = false;
1891
+ this.status = WebSocketStatus.Connecting;
1892
+ this.emit('status', { status: WebSocketStatus.Connecting });
1893
+ // Store resolve/reject for later use
1894
+ this.connectionAttempt = {
1895
+ resolve,
1896
+ reject,
1897
+ };
1898
+ });
1899
+ }
1900
+ resolveConnectionAttempt() {
1901
+ var _a;
1902
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1903
+ this.connectionAttempt = null;
1904
+ this.status = WebSocketStatus.Connected;
1905
+ this.emit('status', { status: WebSocketStatus.Connected });
1906
+ this.emit('connect');
1907
+ }
1908
+ stopConnectionAttempt() {
1909
+ this.connectionAttempt = null;
1781
1910
  }
1782
- setOptions(options = {}) {
1783
- this.options = { ...this.options, ...options };
1911
+ rejectConnectionAttempt() {
1912
+ var _a;
1913
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1914
+ this.connectionAttempt = null;
1784
1915
  }
1785
1916
  get document() {
1786
- return this.options.document;
1917
+ return this.configuration.document;
1787
1918
  }
1788
1919
  get awareness() {
1789
- return this.options.awareness;
1920
+ return this.configuration.awareness;
1921
+ }
1922
+ get hasUnsyncedChanges() {
1923
+ return this.unsyncedChanges > 0;
1790
1924
  }
1791
1925
  checkConnection() {
1926
+ var _a;
1927
+ // Don’t check the connection when it’s not even established
1792
1928
  if (this.status !== WebSocketStatus.Connected) {
1793
1929
  return;
1794
1930
  }
1795
- if (this.options.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1931
+ // Don’t close then connection while waiting for the first message
1932
+ if (!this.lastMessageReceived) {
1933
+ return;
1934
+ }
1935
+ // Don’t close the connection when a message was received recently
1936
+ if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
1796
1937
  return;
1797
1938
  }
1798
1939
  // No message received in a long time, not even your own
1799
1940
  // Awareness updates, which are updated every 15 seconds.
1800
- this.webSocket.close();
1941
+ (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
1801
1942
  }
1802
1943
  forceSync() {
1803
1944
  if (!this.webSocket) {
@@ -1805,18 +1946,21 @@ class HocuspocusProvider extends EventEmitter {
1805
1946
  }
1806
1947
  this.send(SyncStepOneMessage, { document: this.document });
1807
1948
  }
1808
- registerBeforeUnloadEventListener() {
1949
+ beforeUnload() {
1950
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1951
+ }
1952
+ registerEventListeners() {
1809
1953
  if (typeof window === 'undefined') {
1810
1954
  return;
1811
1955
  }
1812
- window.addEventListener('beforeunload', () => {
1813
- removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1814
- });
1956
+ window.addEventListener('online', this.boundConnect);
1957
+ window.addEventListener('beforeunload', this.boundBeforeUnload);
1815
1958
  }
1816
1959
  documentUpdateHandler(update, origin) {
1817
1960
  if (origin === this) {
1818
1961
  return;
1819
1962
  }
1963
+ this.unsyncedChanges += 1;
1820
1964
  this.send(UpdateMessage, { update }, true);
1821
1965
  }
1822
1966
  awarenessUpdateHandler({ added, updated, removed }, origin) {
@@ -1826,16 +1970,26 @@ class HocuspocusProvider extends EventEmitter {
1826
1970
  clients: changedClients,
1827
1971
  }, true);
1828
1972
  }
1973
+ permissionDeniedHandler(reason) {
1974
+ this.emit('authenticationFailed', { reason });
1975
+ this.isAuthenticated = false;
1976
+ this.shouldConnect = false;
1977
+ }
1978
+ authenticatedHandler() {
1979
+ this.isAuthenticated = true;
1980
+ this.emit('authenticated');
1981
+ this.startSync();
1982
+ }
1829
1983
  // Ensure that the URL always ends with /
1830
1984
  get serverUrl() {
1831
- while (this.options.url[this.options.url.length - 1] === '/') {
1832
- return this.options.url.slice(0, this.options.url.length - 1);
1985
+ while (this.configuration.url[this.configuration.url.length - 1] === '/') {
1986
+ return this.configuration.url.slice(0, this.configuration.url.length - 1);
1833
1987
  }
1834
- return this.options.url;
1988
+ return this.configuration.url;
1835
1989
  }
1836
1990
  get url() {
1837
- const encodedParams = encodeQueryParams(this.options.parameters);
1838
- return `${this.serverUrl}/${this.options.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
1991
+ const encodedParams = encodeQueryParams(this.configuration.parameters);
1992
+ return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
1839
1993
  }
1840
1994
  get synced() {
1841
1995
  return this.isSynced;
@@ -1848,12 +2002,8 @@ class HocuspocusProvider extends EventEmitter {
1848
2002
  this.emit('synced', { state });
1849
2003
  this.emit('sync', { state });
1850
2004
  }
1851
- connect() {
1852
- this.shouldConnect = true;
1853
- if (this.status !== WebSocketStatus.Connected) {
1854
- this.createWebSocketConnection();
1855
- this.subscribeToBroadcastChannel();
1856
- }
2005
+ get isAuthenticationRequired() {
2006
+ return !!this.configuration.token && !this.isAuthenticated;
1857
2007
  }
1858
2008
  disconnect() {
1859
2009
  this.shouldConnect = false;
@@ -1868,27 +2018,24 @@ class HocuspocusProvider extends EventEmitter {
1868
2018
  //
1869
2019
  }
1870
2020
  }
1871
- createWebSocketConnection() {
1872
- if (this.webSocket !== null) {
2021
+ async onOpen(event) {
2022
+ this.emit('open', { event });
2023
+ if (this.isAuthenticationRequired) {
2024
+ this.send(AuthenticationMessage, {
2025
+ token: await this.getToken(),
2026
+ });
1873
2027
  return;
1874
2028
  }
1875
- this.webSocket = new this.options.WebSocketPolyfill(this.url);
1876
- this.webSocket.binaryType = 'arraybuffer';
1877
- this.status = WebSocketStatus.Connecting;
1878
- this.synced = false;
1879
- this.webSocket.onmessage = this.onMessage.bind(this);
1880
- this.webSocket.onclose = this.onClose.bind(this);
1881
- this.webSocket.onopen = this.onOpen.bind(this);
1882
- this.emit('status', { status: 'connecting' });
2029
+ this.startSync();
1883
2030
  }
1884
- onOpen(event) {
1885
- this.emit('open', { event });
2031
+ async getToken() {
2032
+ if (typeof this.configuration.token === 'function') {
2033
+ const token = await this.configuration.token();
2034
+ return token;
2035
+ }
2036
+ return this.configuration.token;
1886
2037
  }
1887
- webSocketConnectionEstablished() {
1888
- this.failedConnectionAttempts = 0;
1889
- this.status = WebSocketStatus.Connected;
1890
- this.emit('status', { status: 'connected' });
1891
- this.emit('connect');
2038
+ startSync() {
1892
2039
  this.send(SyncStepOneMessage, { document: this.document });
1893
2040
  if (this.awareness.getLocalState() !== null) {
1894
2041
  this.send(AwarenessMessage, {
@@ -1898,56 +2045,66 @@ class HocuspocusProvider extends EventEmitter {
1898
2045
  }
1899
2046
  }
1900
2047
  send(Message, args, broadcast = false) {
1901
- new Message();
2048
+ var _a;
1902
2049
  if (broadcast) {
1903
- this.mux(() => {
1904
- this.broadcast(Message, args);
1905
- });
2050
+ this.mux(() => { this.broadcast(Message, args); });
1906
2051
  }
1907
- if (this.status === WebSocketStatus.Connected) {
2052
+ if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === WsReadyStates.Open) {
1908
2053
  const messageSender = new MessageSender(Message, args);
1909
2054
  this.emit('outgoingMessage', { message: messageSender.message });
1910
2055
  messageSender.send(this.webSocket);
1911
2056
  }
1912
2057
  }
1913
2058
  onMessage(event) {
1914
- if (this.status !== WebSocketStatus.Connected) {
1915
- this.webSocketConnectionEstablished();
1916
- }
2059
+ this.resolveConnectionAttempt();
1917
2060
  this.lastMessageReceived = getUnixTime();
1918
2061
  const message = new IncomingMessage(event.data);
1919
2062
  this.emit('message', { event, message });
1920
- new MessageReceiver(message, this).apply(this);
1921
- // TODO: What’s that doing?
1922
- // if (encoding.length(encoder) > 1) {
1923
- // this.send(encoding.toUint8Array(encoder))
1924
- // }
2063
+ new MessageReceiver(message).apply(this);
1925
2064
  }
1926
2065
  onClose(event) {
1927
2066
  this.emit('close', { event });
1928
2067
  this.webSocket = null;
2068
+ this.isAuthenticated = false;
2069
+ this.synced = false;
1929
2070
  if (this.status === WebSocketStatus.Connected) {
1930
- this.synced = false;
1931
2071
  // update awareness (all users except local left)
1932
2072
  removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
1933
2073
  this.status = WebSocketStatus.Disconnected;
1934
- this.emit('status', { status: 'disconnected' });
2074
+ this.emit('status', { status: WebSocketStatus.Disconnected });
1935
2075
  this.emit('disconnect', { event });
1936
2076
  }
1937
- else {
1938
- this.failedConnectionAttempts += 1;
2077
+ if (event.code === Unauthorized.code) {
2078
+ if (!this.configuration.quiet) {
2079
+ 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.');
2080
+ }
2081
+ this.shouldConnect = false;
1939
2082
  }
2083
+ if (event.code === Forbidden.code) {
2084
+ if (!this.configuration.quiet) {
2085
+ console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2086
+ }
2087
+ }
2088
+ if (this.connectionAttempt) {
2089
+ // That connection attempt failed.
2090
+ this.rejectConnectionAttempt();
2091
+ }
2092
+ else if (this.shouldConnect) {
2093
+ // The connection was closed by the server. Let’s just try again.
2094
+ this.connect();
2095
+ }
2096
+ // If we’ll reconnect, we’re done for now.
1940
2097
  if (this.shouldConnect) {
1941
- const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
1942
- this.log(`[close] Reconnecting in ${wait}ms …`);
1943
- setTimeout(this.createWebSocketConnection.bind(this), wait);
1944
2098
  return;
1945
2099
  }
1946
- if (this.status !== WebSocketStatus.Disconnected) {
1947
- this.status = WebSocketStatus.Disconnected;
1948
- this.emit('status', { status: 'disconnected' });
1949
- this.emit('disconnect', { event });
2100
+ // The status is set correctly already.
2101
+ if (this.status === WebSocketStatus.Disconnected) {
2102
+ return;
1950
2103
  }
2104
+ // Let’s update the connection status.
2105
+ this.status = WebSocketStatus.Disconnected;
2106
+ this.emit('status', { status: WebSocketStatus.Disconnected });
2107
+ this.emit('disconnect', { event });
1951
2108
  }
1952
2109
  destroy() {
1953
2110
  this.emit('destroy');
@@ -1955,27 +2112,35 @@ class HocuspocusProvider extends EventEmitter {
1955
2112
  clearInterval(this.intervals.forceSync);
1956
2113
  }
1957
2114
  clearInterval(this.intervals.connectionChecker);
2115
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2116
+ // If there is still a connection attempt outstanding then we should stop
2117
+ // it before calling disconnect, otherwise it will be rejected in the onClose
2118
+ // handler and trigger a retry
2119
+ this.stopConnectionAttempt();
1958
2120
  this.disconnect();
1959
2121
  this.awareness.off('update', this.awarenessUpdateHandler);
1960
2122
  this.document.off('update', this.documentUpdateHandler);
1961
2123
  this.removeAllListeners();
2124
+ if (typeof window === 'undefined') {
2125
+ return;
2126
+ }
2127
+ window.removeEventListener('online', this.boundConnect);
2128
+ window.removeEventListener('beforeunload', this.boundBeforeUnload);
1962
2129
  }
1963
2130
  get broadcastChannel() {
1964
- return `${this.serverUrl}/${this.options.name}`;
2131
+ return `${this.serverUrl}/${this.configuration.name}`;
1965
2132
  }
1966
2133
  broadcastChannelSubscriber(data) {
1967
2134
  this.mux(() => {
1968
2135
  const message = new IncomingMessage(data);
1969
- new MessageReceiver(message, this).apply(this, false);
1970
- // TODO: What’s that doing?
1971
- // if (encoding.length(encoder) > 1) {
1972
- // this.broadcast(encoding.toUint8Array(encoder))
1973
- // }
2136
+ new MessageReceiver(message)
2137
+ .setBroadcasted(true)
2138
+ .apply(this, false);
1974
2139
  });
1975
2140
  }
1976
2141
  subscribeToBroadcastChannel() {
1977
2142
  if (!this.subscribedToBroadcastChannel) {
1978
- subscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2143
+ subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
1979
2144
  this.subscribedToBroadcastChannel = true;
1980
2145
  }
1981
2146
  this.mux(() => {
@@ -1993,25 +2158,38 @@ class HocuspocusProvider extends EventEmitter {
1993
2158
  states: new Map(),
1994
2159
  }, true);
1995
2160
  if (this.subscribedToBroadcastChannel) {
1996
- unsubscribe(this.broadcastChannel, this.broadcastChannelSubscriber.bind(this));
2161
+ unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
1997
2162
  this.subscribedToBroadcastChannel = false;
1998
2163
  }
1999
2164
  }
2000
2165
  broadcast(Message, args) {
2001
- if (this.subscribedToBroadcastChannel) {
2002
- new MessageSender(Message, args).broadcast(this.broadcastChannel);
2166
+ if (!this.configuration.broadcast) {
2167
+ return;
2003
2168
  }
2004
- }
2005
- log(message) {
2006
- if (!this.options.debug) {
2169
+ if (!this.subscribedToBroadcastChannel) {
2007
2170
  return;
2008
2171
  }
2009
- console.log(message);
2172
+ new MessageSender(Message, args).broadcast(this.broadcastChannel);
2010
2173
  }
2011
2174
  setAwarenessField(key, value) {
2012
2175
  this.awareness.setLocalStateField(key, value);
2013
2176
  }
2014
2177
  }
2015
2178
 
2016
- export { HocuspocusProvider, MessageType, WebSocketStatus, awarenessStatesToArray };
2179
+ class HocuspocusCloudProvider extends HocuspocusProvider {
2180
+ constructor(configuration) {
2181
+ if (!configuration.url) {
2182
+ configuration.url = 'wss://connect.hocuspocus.cloud';
2183
+ }
2184
+ if (configuration.key) {
2185
+ if (!configuration.parameters) {
2186
+ configuration.parameters = {};
2187
+ }
2188
+ configuration.parameters.key = configuration.key;
2189
+ }
2190
+ super(configuration);
2191
+ }
2192
+ }
2193
+
2194
+ export { HocuspocusCloudProvider, HocuspocusProvider, MessageType, WebSocketStatus };
2017
2195
  //# sourceMappingURL=hocuspocus-provider.esm.js.map