@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.
- package/dist/hocuspocus-provider.cjs +139 -80
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +137 -76
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +80 -14
- package/dist/packages/provider/src/MessageSender.d.ts +2 -8
- package/dist/packages/provider/src/types.d.ts +7 -0
- package/dist/packages/server/src/Connection.d.ts +2 -1
- package/dist/packages/server/src/Document.d.ts +2 -2
- package/dist/packages/server/src/Hocuspocus.d.ts +8 -0
- package/dist/packages/server/src/types.d.ts +4 -0
- package/package.json +3 -2
- package/src/HocuspocusProvider.ts +215 -96
- package/src/MessageReceiver.ts +16 -15
- package/src/MessageSender.ts +2 -15
- package/src/types.ts +14 -0
- package/CHANGELOG.md +0 -110
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
|
+
import { retry } from '@lifeomic/attempt';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Utility module to work with key-value stores.
|
|
@@ -258,8 +259,6 @@ hasConf('production');
|
|
|
258
259
|
*/
|
|
259
260
|
|
|
260
261
|
const floor = Math.floor;
|
|
261
|
-
const round = Math.round;
|
|
262
|
-
const log10 = Math.log10;
|
|
263
262
|
|
|
264
263
|
/**
|
|
265
264
|
* @function
|
|
@@ -1575,7 +1574,8 @@ class MessageReceiver {
|
|
|
1575
1574
|
return this;
|
|
1576
1575
|
}
|
|
1577
1576
|
apply(provider, emitSynced = true) {
|
|
1578
|
-
const
|
|
1577
|
+
const { message } = this;
|
|
1578
|
+
const type = message.readVarUint();
|
|
1579
1579
|
switch (type) {
|
|
1580
1580
|
case MessageType.Sync:
|
|
1581
1581
|
this.applySyncMessage(provider, emitSynced);
|
|
@@ -1592,16 +1592,6 @@ class MessageReceiver {
|
|
|
1592
1592
|
default:
|
|
1593
1593
|
throw new Error(`Can’t apply message of unknown type: ${type}`);
|
|
1594
1594
|
}
|
|
1595
|
-
}
|
|
1596
|
-
applySyncMessage(provider, emitSynced) {
|
|
1597
|
-
const { message } = this;
|
|
1598
|
-
message.writeVarUint(MessageType.Sync);
|
|
1599
|
-
// Apply update
|
|
1600
|
-
const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
|
|
1601
|
-
// Synced
|
|
1602
|
-
if (emitSynced && syncMessageType === messageYjsSyncStep2) {
|
|
1603
|
-
provider.synced = true;
|
|
1604
|
-
}
|
|
1605
1595
|
// Reply
|
|
1606
1596
|
if (message.length() > 1) {
|
|
1607
1597
|
if (this.broadcasted) {
|
|
@@ -1616,6 +1606,16 @@ class MessageReceiver {
|
|
|
1616
1606
|
}
|
|
1617
1607
|
}
|
|
1618
1608
|
}
|
|
1609
|
+
applySyncMessage(provider, emitSynced) {
|
|
1610
|
+
const { message } = this;
|
|
1611
|
+
message.writeVarUint(MessageType.Sync);
|
|
1612
|
+
// Apply update
|
|
1613
|
+
const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
|
|
1614
|
+
// Synced once we receive Step2
|
|
1615
|
+
if (emitSynced && syncMessageType === messageYjsSyncStep2) {
|
|
1616
|
+
provider.synced = true;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
1619
|
applyAwarenessMessage(provider) {
|
|
1620
1620
|
const { message } = this;
|
|
1621
1621
|
applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
|
|
@@ -1755,7 +1755,6 @@ var awarenessStatesToArray = (states) => {
|
|
|
1755
1755
|
});
|
|
1756
1756
|
};
|
|
1757
1757
|
|
|
1758
|
-
// @ts-nocheck
|
|
1759
1758
|
var WebSocketStatus;
|
|
1760
1759
|
(function (WebSocketStatus) {
|
|
1761
1760
|
WebSocketStatus["Connecting"] = "connecting";
|
|
@@ -1766,18 +1765,36 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1766
1765
|
constructor(options = {}) {
|
|
1767
1766
|
super();
|
|
1768
1767
|
this.options = {
|
|
1768
|
+
// @ts-ignore
|
|
1769
|
+
document: undefined,
|
|
1770
|
+
// @ts-ignore
|
|
1771
|
+
awareness: undefined,
|
|
1772
|
+
WebSocketPolyfill: undefined,
|
|
1769
1773
|
url: '',
|
|
1770
1774
|
name: '',
|
|
1771
1775
|
token: null,
|
|
1772
1776
|
parameters: {},
|
|
1773
|
-
debug: false,
|
|
1774
1777
|
connect: true,
|
|
1775
1778
|
broadcast: true,
|
|
1776
1779
|
forceSyncInterval: false,
|
|
1777
|
-
reconnectTimeoutBase: 1200,
|
|
1778
|
-
maxReconnectTimeout: 2500,
|
|
1779
1780
|
// TODO: this should depend on awareness.outdatedTime
|
|
1780
1781
|
messageReconnectTimeout: 30000,
|
|
1782
|
+
// 1 second
|
|
1783
|
+
delay: 1000,
|
|
1784
|
+
// instant
|
|
1785
|
+
initialDelay: 0,
|
|
1786
|
+
// double the delay each time
|
|
1787
|
+
factor: 2,
|
|
1788
|
+
// unlimited retries
|
|
1789
|
+
maxAttempts: 0,
|
|
1790
|
+
// wait at least 1 second
|
|
1791
|
+
minDelay: 1000,
|
|
1792
|
+
// at least every 30 seconds
|
|
1793
|
+
maxDelay: 30000,
|
|
1794
|
+
// randomize
|
|
1795
|
+
jitter: true,
|
|
1796
|
+
// retry forever
|
|
1797
|
+
timeout: 0,
|
|
1781
1798
|
onAuthenticated: () => null,
|
|
1782
1799
|
onAuthenticationFailed: () => null,
|
|
1783
1800
|
onOpen: () => null,
|
|
@@ -1796,7 +1813,6 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1796
1813
|
this.webSocket = null;
|
|
1797
1814
|
this.shouldConnect = true;
|
|
1798
1815
|
this.status = WebSocketStatus.Disconnected;
|
|
1799
|
-
this.failedConnectionAttempts = 0;
|
|
1800
1816
|
this.isSynced = false;
|
|
1801
1817
|
this.isAuthenticated = false;
|
|
1802
1818
|
this.lastMessageReceived = 0;
|
|
@@ -1805,11 +1821,10 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1805
1821
|
forceSync: null,
|
|
1806
1822
|
connectionChecker: null,
|
|
1807
1823
|
};
|
|
1824
|
+
this.connectionAttempt = null;
|
|
1808
1825
|
this.setOptions(options);
|
|
1809
|
-
this.options.document = options.document ? options.document : new Y.Doc();
|
|
1810
1826
|
this.options.awareness = options.awareness ? options.awareness : new Awareness(this.document);
|
|
1811
1827
|
this.options.WebSocketPolyfill = options.WebSocketPolyfill ? options.WebSocketPolyfill : WebSocket;
|
|
1812
|
-
this.shouldConnect = options.connect !== undefined ? options.connect : this.shouldConnect;
|
|
1813
1828
|
this.on('open', this.options.onOpen);
|
|
1814
1829
|
this.on('authenticated', this.options.onAuthenticated);
|
|
1815
1830
|
this.on('authenticationFailed', this.options.onAuthenticationFailed);
|
|
@@ -1824,29 +1839,92 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1824
1839
|
this.on('awarenessUpdate', this.options.onAwarenessUpdate);
|
|
1825
1840
|
this.on('awarenessChange', this.options.onAwarenessChange);
|
|
1826
1841
|
this.awareness.on('update', () => {
|
|
1827
|
-
this.emit('awarenessUpdate', {
|
|
1828
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
1829
|
-
});
|
|
1842
|
+
this.emit('awarenessUpdate', { states: awarenessStatesToArray(this.awareness.getStates()) });
|
|
1830
1843
|
});
|
|
1831
1844
|
this.awareness.on('change', () => {
|
|
1832
|
-
this.emit('awarenessChange', {
|
|
1833
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
1834
|
-
});
|
|
1845
|
+
this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
|
|
1835
1846
|
});
|
|
1836
|
-
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
|
|
1837
1847
|
this.document.on('update', this.documentUpdateHandler.bind(this));
|
|
1838
1848
|
this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
|
|
1839
1849
|
this.registerBeforeUnloadEventListener();
|
|
1850
|
+
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.options.messageReconnectTimeout / 10);
|
|
1840
1851
|
if (this.options.forceSyncInterval) {
|
|
1841
1852
|
this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.options.forceSyncInterval);
|
|
1842
1853
|
}
|
|
1843
|
-
if (
|
|
1844
|
-
this.connect
|
|
1854
|
+
if (typeof options.connect !== 'undefined') {
|
|
1855
|
+
this.shouldConnect = options.connect;
|
|
1856
|
+
}
|
|
1857
|
+
if (!this.shouldConnect) {
|
|
1858
|
+
return;
|
|
1845
1859
|
}
|
|
1860
|
+
this.connect();
|
|
1846
1861
|
}
|
|
1847
1862
|
setOptions(options = {}) {
|
|
1848
1863
|
this.options = { ...this.options, ...options };
|
|
1849
1864
|
}
|
|
1865
|
+
async connect() {
|
|
1866
|
+
if (this.status === WebSocketStatus.Connected) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
this.shouldConnect = true;
|
|
1870
|
+
this.subscribeToBroadcastChannel();
|
|
1871
|
+
try {
|
|
1872
|
+
await retry(this.createWebSocketConnection.bind(this), {
|
|
1873
|
+
delay: this.options.delay,
|
|
1874
|
+
initialDelay: this.options.initialDelay,
|
|
1875
|
+
factor: this.options.factor,
|
|
1876
|
+
maxAttempts: this.options.maxAttempts,
|
|
1877
|
+
minDelay: this.options.minDelay,
|
|
1878
|
+
maxDelay: this.options.maxDelay,
|
|
1879
|
+
jitter: this.options.jitter,
|
|
1880
|
+
timeout: this.options.timeout,
|
|
1881
|
+
beforeAttempt: context => {
|
|
1882
|
+
if (!this.shouldConnect) {
|
|
1883
|
+
context.abort();
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
catch (err) {
|
|
1889
|
+
// If we aborted the connection attempt then don't throw an error
|
|
1890
|
+
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
1891
|
+
if (err.code !== 'ATTEMPT_ABORTED') {
|
|
1892
|
+
throw err;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
createWebSocketConnection() {
|
|
1897
|
+
return new Promise((resolve, reject) => {
|
|
1898
|
+
// Init the WebSocket connection
|
|
1899
|
+
this.webSocket = new this.options.WebSocketPolyfill(this.url);
|
|
1900
|
+
this.webSocket.binaryType = 'arraybuffer';
|
|
1901
|
+
this.webSocket.onmessage = this.onMessage.bind(this);
|
|
1902
|
+
this.webSocket.onclose = this.onClose.bind(this);
|
|
1903
|
+
this.webSocket.onopen = this.onOpen.bind(this);
|
|
1904
|
+
this.webSocket.onerror = () => {
|
|
1905
|
+
reject();
|
|
1906
|
+
};
|
|
1907
|
+
// Reset the status
|
|
1908
|
+
this.synced = false;
|
|
1909
|
+
this.status = WebSocketStatus.Connecting;
|
|
1910
|
+
this.emit('status', { status: 'connecting' });
|
|
1911
|
+
// Store resolve/reject for later use
|
|
1912
|
+
this.connectionAttempt = {
|
|
1913
|
+
resolve,
|
|
1914
|
+
reject,
|
|
1915
|
+
};
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
resolveConnectionAttempt() {
|
|
1919
|
+
var _a;
|
|
1920
|
+
(_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.resolve();
|
|
1921
|
+
this.connectionAttempt = null;
|
|
1922
|
+
}
|
|
1923
|
+
rejectConnectionAttempt() {
|
|
1924
|
+
var _a;
|
|
1925
|
+
(_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
|
|
1926
|
+
this.connectionAttempt = null;
|
|
1927
|
+
}
|
|
1850
1928
|
get document() {
|
|
1851
1929
|
return this.options.document;
|
|
1852
1930
|
}
|
|
@@ -1854,11 +1932,11 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1854
1932
|
return this.options.awareness;
|
|
1855
1933
|
}
|
|
1856
1934
|
checkConnection() {
|
|
1857
|
-
// Don’t
|
|
1935
|
+
// Don’t check the connection when it’s not even established
|
|
1858
1936
|
if (this.status !== WebSocketStatus.Connected) {
|
|
1859
1937
|
return;
|
|
1860
1938
|
}
|
|
1861
|
-
// Don’t
|
|
1939
|
+
// Don’t close then connection while waiting for the first message
|
|
1862
1940
|
if (!this.lastMessageReceived) {
|
|
1863
1941
|
return;
|
|
1864
1942
|
}
|
|
@@ -1899,7 +1977,6 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1899
1977
|
}
|
|
1900
1978
|
permissionDeniedHandler(reason) {
|
|
1901
1979
|
this.emit('authenticationFailed', { reason });
|
|
1902
|
-
this.log('Permission denied', reason);
|
|
1903
1980
|
this.isAuthenticated = false;
|
|
1904
1981
|
this.shouldConnect = false;
|
|
1905
1982
|
}
|
|
@@ -1933,13 +2010,6 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1933
2010
|
get isAuthenticationRequired() {
|
|
1934
2011
|
return !!this.options.token && !this.isAuthenticated;
|
|
1935
2012
|
}
|
|
1936
|
-
connect() {
|
|
1937
|
-
this.shouldConnect = true;
|
|
1938
|
-
if (this.status !== WebSocketStatus.Connected) {
|
|
1939
|
-
this.createWebSocketConnection();
|
|
1940
|
-
this.subscribeToBroadcastChannel();
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
2013
|
disconnect() {
|
|
1944
2014
|
this.shouldConnect = false;
|
|
1945
2015
|
this.disconnectBroadcastChannel();
|
|
@@ -1953,19 +2023,6 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1953
2023
|
//
|
|
1954
2024
|
}
|
|
1955
2025
|
}
|
|
1956
|
-
createWebSocketConnection() {
|
|
1957
|
-
if (this.webSocket !== null) {
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
this.webSocket = new this.options.WebSocketPolyfill(this.url);
|
|
1961
|
-
this.webSocket.binaryType = 'arraybuffer';
|
|
1962
|
-
this.status = WebSocketStatus.Connecting;
|
|
1963
|
-
this.synced = false;
|
|
1964
|
-
this.webSocket.onmessage = this.onMessage.bind(this);
|
|
1965
|
-
this.webSocket.onclose = this.onClose.bind(this);
|
|
1966
|
-
this.webSocket.onopen = this.onOpen.bind(this);
|
|
1967
|
-
this.emit('status', { status: 'connecting' });
|
|
1968
|
-
}
|
|
1969
2026
|
onOpen(event) {
|
|
1970
2027
|
this.emit('open', { event });
|
|
1971
2028
|
if (this.status !== WebSocketStatus.Connected) {
|
|
@@ -1980,16 +2037,17 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
1980
2037
|
return this.options.token;
|
|
1981
2038
|
}
|
|
1982
2039
|
async webSocketConnectionEstablished() {
|
|
1983
|
-
this.failedConnectionAttempts = 0;
|
|
1984
2040
|
this.status = WebSocketStatus.Connected;
|
|
1985
2041
|
this.emit('status', { status: 'connected' });
|
|
1986
2042
|
this.emit('connect');
|
|
1987
2043
|
if (this.isAuthenticationRequired) {
|
|
1988
|
-
|
|
1989
|
-
|
|
2044
|
+
this.send(AuthenticationMessage, {
|
|
2045
|
+
token: await this.getToken(),
|
|
2046
|
+
});
|
|
1990
2047
|
return;
|
|
1991
2048
|
}
|
|
1992
2049
|
this.startSync();
|
|
2050
|
+
this.resolveConnectionAttempt();
|
|
1993
2051
|
}
|
|
1994
2052
|
startSync() {
|
|
1995
2053
|
this.send(SyncStepOneMessage, { document: this.document });
|
|
@@ -2002,9 +2060,7 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
2002
2060
|
}
|
|
2003
2061
|
send(Message, args, broadcast = false) {
|
|
2004
2062
|
if (broadcast) {
|
|
2005
|
-
this.mux(() => {
|
|
2006
|
-
this.broadcast(Message, args);
|
|
2007
|
-
});
|
|
2063
|
+
this.mux(() => { this.broadcast(Message, args); });
|
|
2008
2064
|
}
|
|
2009
2065
|
if (this.status === WebSocketStatus.Connected) {
|
|
2010
2066
|
const messageSender = new MessageSender(Message, args);
|
|
@@ -2020,30 +2076,36 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
2020
2076
|
}
|
|
2021
2077
|
onClose(event) {
|
|
2022
2078
|
this.emit('close', { event });
|
|
2023
|
-
this.isAuthenticated = false;
|
|
2024
2079
|
this.webSocket = null;
|
|
2080
|
+
this.isAuthenticated = false;
|
|
2081
|
+
this.synced = false;
|
|
2025
2082
|
if (this.status === WebSocketStatus.Connected) {
|
|
2026
|
-
this.synced = false;
|
|
2027
2083
|
// update awareness (all users except local left)
|
|
2028
2084
|
removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
|
|
2029
2085
|
this.status = WebSocketStatus.Disconnected;
|
|
2030
2086
|
this.emit('status', { status: 'disconnected' });
|
|
2031
2087
|
this.emit('disconnect', { event });
|
|
2032
2088
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2089
|
+
if (this.connectionAttempt) {
|
|
2090
|
+
// Okay, that connection attempt failed …
|
|
2091
|
+
this.rejectConnectionAttempt();
|
|
2092
|
+
}
|
|
2093
|
+
else if (this.shouldConnect) {
|
|
2094
|
+
// The connection was closed by the server, so let’s just try again.
|
|
2095
|
+
this.connect();
|
|
2035
2096
|
}
|
|
2097
|
+
// If we’ll reconnect anyway, we’re done for now.
|
|
2036
2098
|
if (this.shouldConnect) {
|
|
2037
|
-
const wait = round(min(log10(this.failedConnectionAttempts + 1) * this.options.reconnectTimeoutBase, this.options.maxReconnectTimeout));
|
|
2038
|
-
this.log(`[close] Reconnecting in ${wait}ms …`);
|
|
2039
|
-
setTimeout(this.createWebSocketConnection.bind(this), wait);
|
|
2040
2099
|
return;
|
|
2041
2100
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
this.emit('disconnect', { event });
|
|
2101
|
+
// The status is set correctly already.
|
|
2102
|
+
if (this.status === WebSocketStatus.Disconnected) {
|
|
2103
|
+
return;
|
|
2046
2104
|
}
|
|
2105
|
+
// Let’s update the connection status.
|
|
2106
|
+
this.status = WebSocketStatus.Disconnected;
|
|
2107
|
+
this.emit('status', { status: 'disconnected' });
|
|
2108
|
+
this.emit('disconnect', { event });
|
|
2047
2109
|
}
|
|
2048
2110
|
destroy() {
|
|
2049
2111
|
this.emit('destroy');
|
|
@@ -2051,6 +2113,11 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
2051
2113
|
clearInterval(this.intervals.forceSync);
|
|
2052
2114
|
}
|
|
2053
2115
|
clearInterval(this.intervals.connectionChecker);
|
|
2116
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
|
|
2117
|
+
// If there is still a connection attempt outstanding then we should resolve
|
|
2118
|
+
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
2119
|
+
// handler and trigger a retry
|
|
2120
|
+
this.resolveConnectionAttempt();
|
|
2054
2121
|
this.disconnect();
|
|
2055
2122
|
this.awareness.off('update', this.awarenessUpdateHandler);
|
|
2056
2123
|
this.document.off('update', this.documentUpdateHandler);
|
|
@@ -2062,7 +2129,7 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
2062
2129
|
broadcastChannelSubscriber(data) {
|
|
2063
2130
|
this.mux(() => {
|
|
2064
2131
|
const message = new IncomingMessage(data);
|
|
2065
|
-
new MessageReceiver(message
|
|
2132
|
+
new MessageReceiver(message)
|
|
2066
2133
|
.setBroadcasted(true)
|
|
2067
2134
|
.apply(this, false);
|
|
2068
2135
|
});
|
|
@@ -2100,12 +2167,6 @@ class HocuspocusProvider extends EventEmitter {
|
|
|
2100
2167
|
}
|
|
2101
2168
|
new MessageSender(Message, args).broadcast(this.broadcastChannel);
|
|
2102
2169
|
}
|
|
2103
|
-
log(message) {
|
|
2104
|
-
if (!this.options.debug) {
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
console.log(message);
|
|
2108
|
-
}
|
|
2109
2170
|
setAwarenessField(key, value) {
|
|
2110
2171
|
this.awareness.setLocalStateField(key, value);
|
|
2111
2172
|
}
|