@hocuspocus/provider 1.1.1 → 2.0.0-alpha.1

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