@hocuspocus/provider 1.0.0-alpha.14 → 1.0.0-alpha.18

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.
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var Y = require('yjs');
6
+ var attempt = require('@lifeomic/attempt');
6
7
 
7
8
  function _interopNamespace(e) {
8
9
  if (e && e.__esModule) return e;
@@ -13,14 +14,12 @@ function _interopNamespace(e) {
13
14
  var d = Object.getOwnPropertyDescriptor(e, k);
14
15
  Object.defineProperty(n, k, d.get ? d : {
15
16
  enumerable: true,
16
- get: function () {
17
- return e[k];
18
- }
17
+ get: function () { return e[k]; }
19
18
  });
20
19
  }
21
20
  });
22
21
  }
23
- n['default'] = e;
22
+ n["default"] = e;
24
23
  return Object.freeze(n);
25
24
  }
26
25
 
@@ -284,8 +283,6 @@ hasConf('production');
284
283
  */
285
284
 
286
285
  const floor = Math.floor;
287
- const round = Math.round;
288
- const log10 = Math.log10;
289
286
 
290
287
  /**
291
288
  * @function
@@ -1601,7 +1598,8 @@ class MessageReceiver {
1601
1598
  return this;
1602
1599
  }
1603
1600
  apply(provider, emitSynced = true) {
1604
- const type = this.message.readVarUint();
1601
+ const { message } = this;
1602
+ const type = message.readVarUint();
1605
1603
  switch (type) {
1606
1604
  case exports.MessageType.Sync:
1607
1605
  this.applySyncMessage(provider, emitSynced);
@@ -1618,16 +1616,6 @@ class MessageReceiver {
1618
1616
  default:
1619
1617
  throw new Error(`Can’t apply message of unknown type: ${type}`);
1620
1618
  }
1621
- }
1622
- applySyncMessage(provider, emitSynced) {
1623
- const { message } = this;
1624
- message.writeVarUint(exports.MessageType.Sync);
1625
- // Apply update
1626
- const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
1627
- // Synced
1628
- if (emitSynced && syncMessageType === messageYjsSyncStep2) {
1629
- provider.synced = true;
1630
- }
1631
1619
  // Reply
1632
1620
  if (message.length() > 1) {
1633
1621
  if (this.broadcasted) {
@@ -1642,6 +1630,16 @@ class MessageReceiver {
1642
1630
  }
1643
1631
  }
1644
1632
  }
1633
+ applySyncMessage(provider, emitSynced) {
1634
+ const { message } = this;
1635
+ message.writeVarUint(exports.MessageType.Sync);
1636
+ // Apply update
1637
+ const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
1638
+ // Synced once we receive Step2
1639
+ if (emitSynced && syncMessageType === messageYjsSyncStep2) {
1640
+ provider.synced = true;
1641
+ }
1642
+ }
1645
1643
  applyAwarenessMessage(provider) {
1646
1644
  const { message } = this;
1647
1645
  applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
@@ -1781,7 +1779,6 @@ var awarenessStatesToArray = (states) => {
1781
1779
  });
1782
1780
  };
1783
1781
 
1784
- // @ts-nocheck
1785
1782
  exports.WebSocketStatus = void 0;
1786
1783
  (function (WebSocketStatus) {
1787
1784
  WebSocketStatus["Connecting"] = "connecting";
@@ -1792,18 +1789,36 @@ class HocuspocusProvider extends EventEmitter {
1792
1789
  constructor(options = {}) {
1793
1790
  super();
1794
1791
  this.options = {
1792
+ // @ts-ignore
1793
+ document: undefined,
1794
+ // @ts-ignore
1795
+ awareness: undefined,
1796
+ WebSocketPolyfill: undefined,
1795
1797
  url: '',
1796
1798
  name: '',
1797
1799
  token: null,
1798
1800
  parameters: {},
1799
- debug: false,
1800
1801
  connect: true,
1801
1802
  broadcast: true,
1802
1803
  forceSyncInterval: false,
1803
- reconnectTimeoutBase: 1200,
1804
- maxReconnectTimeout: 2500,
1805
1804
  // TODO: this should depend on awareness.outdatedTime
1806
1805
  messageReconnectTimeout: 30000,
1806
+ // 1 second
1807
+ delay: 1000,
1808
+ // instant
1809
+ initialDelay: 0,
1810
+ // double the delay each time
1811
+ factor: 2,
1812
+ // unlimited retries
1813
+ maxAttempts: 0,
1814
+ // wait at least 1 second
1815
+ minDelay: 1000,
1816
+ // at least every 30 seconds
1817
+ maxDelay: 30000,
1818
+ // randomize
1819
+ jitter: true,
1820
+ // retry forever
1821
+ timeout: 0,
1807
1822
  onAuthenticated: () => null,
1808
1823
  onAuthenticationFailed: () => null,
1809
1824
  onOpen: () => null,
@@ -1822,7 +1837,6 @@ class HocuspocusProvider extends EventEmitter {
1822
1837
  this.webSocket = null;
1823
1838
  this.shouldConnect = true;
1824
1839
  this.status = exports.WebSocketStatus.Disconnected;
1825
- this.failedConnectionAttempts = 0;
1826
1840
  this.isSynced = false;
1827
1841
  this.isAuthenticated = false;
1828
1842
  this.lastMessageReceived = 0;
@@ -1831,11 +1845,10 @@ class HocuspocusProvider extends EventEmitter {
1831
1845
  forceSync: null,
1832
1846
  connectionChecker: null,
1833
1847
  };
1848
+ this.connectionAttempt = null;
1834
1849
  this.setOptions(options);
1835
- this.options.document = options.document ? options.document : new Y__namespace.Doc();
1836
1850
  this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
1837
1851
  this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
1838
- this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
1839
1852
  this.on('open', this.options.onOpen);
1840
1853
  this.on('authenticated', this.options.onAuthenticated);
1841
1854
  this.on('authenticationFailed', this.options.onAuthenticationFailed);
@@ -1850,29 +1863,92 @@ class HocuspocusProvider extends EventEmitter {
1850
1863
  this.on('awarenessUpdate', this.options.onAwarenessUpdate);
1851
1864
  this.on('awarenessChange', this.options.onAwarenessChange);
1852
1865
  this.awareness.on('update', () => {
1853
- this.emit('awarenessUpdate', {
1854
- states: awarenessStatesToArray(this.awareness.getStates()),
1855
- });
1866
+ this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) });
1856
1867
  });
1857
1868
  this.awareness.on('change', () => {
1858
- this.emit('awarenessChange', {
1859
- states: awarenessStatesToArray(this.awareness.getStates()),
1860
- });
1869
+ this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
1861
1870
  });
1862
- this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1863
1871
  this.document.on('update', this.documentUpdateHandler.bind(this));
1864
1872
  this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
1865
1873
  this.registerBeforeUnloadEventListener();
1874
+ this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
1866
1875
  if (this.options.forceSyncInterval) {
1867
1876
  this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.options.forceSyncInterval);
1868
1877
  }
1869
- if (this.options.connect) {
1870
- this.connect();
1878
+ if (typeof options.connect !== 'undefined') {
1879
+ this.shouldConnect = options.connect;
1880
+ }
1881
+ if (!this.shouldConnect) {
1882
+ return;
1871
1883
  }
1884
+ this.connect();
1872
1885
  }
1873
1886
  setOptions(options = {}) {
1874
1887
  this.options = { ...this.options, ...options };
1875
1888
  }
1889
+ async connect() {
1890
+ if (this.status === exports.WebSocketStatus.Connected) {
1891
+ return;
1892
+ }
1893
+ this.shouldConnect = true;
1894
+ this.subscribeToBroadcastChannel();
1895
+ try {
1896
+ await attempt.retry(this.createWebSocketConnection.bind(this), {
1897
+ delay: this.options.delay,
1898
+ initialDelay: this.options.initialDelay,
1899
+ factor: this.options.factor,
1900
+ maxAttempts: this.options.maxAttempts,
1901
+ minDelay: this.options.minDelay,
1902
+ maxDelay: this.options.maxDelay,
1903
+ jitter: this.options.jitter,
1904
+ timeout: this.options.timeout,
1905
+ beforeAttempt: context => {
1906
+ if (!this.shouldConnect) {
1907
+ context.abort();
1908
+ }
1909
+ },
1910
+ });
1911
+ }
1912
+ catch (err) {
1913
+ // If we aborted the connection attempt then don't throw an error
1914
+ // ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
1915
+ if (err.code !== 'ATTEMPT_ABORTED') {
1916
+ throw err;
1917
+ }
1918
+ }
1919
+ }
1920
+ createWebSocketConnection() {
1921
+ return new Promise((resolve, reject) => {
1922
+ // Init the WebSocket connection
1923
+ this.webSocket = new this.options.WebSocketPolyfill(this.url);
1924
+ this.webSocket.binaryType = 'arraybuffer';
1925
+ this.webSocket.onmessage = this.onMessage.bind(this);
1926
+ this.webSocket.onclose = this.onClose.bind(this);
1927
+ this.webSocket.onopen = this.onOpen.bind(this);
1928
+ this.webSocket.onerror = () => {
1929
+ reject();
1930
+ };
1931
+ // Reset the status
1932
+ this.synced = false;
1933
+ this.status = exports.WebSocketStatus.Connecting;
1934
+ this.emit('status', { status: 'connecting' });
1935
+ // Store resolve/reject for later use
1936
+ this.connectionAttempt = {
1937
+ resolve,
1938
+ reject,
1939
+ };
1940
+ });
1941
+ }
1942
+ resolveConnectionAttempt() {
1943
+ var _a;
1944
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
1945
+ this.connectionAttempt = null;
1946
+ }
1947
+ rejectConnectionAttempt() {
1948
+ var _a;
1949
+ (_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
1950
+ this.connectionAttempt = null;
1951
+ }
1876
1952
  get document() {
1877
1953
  return this.options.document;
1878
1954
  }
@@ -1880,11 +1956,11 @@ class HocuspocusProvider extends EventEmitter {
1880
1956
  return this.options.awareness;
1881
1957
  }
1882
1958
  checkConnection() {
1883
- // Don’t close the connection when it’s not established anyway
1959
+ // Don’t check the connection when it’s not even established
1884
1960
  if (this.status !== exports.WebSocketStatus.Connected) {
1885
1961
  return;
1886
1962
  }
1887
- // Don’t just close then connection while waiting for the first message
1963
+ // Don’t close then connection while waiting for the first message
1888
1964
  if (!this.lastMessageReceived) {
1889
1965
  return;
1890
1966
  }
@@ -1925,7 +2001,6 @@ class HocuspocusProvider extends EventEmitter {
1925
2001
  }
1926
2002
  permissionDeniedHandler(reason) {
1927
2003
  this.emit('authenticationFailed', { reason });
1928
- this.log('Permission denied', reason);
1929
2004
  this.isAuthenticated = false;
1930
2005
  this.shouldConnect = false;
1931
2006
  }
@@ -1959,13 +2034,6 @@ class HocuspocusProvider extends EventEmitter {
1959
2034
  get isAuthenticationRequired() {
1960
2035
  return !!this.options.token && !this.isAuthenticated;
1961
2036
  }
1962
- connect() {
1963
- this.shouldConnect = true;
1964
- if (this.status !== exports.WebSocketStatus.Connected) {
1965
- this.createWebSocketConnection();
1966
- this.subscribeToBroadcastChannel();
1967
- }
1968
- }
1969
2037
  disconnect() {
1970
2038
  this.shouldConnect = false;
1971
2039
  this.disconnectBroadcastChannel();
@@ -1979,19 +2047,6 @@ class HocuspocusProvider extends EventEmitter {
1979
2047
  //
1980
2048
  }
1981
2049
  }
1982
- createWebSocketConnection() {
1983
- if (this.webSocket !== null) {
1984
- return;
1985
- }
1986
- this.webSocket = new this.options.WebSocketPolyfill(this.url);
1987
- this.webSocket.binaryType = 'arraybuffer';
1988
- this.status = exports.WebSocketStatus.Connecting;
1989
- this.synced = false;
1990
- this.webSocket.onmessage = this.onMessage.bind(this);
1991
- this.webSocket.onclose = this.onClose.bind(this);
1992
- this.webSocket.onopen = this.onOpen.bind(this);
1993
- this.emit('status', { status: 'connecting' });
1994
- }
1995
2050
  onOpen(event) {
1996
2051
  this.emit('open', { event });
1997
2052
  if (this.status !== exports.WebSocketStatus.Connected) {
@@ -2006,16 +2061,17 @@ class HocuspocusProvider extends EventEmitter {
2006
2061
  return this.options.token;
2007
2062
  }
2008
2063
  async webSocketConnectionEstablished() {
2009
- this.failedConnectionAttempts = 0;
2010
2064
  this.status = exports.WebSocketStatus.Connected;
2011
2065
  this.emit('status', { status: 'connected' });
2012
2066
  this.emit('connect');
2013
2067
  if (this.isAuthenticationRequired) {
2014
- const token = await this.getToken();
2015
- this.send(AuthenticationMessage, { token });
2068
+ this.send(AuthenticationMessage, {
2069
+ token: await this.getToken(),
2070
+ });
2016
2071
  return;
2017
2072
  }
2018
2073
  this.startSync();
2074
+ this.resolveConnectionAttempt();
2019
2075
  }
2020
2076
  startSync() {
2021
2077
  this.send(SyncStepOneMessage, { document: this.document });
@@ -2028,9 +2084,7 @@ class HocuspocusProvider extends EventEmitter {
2028
2084
  }
2029
2085
  send(Message, args, broadcast = false) {
2030
2086
  if (broadcast) {
2031
- this.mux(() => {
2032
- this.broadcast(Message, args);
2033
- });
2087
+ this.mux(() => { this.broadcast(Message, args); });
2034
2088
  }
2035
2089
  if (this.status === exports.WebSocketStatus.Connected) {
2036
2090
  const messageSender = new MessageSender(Message, args);
@@ -2046,30 +2100,36 @@ class HocuspocusProvider extends EventEmitter {
2046
2100
  }
2047
2101
  onClose(event) {
2048
2102
  this.emit('close', { event });
2049
- this.isAuthenticated = false;
2050
2103
  this.webSocket = null;
2104
+ this.isAuthenticated = false;
2105
+ this.synced = false;
2051
2106
  if (this.status === exports.WebSocketStatus.Connected) {
2052
- this.synced = false;
2053
2107
  // update awareness (all users except local left)
2054
2108
  removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
2055
2109
  this.status = exports.WebSocketStatus.Disconnected;
2056
2110
  this.emit('status', { status: 'disconnected' });
2057
2111
  this.emit('disconnect', { event });
2058
2112
  }
2059
- else {
2060
- this.failedConnectionAttempts += 1;
2113
+ if (this.connectionAttempt) {
2114
+ // Okay, that connection attempt failed …
2115
+ this.rejectConnectionAttempt();
2116
+ }
2117
+ else if (this.shouldConnect) {
2118
+ // The connection was closed by the server, so let’s just try again.
2119
+ this.connect();
2061
2120
  }
2121
+ // If we’ll reconnect anyway, we’re done for now.
2062
2122
  if (this.shouldConnect) {
2063
- const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
2064
- this.log(`[close] Reconnecting in ${wait}ms …`);
2065
- setTimeout(this.createWebSocketConnection.bind(this), wait);
2066
2123
  return;
2067
2124
  }
2068
- if (this.status !== exports.WebSocketStatus.Disconnected) {
2069
- this.status = exports.WebSocketStatus.Disconnected;
2070
- this.emit('status', { status: 'disconnected' });
2071
- this.emit('disconnect', { event });
2125
+ // The status is set correctly already.
2126
+ if (this.status === exports.WebSocketStatus.Disconnected) {
2127
+ return;
2072
2128
  }
2129
+ // Let’s update the connection status.
2130
+ this.status = exports.WebSocketStatus.Disconnected;
2131
+ this.emit('status', { status: 'disconnected' });
2132
+ this.emit('disconnect', { event });
2073
2133
  }
2074
2134
  destroy() {
2075
2135
  this.emit('destroy');
@@ -2077,6 +2137,11 @@ class HocuspocusProvider extends EventEmitter {
2077
2137
  clearInterval(this.intervals.forceSync);
2078
2138
  }
2079
2139
  clearInterval(this.intervals.connectionChecker);
2140
+ removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2141
+ // If there is still a connection attempt outstanding then we should resolve
2142
+ // it before calling disconnect, otherwise it will be rejected in the onClose
2143
+ // handler and trigger a retry
2144
+ this.resolveConnectionAttempt();
2080
2145
  this.disconnect();
2081
2146
  this.awareness.off('update', this.awarenessUpdateHandler);
2082
2147
  this.document.off('update', this.documentUpdateHandler);
@@ -2088,7 +2153,7 @@ class HocuspocusProvider extends EventEmitter {
2088
2153
  broadcastChannelSubscriber(data) {
2089
2154
  this.mux(() => {
2090
2155
  const message = new IncomingMessage(data);
2091
- new MessageReceiver(message, this)
2156
+ new MessageReceiver(message)
2092
2157
  .setBroadcasted(true)
2093
2158
  .apply(this, false);
2094
2159
  });
@@ -2126,12 +2191,6 @@ class HocuspocusProvider extends EventEmitter {
2126
2191
  }
2127
2192
  new MessageSender(Message, args).broadcast(this.broadcastChannel);
2128
2193
  }
2129
- log(message) {
2130
- if (!this.options.debug) {
2131
- return;
2132
- }
2133
- console.log(message);
2134
- }
2135
2194
  setAwarenessField(key, value) {
2136
2195
  this.awareness.setLocalStateField(key, value);
2137
2196
  }