@hocuspocus/provider 1.0.2 → 2.0.0-alpha.0

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 (45) hide show
  1. package/dist/hocuspocus-provider.cjs +370 -209
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +370 -210
  4. package/dist/hocuspocus-provider.esm.js.map +1 -1
  5. package/dist/packages/extension-monitor/src/Dashboard.d.ts +1 -1
  6. package/dist/packages/extension-monitor/src/index.d.ts +1 -1
  7. package/dist/packages/extension-redis/src/Redis.d.ts +19 -5
  8. package/dist/packages/provider/src/HocuspocusCloudProvider.d.ts +2 -1
  9. package/dist/packages/provider/src/HocuspocusProvider.d.ts +14 -70
  10. package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +115 -0
  11. package/dist/packages/provider/src/IncomingMessage.d.ts +2 -0
  12. package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +7 -0
  13. package/dist/packages/provider/src/index.d.ts +1 -0
  14. package/dist/packages/provider/src/types.d.ts +7 -1
  15. package/dist/packages/server/src/Connection.d.ts +10 -11
  16. package/dist/packages/server/src/Document.d.ts +9 -0
  17. package/dist/packages/server/src/Hocuspocus.d.ts +3 -8
  18. package/dist/packages/server/src/IncomingMessage.d.ts +2 -0
  19. package/dist/packages/server/src/MessageReceiver.d.ts +1 -2
  20. package/dist/packages/server/src/OutgoingMessage.d.ts +3 -1
  21. package/dist/packages/server/src/types.d.ts +19 -3
  22. package/dist/packages/transformer/src/Prosemirror.d.ts +1 -1
  23. package/dist/tests/providerwebsocket/configuration.d.ts +1 -0
  24. package/dist/tests/server/beforeBroadcastStateless.d.ts +1 -0
  25. package/dist/tests/server/onStateless.d.ts +1 -0
  26. package/dist/tests/utils/index.d.ts +1 -0
  27. package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
  28. package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +3 -0
  29. package/package.json +3 -3
  30. package/src/HocuspocusCloudProvider.ts +8 -1
  31. package/src/HocuspocusProvider.ts +111 -358
  32. package/src/HocuspocusProviderWebsocket.ts +475 -0
  33. package/src/IncomingMessage.ts +10 -0
  34. package/src/MessageReceiver.ts +9 -2
  35. package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
  36. package/src/OutgoingMessages/AwarenessMessage.ts +1 -0
  37. package/src/OutgoingMessages/QueryAwarenessMessage.ts +5 -0
  38. package/src/OutgoingMessages/StatelessMessage.ts +17 -0
  39. package/src/OutgoingMessages/SyncStepOneMessage.ts +1 -0
  40. package/src/OutgoingMessages/SyncStepTwoMessage.ts +1 -1
  41. package/src/OutgoingMessages/UpdateMessage.ts +3 -1
  42. package/src/index.ts +1 -0
  43. package/src/types.ts +7 -0
  44. /package/dist/tests/{provider/configuration.d.ts → extension-redis/onStateless.d.ts} +0 -0
  45. /package/dist/tests/{server/getDocumentName.d.ts → provider/onStateless.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import * as Y from 'yjs';
2
- import { retry } from '@lifeomic/attempt';
3
2
  import { readAuthMessage, writeAuthentication, awarenessStatesToArray, WsReadyStates, Unauthorized, Forbidden } from '@hocuspocus/common';
3
+ import { retry } from '@lifeomic/attempt';
4
4
 
5
5
  /**
6
6
  * Utility module to work with key-value stores.
@@ -1324,19 +1324,6 @@ const createMutex = () => {
1324
1324
  }
1325
1325
  };
1326
1326
 
1327
- /**
1328
- * Utility module to work with urls.
1329
- *
1330
- * @module url
1331
- */
1332
-
1333
- /**
1334
- * @param {Object<string,string>} params
1335
- * @return {string}
1336
- */
1337
- const encodeQueryParams = params =>
1338
- map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
1339
-
1340
1327
  class EventEmitter {
1341
1328
  constructor() {
1342
1329
  this.callbacks = {};
@@ -1381,12 +1368,18 @@ class IncomingMessage {
1381
1368
  readVarUint() {
1382
1369
  return readVarUint(this.decoder);
1383
1370
  }
1371
+ readVarString() {
1372
+ return readVarString(this.decoder);
1373
+ }
1384
1374
  readVarUint8Array() {
1385
1375
  return readVarUint8Array(this.decoder);
1386
1376
  }
1387
1377
  writeVarUint(type) {
1388
1378
  return writeVarUint(this.encoder, type);
1389
1379
  }
1380
+ writeVarString(string) {
1381
+ return writeVarString(this.encoder, string);
1382
+ }
1390
1383
  writeVarUint8Array(data) {
1391
1384
  return writeVarUint8Array(this.encoder, data);
1392
1385
  }
@@ -1528,6 +1521,7 @@ var MessageType;
1528
1521
  MessageType[MessageType["Awareness"] = 1] = "Awareness";
1529
1522
  MessageType[MessageType["Auth"] = 2] = "Auth";
1530
1523
  MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1524
+ MessageType[MessageType["Stateless"] = 5] = "Stateless";
1531
1525
  })(MessageType || (MessageType = {}));
1532
1526
  var WebSocketStatus;
1533
1527
  (function (WebSocketStatus) {
@@ -1560,6 +1554,7 @@ class MessageReceiver {
1560
1554
  apply(provider, emitSynced = true) {
1561
1555
  const { message } = this;
1562
1556
  const type = message.readVarUint();
1557
+ const emptyMessageLength = message.length();
1563
1558
  switch (type) {
1564
1559
  case MessageType.Sync:
1565
1560
  this.applySyncMessage(provider, emitSynced);
@@ -1573,11 +1568,14 @@ class MessageReceiver {
1573
1568
  case MessageType.QueryAwareness:
1574
1569
  this.applyQueryAwarenessMessage(provider);
1575
1570
  break;
1571
+ case MessageType.Stateless:
1572
+ provider.receiveStateless(readVarString(message.decoder));
1573
+ break;
1576
1574
  default:
1577
1575
  throw new Error(`Can’t apply message of unknown type: ${type}`);
1578
1576
  }
1579
1577
  // Reply
1580
- if (message.length() > 1) {
1578
+ if (message.length() > emptyMessageLength + 1) { // length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)
1581
1579
  if (this.broadcasted) {
1582
1580
  // TODO: Some weird TypeScript error
1583
1581
  // @ts-ignore
@@ -1596,7 +1594,7 @@ class MessageReceiver {
1596
1594
  // Apply update
1597
1595
  const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
1598
1596
  // Synced once we receive Step2
1599
- if (emitSynced && (syncMessageType === messageYjsSyncStep2)) {
1597
+ if (emitSynced && syncMessageType === messageYjsSyncStep2) {
1600
1598
  provider.synced = true;
1601
1599
  }
1602
1600
  if (syncMessageType === messageYjsUpdate || syncMessageType === messageYjsSyncStep2) {
@@ -1646,6 +1644,7 @@ class SyncStepOneMessage extends OutgoingMessage {
1646
1644
  if (typeof args.document === 'undefined') {
1647
1645
  throw new Error('The sync step one message requires document as an argument');
1648
1646
  }
1647
+ writeVarString(this.encoder, args.documentName);
1649
1648
  writeVarUint(this.encoder, this.type);
1650
1649
  writeSyncStep1(this.encoder, args.document);
1651
1650
  return this.encoder;
@@ -1662,6 +1661,7 @@ class SyncStepTwoMessage extends OutgoingMessage {
1662
1661
  if (typeof args.document === 'undefined') {
1663
1662
  throw new Error('The sync step two message requires document as an argument');
1664
1663
  }
1664
+ writeVarString(this.encoder, args.documentName);
1665
1665
  writeVarUint(this.encoder, this.type);
1666
1666
  writeSyncStep2(this.encoder, args.document);
1667
1667
  return this.encoder;
@@ -1675,6 +1675,9 @@ class QueryAwarenessMessage extends OutgoingMessage {
1675
1675
  this.description = 'Queries awareness states';
1676
1676
  }
1677
1677
  get(args) {
1678
+ console.log('queryAwareness: writing string docName', args.documentName);
1679
+ console.log(this.encoder.cpos);
1680
+ writeVarString(this.encoder, args.documentName);
1678
1681
  writeVarUint(this.encoder, this.type);
1679
1682
  return this.encoder;
1680
1683
  }
@@ -1690,6 +1693,7 @@ class AuthenticationMessage extends OutgoingMessage {
1690
1693
  if (typeof args.token === 'undefined') {
1691
1694
  throw new Error('The authentication message requires `token` as an argument.');
1692
1695
  }
1696
+ writeVarString(this.encoder, args.documentName);
1693
1697
  writeVarUint(this.encoder, this.type);
1694
1698
  writeAuthentication(this.encoder, args.token);
1695
1699
  return this.encoder;
@@ -1709,6 +1713,7 @@ class AwarenessMessage extends OutgoingMessage {
1709
1713
  if (typeof args.clients === 'undefined') {
1710
1714
  throw new Error('The awareness message requires clients as an argument');
1711
1715
  }
1716
+ writeVarString(this.encoder, args.documentName);
1712
1717
  writeVarUint(this.encoder, this.type);
1713
1718
  let awarenessUpdate;
1714
1719
  if (args.states === undefined) {
@@ -1729,24 +1734,329 @@ class UpdateMessage extends OutgoingMessage {
1729
1734
  this.description = 'A document update';
1730
1735
  }
1731
1736
  get(args) {
1737
+ writeVarString(this.encoder, args.documentName);
1732
1738
  writeVarUint(this.encoder, this.type);
1733
1739
  writeUpdate(this.encoder, args.update);
1734
1740
  return this.encoder;
1735
1741
  }
1736
1742
  }
1737
1743
 
1744
+ class StatelessMessage extends OutgoingMessage {
1745
+ constructor() {
1746
+ super(...arguments);
1747
+ this.type = MessageType.Stateless;
1748
+ this.description = 'A stateless message';
1749
+ }
1750
+ get(args) {
1751
+ var _a;
1752
+ writeVarString(this.encoder, args.documentName);
1753
+ writeVarUint(this.encoder, this.type);
1754
+ writeVarString(this.encoder, (_a = args.payload) !== null && _a !== void 0 ? _a : '');
1755
+ return this.encoder;
1756
+ }
1757
+ }
1758
+
1738
1759
  class HocuspocusProvider extends EventEmitter {
1739
1760
  constructor(configuration) {
1740
1761
  super();
1741
1762
  this.configuration = {
1742
1763
  name: '',
1764
+ // @ts-ignore
1765
+ document: undefined,
1766
+ // @ts-ignore
1767
+ awareness: undefined,
1768
+ token: null,
1769
+ parameters: {},
1770
+ broadcast: true,
1771
+ forceSyncInterval: false,
1772
+ onAuthenticated: () => null,
1773
+ onAuthenticationFailed: () => null,
1774
+ onOpen: () => null,
1775
+ onConnect: () => null,
1776
+ onMessage: () => null,
1777
+ onOutgoingMessage: () => null,
1778
+ onStatus: () => null,
1779
+ onSynced: () => null,
1780
+ onDisconnect: () => null,
1781
+ onClose: () => null,
1782
+ onDestroy: () => null,
1783
+ onAwarenessUpdate: () => null,
1784
+ onAwarenessChange: () => null,
1785
+ onStateless: () => null,
1786
+ quiet: false,
1787
+ };
1788
+ this.subscribedToBroadcastChannel = false;
1789
+ this.isSynced = false;
1790
+ this.unsyncedChanges = 0;
1791
+ this.status = WebSocketStatus.Disconnected;
1792
+ this.isAuthenticated = false;
1793
+ this.mux = createMutex();
1794
+ this.intervals = {
1795
+ forceSync: null,
1796
+ };
1797
+ this.boundBeforeUnload = this.beforeUnload.bind(this);
1798
+ this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
1799
+ this.setConfiguration(configuration);
1800
+ this.configuration.document = configuration.document ? configuration.document : new Y.Doc();
1801
+ this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document);
1802
+ this.on('open', this.configuration.onOpen);
1803
+ this.on('message', this.configuration.onMessage);
1804
+ this.on('outgoingMessage', this.configuration.onOutgoingMessage);
1805
+ this.on('synced', this.configuration.onSynced);
1806
+ this.on('destroy', this.configuration.onDestroy);
1807
+ this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
1808
+ this.on('awarenessChange', this.configuration.onAwarenessChange);
1809
+ this.on('stateless', this.configuration.onStateless);
1810
+ this.on('authenticated', this.configuration.onAuthenticated);
1811
+ this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
1812
+ this.configuration.websocketProvider.on('connect', this.configuration.onConnect);
1813
+ this.configuration.websocketProvider.on('connect', (e) => this.emit('connect', e));
1814
+ this.configuration.websocketProvider.on('open', this.onOpen.bind(this));
1815
+ this.configuration.websocketProvider.on('open', (e) => this.emit('open', e));
1816
+ this.configuration.websocketProvider.on('message', this.onMessage.bind(this));
1817
+ this.configuration.websocketProvider.on('close', this.onClose.bind(this));
1818
+ this.configuration.websocketProvider.on('close', this.configuration.onClose);
1819
+ this.configuration.websocketProvider.on('close', (e) => this.emit('close', e));
1820
+ this.configuration.websocketProvider.on('status', this.onStatus.bind(this));
1821
+ this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect);
1822
+ this.configuration.websocketProvider.on('disconnect', (e) => this.emit('disconnect', e));
1823
+ this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy);
1824
+ this.configuration.websocketProvider.on('destroy', (e) => this.emit('destroy', e));
1825
+ this.awareness.on('update', () => {
1826
+ this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) });
1827
+ });
1828
+ this.awareness.on('change', () => {
1829
+ this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
1830
+ });
1831
+ this.document.on('update', this.documentUpdateHandler.bind(this));
1832
+ this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1833
+ this.registerEventListeners();
1834
+ if (this.configuration.forceSyncInterval) {
1835
+ this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
1836
+ }
1837
+ this.configuration.websocketProvider.attach(this);
1838
+ }
1839
+ onStatus({ status }) {
1840
+ this.status = status;
1841
+ this.configuration.onStatus({ status });
1842
+ this.emit('status', { status });
1843
+ }
1844
+ setConfiguration(configuration = {}) {
1845
+ this.configuration = { ...this.configuration, ...configuration };
1846
+ }
1847
+ get document() {
1848
+ return this.configuration.document;
1849
+ }
1850
+ get awareness() {
1851
+ return this.configuration.awareness;
1852
+ }
1853
+ get hasUnsyncedChanges() {
1854
+ return this.unsyncedChanges > 0;
1855
+ }
1856
+ forceSync() {
1857
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
1858
+ }
1859
+ beforeUnload() {
1860
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1861
+ }
1862
+ registerEventListeners() {
1863
+ if (typeof window === 'undefined') {
1864
+ return;
1865
+ }
1866
+ window.addEventListener('beforeunload', this.boundBeforeUnload);
1867
+ }
1868
+ sendStateless(payload) {
1869
+ this.send(StatelessMessage, { documentName: this.configuration.name, payload });
1870
+ }
1871
+ documentUpdateHandler(update, origin) {
1872
+ if (origin === this) {
1873
+ return;
1874
+ }
1875
+ this.unsyncedChanges += 1;
1876
+ this.send(UpdateMessage, { update, documentName: this.configuration.name }, true);
1877
+ }
1878
+ awarenessUpdateHandler({ added, updated, removed }, origin) {
1879
+ const changedClients = added.concat(updated).concat(removed);
1880
+ this.send(AwarenessMessage, {
1881
+ awareness: this.awareness,
1882
+ clients: changedClients,
1883
+ documentName: this.configuration.name,
1884
+ }, true);
1885
+ }
1886
+ get synced() {
1887
+ return this.isSynced;
1888
+ }
1889
+ set synced(state) {
1890
+ if (this.isSynced === state) {
1891
+ return;
1892
+ }
1893
+ this.isSynced = state;
1894
+ this.emit('synced', { state });
1895
+ this.emit('sync', { state });
1896
+ }
1897
+ receiveStateless(payload) {
1898
+ this.emit('stateless', { payload });
1899
+ }
1900
+ get isAuthenticationRequired() {
1901
+ return !!this.configuration.token && !this.isAuthenticated;
1902
+ }
1903
+ disconnect() {
1904
+ this.disconnectBroadcastChannel();
1905
+ this.configuration.websocketProvider.detach(this);
1906
+ }
1907
+ async onOpen(event) {
1908
+ this.emit('open', { event });
1909
+ if (this.isAuthenticationRequired) {
1910
+ this.send(AuthenticationMessage, {
1911
+ token: await this.getToken(),
1912
+ documentName: this.configuration.name,
1913
+ });
1914
+ return;
1915
+ }
1916
+ this.startSync();
1917
+ }
1918
+ async getToken() {
1919
+ if (typeof this.configuration.token === 'function') {
1920
+ const token = await this.configuration.token();
1921
+ return token;
1922
+ }
1923
+ return this.configuration.token;
1924
+ }
1925
+ startSync() {
1926
+ this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
1927
+ if (this.awareness.getLocalState() !== null) {
1928
+ this.send(AwarenessMessage, {
1929
+ awareness: this.awareness,
1930
+ clients: [this.document.clientID],
1931
+ documentName: this.configuration.name,
1932
+ });
1933
+ }
1934
+ }
1935
+ send(message, args, broadcast = false) {
1936
+ if (broadcast) {
1937
+ this.mux(() => { this.broadcast(message, args); });
1938
+ }
1939
+ const messageSender = new MessageSender(message, args);
1940
+ this.emit('outgoingMessage', { message: messageSender.message });
1941
+ messageSender.send(this.configuration.websocketProvider);
1942
+ }
1943
+ onMessage(event) {
1944
+ const message = new IncomingMessage(event.data);
1945
+ const documentName = message.readVarString();
1946
+ if (documentName !== this.configuration.name) {
1947
+ return; // message is meant for another provider
1948
+ }
1949
+ message.writeVarString(documentName);
1950
+ this.emit('message', { event, message: new IncomingMessage(event.data) });
1951
+ new MessageReceiver(message).apply(this);
1952
+ }
1953
+ onClose(event) {
1954
+ this.isAuthenticated = false;
1955
+ this.synced = false;
1956
+ // update awareness (all users except local left)
1957
+ removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
1958
+ }
1959
+ destroy() {
1960
+ this.emit('destroy');
1961
+ if (this.intervals.forceSync) {
1962
+ clearInterval(this.intervals.forceSync);
1963
+ }
1964
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
1965
+ this.disconnect();
1966
+ this.awareness.off('update', this.awarenessUpdateHandler);
1967
+ this.document.off('update', this.documentUpdateHandler);
1968
+ this.removeAllListeners();
1969
+ if (typeof window === 'undefined') {
1970
+ return;
1971
+ }
1972
+ window.removeEventListener('beforeunload', this.boundBeforeUnload);
1973
+ }
1974
+ permissionDeniedHandler(reason) {
1975
+ this.emit('authenticationFailed', { reason });
1976
+ this.isAuthenticated = false;
1977
+ this.disconnect();
1978
+ this.status = WebSocketStatus.Disconnected;
1979
+ }
1980
+ authenticatedHandler() {
1981
+ this.isAuthenticated = true;
1982
+ this.emit('authenticated');
1983
+ this.startSync();
1984
+ }
1985
+ get broadcastChannel() {
1986
+ return `${this.configuration.name}`;
1987
+ }
1988
+ broadcastChannelSubscriber(data) {
1989
+ this.mux(() => {
1990
+ const message = new IncomingMessage(data);
1991
+ const documentName = message.readVarString();
1992
+ message.writeVarString(documentName);
1993
+ new MessageReceiver(message)
1994
+ .setBroadcasted(true)
1995
+ .apply(this, false);
1996
+ });
1997
+ }
1998
+ subscribeToBroadcastChannel() {
1999
+ if (!this.subscribedToBroadcastChannel) {
2000
+ subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2001
+ this.subscribedToBroadcastChannel = true;
2002
+ }
2003
+ this.mux(() => {
2004
+ this.broadcast(SyncStepOneMessage, { document: this.document });
2005
+ this.broadcast(SyncStepTwoMessage, { document: this.document });
2006
+ this.broadcast(QueryAwarenessMessage, { document: this.document });
2007
+ this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], document: this.document });
2008
+ });
2009
+ }
2010
+ disconnectBroadcastChannel() {
2011
+ // broadcast message with local awareness state set to null (indicating disconnect)
2012
+ this.send(AwarenessMessage, {
2013
+ awareness: this.awareness,
2014
+ clients: [this.document.clientID],
2015
+ states: new Map(),
2016
+ documentName: this.configuration.name,
2017
+ }, true);
2018
+ if (this.subscribedToBroadcastChannel) {
2019
+ unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2020
+ this.subscribedToBroadcastChannel = false;
2021
+ }
2022
+ }
2023
+ broadcast(Message, args) {
2024
+ if (!this.configuration.broadcast) {
2025
+ return;
2026
+ }
2027
+ if (!this.subscribedToBroadcastChannel) {
2028
+ return;
2029
+ }
2030
+ new MessageSender(Message, args).broadcast(this.broadcastChannel);
2031
+ }
2032
+ setAwarenessField(key, value) {
2033
+ this.awareness.setLocalStateField(key, value);
2034
+ }
2035
+ }
2036
+
2037
+ /**
2038
+ * Utility module to work with urls.
2039
+ *
2040
+ * @module url
2041
+ */
2042
+
2043
+ /**
2044
+ * @param {Object<string,string>} params
2045
+ * @return {string}
2046
+ */
2047
+ const encodeQueryParams = params =>
2048
+ map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
2049
+
2050
+ class HocuspocusProviderWebsocket extends EventEmitter {
2051
+ constructor(configuration) {
2052
+ super();
2053
+ this.configuration = {
1743
2054
  url: '',
1744
2055
  // @ts-ignore
1745
2056
  document: undefined,
1746
2057
  // @ts-ignore
1747
2058
  awareness: undefined,
1748
2059
  WebSocketPolyfill: undefined,
1749
- token: null,
1750
2060
  parameters: {},
1751
2061
  connect: true,
1752
2062
  broadcast: true,
@@ -1769,14 +2079,11 @@ class HocuspocusProvider extends EventEmitter {
1769
2079
  jitter: true,
1770
2080
  // retry forever
1771
2081
  timeout: 0,
1772
- onAuthenticated: () => null,
1773
- onAuthenticationFailed: () => null,
1774
2082
  onOpen: () => null,
1775
2083
  onConnect: () => null,
1776
2084
  onMessage: () => null,
1777
2085
  onOutgoingMessage: () => null,
1778
2086
  onStatus: () => null,
1779
- onSynced: () => null,
1780
2087
  onDisconnect: () => null,
1781
2088
  onClose: () => null,
1782
2089
  onDestroy: () => null,
@@ -1788,9 +2095,6 @@ class HocuspocusProvider extends EventEmitter {
1788
2095
  this.webSocket = null;
1789
2096
  this.shouldConnect = true;
1790
2097
  this.status = WebSocketStatus.Disconnected;
1791
- this.isSynced = false;
1792
- this.unsyncedChanges = 0;
1793
- this.isAuthenticated = false;
1794
2098
  this.lastMessageReceived = 0;
1795
2099
  this.mux = createMutex();
1796
2100
  this.intervals = {
@@ -1798,39 +2102,27 @@ class HocuspocusProvider extends EventEmitter {
1798
2102
  connectionChecker: null,
1799
2103
  };
1800
2104
  this.connectionAttempt = null;
2105
+ this.receivedOnOpenPayload = undefined;
2106
+ this.receivedOnStatusPayload = undefined;
1801
2107
  this.boundConnect = this.connect.bind(this);
1802
- this.boundBeforeUnload = this.beforeUnload.bind(this);
1803
- this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
1804
2108
  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
2109
  this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket;
1808
2110
  this.on('open', this.configuration.onOpen);
1809
- this.on('authenticated', this.configuration.onAuthenticated);
1810
- this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
2111
+ this.on('open', this.onOpen.bind(this));
1811
2112
  this.on('connect', this.configuration.onConnect);
1812
2113
  this.on('message', this.configuration.onMessage);
1813
2114
  this.on('outgoingMessage', this.configuration.onOutgoingMessage);
1814
- this.on('synced', this.configuration.onSynced);
1815
2115
  this.on('status', this.configuration.onStatus);
2116
+ this.on('status', this.onStatus.bind(this));
1816
2117
  this.on('disconnect', this.configuration.onDisconnect);
1817
2118
  this.on('close', this.configuration.onClose);
1818
2119
  this.on('destroy', this.configuration.onDestroy);
1819
2120
  this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
1820
2121
  this.on('awarenessChange', this.configuration.onAwarenessChange);
1821
- this.awareness.on('update', () => {
1822
- this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) });
1823
- });
1824
- this.awareness.on('change', () => {
1825
- this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
1826
- });
1827
- this.document.on('update', this.documentUpdateHandler.bind(this));
1828
- this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
2122
+ this.on('close', this.onClose.bind(this));
2123
+ this.on('message', this.onMessage.bind(this));
1829
2124
  this.registerEventListeners();
1830
2125
  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);
1833
- }
1834
2126
  if (typeof configuration.connect !== 'undefined') {
1835
2127
  this.shouldConnect = configuration.connect;
1836
2128
  }
@@ -1839,6 +2131,23 @@ class HocuspocusProvider extends EventEmitter {
1839
2131
  }
1840
2132
  this.connect();
1841
2133
  }
2134
+ async onOpen(event) {
2135
+ this.receivedOnOpenPayload = event;
2136
+ }
2137
+ async onStatus(data) {
2138
+ this.receivedOnStatusPayload = data;
2139
+ }
2140
+ attach(provider) {
2141
+ if (this.receivedOnOpenPayload) {
2142
+ provider.onOpen(this.receivedOnOpenPayload);
2143
+ }
2144
+ if (this.receivedOnStatusPayload) {
2145
+ provider.onStatus(this.receivedOnStatusPayload);
2146
+ }
2147
+ }
2148
+ detach(provider) {
2149
+ // tell the server to remove the listener
2150
+ }
1842
2151
  setConfiguration(configuration = {}) {
1843
2152
  this.configuration = { ...this.configuration, ...configuration };
1844
2153
  }
@@ -1851,9 +2160,7 @@ class HocuspocusProvider extends EventEmitter {
1851
2160
  this.cancelWebsocketRetry();
1852
2161
  this.cancelWebsocketRetry = undefined;
1853
2162
  }
1854
- this.unsyncedChanges = 0; // set to 0 in case we got reconnected
1855
2163
  this.shouldConnect = true;
1856
- this.subscribeToBroadcastChannel();
1857
2164
  const abortableRetry = () => {
1858
2165
  let cancelAttempt = false;
1859
2166
  const retryPromise = retry(this.createWebSocketConnection.bind(this), {
@@ -1897,15 +2204,14 @@ class HocuspocusProvider extends EventEmitter {
1897
2204
  // Init the WebSocket connection
1898
2205
  const ws = new this.configuration.WebSocketPolyfill(this.url);
1899
2206
  ws.binaryType = 'arraybuffer';
1900
- ws.onmessage = this.onMessage.bind(this);
1901
- ws.onclose = this.onClose.bind(this);
1902
- ws.onopen = this.onOpen.bind(this);
2207
+ ws.onmessage = (payload) => this.emit('message', payload);
2208
+ ws.onclose = (payload) => this.emit('close', { event: payload });
2209
+ ws.onopen = (payload) => this.emit('open', payload);
1903
2210
  ws.onerror = (err) => {
1904
2211
  reject(err);
1905
2212
  };
1906
2213
  this.webSocket = ws;
1907
2214
  // Reset the status
1908
- this.synced = false;
1909
2215
  this.status = WebSocketStatus.Connecting;
1910
2216
  this.emit('status', { status: WebSocketStatus.Connecting });
1911
2217
  // Store resolve/reject for later use
@@ -1915,13 +2221,17 @@ class HocuspocusProvider extends EventEmitter {
1915
2221
  };
1916
2222
  });
1917
2223
  }
2224
+ onMessage(event) {
2225
+ this.resolveConnectionAttempt();
2226
+ }
1918
2227
  resolveConnectionAttempt() {
1919
- var _a;
1920
- (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1921
- this.connectionAttempt = null;
1922
- this.status = WebSocketStatus.Connected;
1923
- this.emit('status', { status: WebSocketStatus.Connected });
1924
- this.emit('connect');
2228
+ if (this.connectionAttempt) {
2229
+ this.connectionAttempt.resolve();
2230
+ this.connectionAttempt = null;
2231
+ this.status = WebSocketStatus.Connected;
2232
+ this.emit('status', { status: WebSocketStatus.Connected });
2233
+ this.emit('connect');
2234
+ }
1925
2235
  }
1926
2236
  stopConnectionAttempt() {
1927
2237
  this.connectionAttempt = null;
@@ -1931,15 +2241,6 @@ class HocuspocusProvider extends EventEmitter {
1931
2241
  (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1932
2242
  this.connectionAttempt = null;
1933
2243
  }
1934
- get document() {
1935
- return this.configuration.document;
1936
- }
1937
- get awareness() {
1938
- return this.configuration.awareness;
1939
- }
1940
- get hasUnsyncedChanges() {
1941
- return this.unsyncedChanges > 0;
1942
- }
1943
2244
  checkConnection() {
1944
2245
  var _a;
1945
2246
  // Don’t check the connection when it’s not even established
@@ -1958,45 +2259,11 @@ class HocuspocusProvider extends EventEmitter {
1958
2259
  // Awareness updates, which are updated every 15 seconds.
1959
2260
  (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
1960
2261
  }
1961
- forceSync() {
1962
- if (!this.webSocket) {
1963
- return;
1964
- }
1965
- this.send(SyncStepOneMessage, { document: this.document });
1966
- }
1967
- beforeUnload() {
1968
- removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
1969
- }
1970
2262
  registerEventListeners() {
1971
2263
  if (typeof window === 'undefined') {
1972
2264
  return;
1973
2265
  }
1974
2266
  window.addEventListener('online', this.boundConnect);
1975
- window.addEventListener('beforeunload', this.boundBeforeUnload);
1976
- }
1977
- documentUpdateHandler(update, origin) {
1978
- if (origin === this) {
1979
- return;
1980
- }
1981
- this.unsyncedChanges += 1;
1982
- this.send(UpdateMessage, { update }, true);
1983
- }
1984
- awarenessUpdateHandler({ added, updated, removed }, origin) {
1985
- const changedClients = added.concat(updated).concat(removed);
1986
- this.send(AwarenessMessage, {
1987
- awareness: this.awareness,
1988
- clients: changedClients,
1989
- }, true);
1990
- }
1991
- permissionDeniedHandler(reason) {
1992
- this.emit('authenticationFailed', { reason });
1993
- this.isAuthenticated = false;
1994
- this.shouldConnect = false;
1995
- }
1996
- authenticatedHandler() {
1997
- this.isAuthenticated = true;
1998
- this.emit('authenticated');
1999
- this.startSync();
2000
2267
  }
2001
2268
  // Ensure that the URL always ends with /
2002
2269
  get serverUrl() {
@@ -2007,25 +2274,10 @@ class HocuspocusProvider extends EventEmitter {
2007
2274
  }
2008
2275
  get url() {
2009
2276
  const encodedParams = encodeQueryParams(this.configuration.parameters);
2010
- return `${this.serverUrl}/${this.configuration.name}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
2011
- }
2012
- get synced() {
2013
- return this.isSynced;
2014
- }
2015
- set synced(state) {
2016
- if (this.isSynced === state) {
2017
- return;
2018
- }
2019
- this.isSynced = state;
2020
- this.emit('synced', { state });
2021
- this.emit('sync', { state });
2022
- }
2023
- get isAuthenticationRequired() {
2024
- return !!this.configuration.token && !this.isAuthenticated;
2277
+ return `${this.serverUrl}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
2025
2278
  }
2026
2279
  disconnect() {
2027
2280
  this.shouldConnect = false;
2028
- this.disconnectBroadcastChannel();
2029
2281
  if (this.webSocket === null) {
2030
2282
  return;
2031
2283
  }
@@ -2036,58 +2288,15 @@ class HocuspocusProvider extends EventEmitter {
2036
2288
  //
2037
2289
  }
2038
2290
  }
2039
- async onOpen(event) {
2040
- this.emit('open', { event });
2041
- if (this.isAuthenticationRequired) {
2042
- this.send(AuthenticationMessage, {
2043
- token: await this.getToken(),
2044
- });
2045
- return;
2046
- }
2047
- this.startSync();
2048
- }
2049
- async getToken() {
2050
- if (typeof this.configuration.token === 'function') {
2051
- const token = await this.configuration.token();
2052
- return token;
2053
- }
2054
- return this.configuration.token;
2055
- }
2056
- startSync() {
2057
- this.send(SyncStepOneMessage, { document: this.document });
2058
- if (this.awareness.getLocalState() !== null) {
2059
- this.send(AwarenessMessage, {
2060
- awareness: this.awareness,
2061
- clients: [this.document.clientID],
2062
- });
2063
- }
2064
- }
2065
- send(Message, args, broadcast = false) {
2291
+ send(message) {
2066
2292
  var _a;
2067
- if (broadcast) {
2068
- this.mux(() => { this.broadcast(Message, args); });
2069
- }
2070
2293
  if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === WsReadyStates.Open) {
2071
- const messageSender = new MessageSender(Message, args);
2072
- this.emit('outgoingMessage', { message: messageSender.message });
2073
- messageSender.send(this.webSocket);
2294
+ this.webSocket.send(message);
2074
2295
  }
2075
2296
  }
2076
- onMessage(event) {
2077
- this.resolveConnectionAttempt();
2078
- this.lastMessageReceived = getUnixTime();
2079
- const message = new IncomingMessage(event.data);
2080
- this.emit('message', { event, message });
2081
- new MessageReceiver(message).apply(this);
2082
- }
2083
- onClose(event) {
2084
- this.emit('close', { event });
2297
+ onClose({ event }) {
2085
2298
  this.webSocket = null;
2086
- this.isAuthenticated = false;
2087
- this.synced = false;
2088
2299
  if (this.status === WebSocketStatus.Connected) {
2089
- // update awareness (all users except local left)
2090
- removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
2091
2300
  this.status = WebSocketStatus.Disconnected;
2092
2301
  this.emit('status', { status: WebSocketStatus.Disconnected });
2093
2302
  this.emit('disconnect', { event });
@@ -2101,6 +2310,7 @@ class HocuspocusProvider extends EventEmitter {
2101
2310
  if (event.code === Forbidden.code) {
2102
2311
  if (!this.configuration.quiet) {
2103
2312
  console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2313
+ return; // TODO REMOVE ME
2104
2314
  }
2105
2315
  }
2106
2316
  if (this.connectionAttempt) {
@@ -2130,67 +2340,16 @@ class HocuspocusProvider extends EventEmitter {
2130
2340
  clearInterval(this.intervals.forceSync);
2131
2341
  }
2132
2342
  clearInterval(this.intervals.connectionChecker);
2133
- removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2134
2343
  // If there is still a connection attempt outstanding then we should stop
2135
2344
  // it before calling disconnect, otherwise it will be rejected in the onClose
2136
2345
  // handler and trigger a retry
2137
2346
  this.stopConnectionAttempt();
2138
2347
  this.disconnect();
2139
- this.awareness.off('update', this.awarenessUpdateHandler);
2140
- this.document.off('update', this.documentUpdateHandler);
2141
2348
  this.removeAllListeners();
2142
2349
  if (typeof window === 'undefined') {
2143
2350
  return;
2144
2351
  }
2145
2352
  window.removeEventListener('online', this.boundConnect);
2146
- window.removeEventListener('beforeunload', this.boundBeforeUnload);
2147
- }
2148
- get broadcastChannel() {
2149
- return `${this.serverUrl}/${this.configuration.name}`;
2150
- }
2151
- broadcastChannelSubscriber(data) {
2152
- this.mux(() => {
2153
- const message = new IncomingMessage(data);
2154
- new MessageReceiver(message)
2155
- .setBroadcasted(true)
2156
- .apply(this, false);
2157
- });
2158
- }
2159
- subscribeToBroadcastChannel() {
2160
- if (!this.subscribedToBroadcastChannel) {
2161
- subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2162
- this.subscribedToBroadcastChannel = true;
2163
- }
2164
- this.mux(() => {
2165
- this.broadcast(SyncStepOneMessage, { document: this.document });
2166
- this.broadcast(SyncStepTwoMessage, { document: this.document });
2167
- this.broadcast(QueryAwarenessMessage);
2168
- this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID] });
2169
- });
2170
- }
2171
- disconnectBroadcastChannel() {
2172
- // broadcast message with local awareness state set to null (indicating disconnect)
2173
- this.send(AwarenessMessage, {
2174
- awareness: this.awareness,
2175
- clients: [this.document.clientID],
2176
- states: new Map(),
2177
- }, true);
2178
- if (this.subscribedToBroadcastChannel) {
2179
- unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2180
- this.subscribedToBroadcastChannel = false;
2181
- }
2182
- }
2183
- broadcast(Message, args) {
2184
- if (!this.configuration.broadcast) {
2185
- return;
2186
- }
2187
- if (!this.subscribedToBroadcastChannel) {
2188
- return;
2189
- }
2190
- new MessageSender(Message, args).broadcast(this.broadcastChannel);
2191
- }
2192
- setAwarenessField(key, value) {
2193
- this.awareness.setLocalStateField(key, value);
2194
2353
  }
2195
2354
  }
2196
2355
 
@@ -2205,9 +2364,10 @@ class HocuspocusCloudProvider extends HocuspocusProvider {
2205
2364
  }
2206
2365
  configuration.parameters.key = configuration.key;
2207
2366
  }
2367
+ configuration.websocketProvider = new HocuspocusProviderWebsocket({ url: configuration.url });
2208
2368
  super(configuration);
2209
2369
  }
2210
2370
  }
2211
2371
 
2212
- export { HocuspocusCloudProvider, HocuspocusProvider, MessageType, WebSocketStatus };
2372
+ export { HocuspocusCloudProvider, HocuspocusProvider, HocuspocusProviderWebsocket, MessageType, WebSocketStatus };
2213
2373
  //# sourceMappingURL=hocuspocus-provider.esm.js.map