@hocuspocus/provider 2.0.0-alpha.0 → 2.0.0-beta.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.
- package/dist/hocuspocus-provider.cjs +475 -447
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +476 -448
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-webhook/src/index.d.ts +1 -2
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +4 -2
- package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +8 -0
- package/dist/packages/provider/src/types.d.ts +2 -1
- package/dist/packages/server/src/Connection.d.ts +5 -1
- package/dist/packages/server/src/types.d.ts +2 -1
- package/dist/tests/server/onClose.d.ts +1 -0
- package/package.json +4 -3
- package/src/HocuspocusProvider.ts +26 -4
- package/src/HocuspocusProviderWebsocket.ts +3 -1
- package/src/OutgoingMessages/CloseMessage.ts +16 -0
- package/src/types.ts +1 -0
|
@@ -1546,6 +1546,7 @@ exports.MessageType = void 0;
|
|
|
1546
1546
|
MessageType[MessageType["Auth"] = 2] = "Auth";
|
|
1547
1547
|
MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
|
|
1548
1548
|
MessageType[MessageType["Stateless"] = 5] = "Stateless";
|
|
1549
|
+
MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
|
|
1549
1550
|
})(exports.MessageType || (exports.MessageType = {}));
|
|
1550
1551
|
exports.WebSocketStatus = void 0;
|
|
1551
1552
|
(function (WebSocketStatus) {
|
|
@@ -1765,615 +1766,642 @@ class UpdateMessage extends OutgoingMessage {
|
|
|
1765
1766
|
}
|
|
1766
1767
|
}
|
|
1767
1768
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
1774
|
-
get(args) {
|
|
1775
|
-
var _a;
|
|
1776
|
-
writeVarString(this.encoder, args.documentName);
|
|
1777
|
-
writeVarUint(this.encoder, this.type);
|
|
1778
|
-
writeVarString(this.encoder, (_a = args.payload) !== null && _a !== void 0 ? _a : '');
|
|
1779
|
-
return this.encoder;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Utility module to work with urls.
|
|
1771
|
+
*
|
|
1772
|
+
* @module url
|
|
1773
|
+
*/
|
|
1782
1774
|
|
|
1783
|
-
|
|
1775
|
+
/**
|
|
1776
|
+
* @param {Object<string,string>} params
|
|
1777
|
+
* @return {string}
|
|
1778
|
+
*/
|
|
1779
|
+
const encodeQueryParams = params =>
|
|
1780
|
+
map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
|
|
1781
|
+
|
|
1782
|
+
class HocuspocusProviderWebsocket extends EventEmitter {
|
|
1784
1783
|
constructor(configuration) {
|
|
1785
1784
|
super();
|
|
1786
1785
|
this.configuration = {
|
|
1787
|
-
|
|
1786
|
+
url: '',
|
|
1788
1787
|
// @ts-ignore
|
|
1789
1788
|
document: undefined,
|
|
1790
1789
|
// @ts-ignore
|
|
1791
1790
|
awareness: undefined,
|
|
1792
|
-
|
|
1791
|
+
WebSocketPolyfill: undefined,
|
|
1793
1792
|
parameters: {},
|
|
1793
|
+
connect: true,
|
|
1794
1794
|
broadcast: true,
|
|
1795
1795
|
forceSyncInterval: false,
|
|
1796
|
-
|
|
1797
|
-
|
|
1796
|
+
// TODO: this should depend on awareness.outdatedTime
|
|
1797
|
+
messageReconnectTimeout: 30000,
|
|
1798
|
+
// 1 second
|
|
1799
|
+
delay: 1000,
|
|
1800
|
+
// instant
|
|
1801
|
+
initialDelay: 0,
|
|
1802
|
+
// double the delay each time
|
|
1803
|
+
factor: 2,
|
|
1804
|
+
// unlimited retries
|
|
1805
|
+
maxAttempts: 0,
|
|
1806
|
+
// wait at least 1 second
|
|
1807
|
+
minDelay: 1000,
|
|
1808
|
+
// at least every 30 seconds
|
|
1809
|
+
maxDelay: 30000,
|
|
1810
|
+
// randomize
|
|
1811
|
+
jitter: true,
|
|
1812
|
+
// retry forever
|
|
1813
|
+
timeout: 0,
|
|
1798
1814
|
onOpen: () => null,
|
|
1799
1815
|
onConnect: () => null,
|
|
1800
1816
|
onMessage: () => null,
|
|
1801
1817
|
onOutgoingMessage: () => null,
|
|
1802
1818
|
onStatus: () => null,
|
|
1803
|
-
onSynced: () => null,
|
|
1804
1819
|
onDisconnect: () => null,
|
|
1805
1820
|
onClose: () => null,
|
|
1806
1821
|
onDestroy: () => null,
|
|
1807
1822
|
onAwarenessUpdate: () => null,
|
|
1808
1823
|
onAwarenessChange: () => null,
|
|
1809
|
-
onStateless: () => null,
|
|
1810
1824
|
quiet: false,
|
|
1811
1825
|
};
|
|
1812
1826
|
this.subscribedToBroadcastChannel = false;
|
|
1813
|
-
this.
|
|
1814
|
-
this.
|
|
1827
|
+
this.webSocket = null;
|
|
1828
|
+
this.shouldConnect = true;
|
|
1815
1829
|
this.status = exports.WebSocketStatus.Disconnected;
|
|
1816
|
-
this.
|
|
1830
|
+
this.lastMessageReceived = 0;
|
|
1817
1831
|
this.mux = createMutex();
|
|
1818
1832
|
this.intervals = {
|
|
1819
1833
|
forceSync: null,
|
|
1834
|
+
connectionChecker: null,
|
|
1820
1835
|
};
|
|
1821
|
-
this.
|
|
1822
|
-
this.
|
|
1836
|
+
this.connectionAttempt = null;
|
|
1837
|
+
this.receivedOnOpenPayload = undefined;
|
|
1838
|
+
this.receivedOnStatusPayload = undefined;
|
|
1839
|
+
this.boundConnect = this.connect.bind(this);
|
|
1823
1840
|
this.setConfiguration(configuration);
|
|
1824
|
-
this.configuration.
|
|
1825
|
-
this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document);
|
|
1841
|
+
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket;
|
|
1826
1842
|
this.on('open', this.configuration.onOpen);
|
|
1843
|
+
this.on('open', this.onOpen.bind(this));
|
|
1844
|
+
this.on('connect', this.configuration.onConnect);
|
|
1827
1845
|
this.on('message', this.configuration.onMessage);
|
|
1828
1846
|
this.on('outgoingMessage', this.configuration.onOutgoingMessage);
|
|
1829
|
-
this.on('
|
|
1847
|
+
this.on('status', this.configuration.onStatus);
|
|
1848
|
+
this.on('status', this.onStatus.bind(this));
|
|
1849
|
+
this.on('disconnect', this.configuration.onDisconnect);
|
|
1850
|
+
this.on('close', this.configuration.onClose);
|
|
1830
1851
|
this.on('destroy', this.configuration.onDestroy);
|
|
1831
1852
|
this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
|
|
1832
1853
|
this.on('awarenessChange', this.configuration.onAwarenessChange);
|
|
1833
|
-
this.on('
|
|
1834
|
-
this.on('
|
|
1835
|
-
this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
|
|
1836
|
-
this.configuration.websocketProvider.on('connect', this.configuration.onConnect);
|
|
1837
|
-
this.configuration.websocketProvider.on('connect', (e) => this.emit('connect', e));
|
|
1838
|
-
this.configuration.websocketProvider.on('open', this.onOpen.bind(this));
|
|
1839
|
-
this.configuration.websocketProvider.on('open', (e) => this.emit('open', e));
|
|
1840
|
-
this.configuration.websocketProvider.on('message', this.onMessage.bind(this));
|
|
1841
|
-
this.configuration.websocketProvider.on('close', this.onClose.bind(this));
|
|
1842
|
-
this.configuration.websocketProvider.on('close', this.configuration.onClose);
|
|
1843
|
-
this.configuration.websocketProvider.on('close', (e) => this.emit('close', e));
|
|
1844
|
-
this.configuration.websocketProvider.on('status', this.onStatus.bind(this));
|
|
1845
|
-
this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect);
|
|
1846
|
-
this.configuration.websocketProvider.on('disconnect', (e) => this.emit('disconnect', e));
|
|
1847
|
-
this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy);
|
|
1848
|
-
this.configuration.websocketProvider.on('destroy', (e) => this.emit('destroy', e));
|
|
1849
|
-
this.awareness.on('update', () => {
|
|
1850
|
-
this.emit('awarenessUpdate', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
|
|
1851
|
-
});
|
|
1852
|
-
this.awareness.on('change', () => {
|
|
1853
|
-
this.emit('awarenessChange', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
|
|
1854
|
-
});
|
|
1855
|
-
this.document.on('update', this.documentUpdateHandler.bind(this));
|
|
1856
|
-
this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
|
|
1854
|
+
this.on('close', this.onClose.bind(this));
|
|
1855
|
+
this.on('message', this.onMessage.bind(this));
|
|
1857
1856
|
this.registerEventListeners();
|
|
1858
|
-
|
|
1859
|
-
|
|
1857
|
+
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
|
|
1858
|
+
if (typeof configuration.connect !== 'undefined') {
|
|
1859
|
+
this.shouldConnect = configuration.connect;
|
|
1860
1860
|
}
|
|
1861
|
-
this.
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
this.
|
|
1865
|
-
this.configuration.onStatus({ status });
|
|
1866
|
-
this.emit('status', { status });
|
|
1867
|
-
}
|
|
1868
|
-
setConfiguration(configuration = {}) {
|
|
1869
|
-
this.configuration = { ...this.configuration, ...configuration };
|
|
1861
|
+
if (!this.shouldConnect) {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
this.connect();
|
|
1870
1865
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1866
|
+
async onOpen(event) {
|
|
1867
|
+
this.receivedOnOpenPayload = event;
|
|
1873
1868
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1869
|
+
async onStatus(data) {
|
|
1870
|
+
this.receivedOnStatusPayload = data;
|
|
1876
1871
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1872
|
+
attach(provider) {
|
|
1873
|
+
if (this.receivedOnOpenPayload) {
|
|
1874
|
+
provider.onOpen(this.receivedOnOpenPayload);
|
|
1875
|
+
}
|
|
1876
|
+
if (this.receivedOnStatusPayload) {
|
|
1877
|
+
provider.onStatus(this.receivedOnStatusPayload);
|
|
1878
|
+
}
|
|
1879
1879
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1880
|
+
detach(provider) {
|
|
1881
|
+
// tell the server to remove the listener
|
|
1882
1882
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1883
|
+
setConfiguration(configuration = {}) {
|
|
1884
|
+
this.configuration = { ...this.configuration, ...configuration };
|
|
1885
1885
|
}
|
|
1886
|
-
|
|
1887
|
-
if (
|
|
1886
|
+
async connect() {
|
|
1887
|
+
if (this.status === exports.WebSocketStatus.Connected) {
|
|
1888
1888
|
return;
|
|
1889
1889
|
}
|
|
1890
|
-
|
|
1890
|
+
// Always cancel any previously initiated connection retryer instances
|
|
1891
|
+
if (this.cancelWebsocketRetry) {
|
|
1892
|
+
this.cancelWebsocketRetry();
|
|
1893
|
+
this.cancelWebsocketRetry = undefined;
|
|
1894
|
+
}
|
|
1895
|
+
this.shouldConnect = true;
|
|
1896
|
+
const abortableRetry = () => {
|
|
1897
|
+
let cancelAttempt = false;
|
|
1898
|
+
const retryPromise = attempt.retry(this.createWebSocketConnection.bind(this), {
|
|
1899
|
+
delay: this.configuration.delay,
|
|
1900
|
+
initialDelay: this.configuration.initialDelay,
|
|
1901
|
+
factor: this.configuration.factor,
|
|
1902
|
+
maxAttempts: this.configuration.maxAttempts,
|
|
1903
|
+
minDelay: this.configuration.minDelay,
|
|
1904
|
+
maxDelay: this.configuration.maxDelay,
|
|
1905
|
+
jitter: this.configuration.jitter,
|
|
1906
|
+
timeout: this.configuration.timeout,
|
|
1907
|
+
beforeAttempt: context => {
|
|
1908
|
+
if (!this.shouldConnect || cancelAttempt) {
|
|
1909
|
+
context.abort();
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
}).catch((error) => {
|
|
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 (error && error.code !== 'ATTEMPT_ABORTED') {
|
|
1916
|
+
throw error;
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
return {
|
|
1920
|
+
retryPromise,
|
|
1921
|
+
cancelFunc: () => {
|
|
1922
|
+
cancelAttempt = true;
|
|
1923
|
+
},
|
|
1924
|
+
};
|
|
1925
|
+
};
|
|
1926
|
+
const { retryPromise, cancelFunc } = abortableRetry();
|
|
1927
|
+
this.cancelWebsocketRetry = cancelFunc;
|
|
1928
|
+
return retryPromise;
|
|
1891
1929
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1930
|
+
createWebSocketConnection() {
|
|
1931
|
+
return new Promise((resolve, reject) => {
|
|
1932
|
+
if (this.webSocket) {
|
|
1933
|
+
this.webSocket.close();
|
|
1934
|
+
this.webSocket = null;
|
|
1935
|
+
}
|
|
1936
|
+
// Init the WebSocket connection
|
|
1937
|
+
const ws = new this.configuration.WebSocketPolyfill(this.url);
|
|
1938
|
+
ws.binaryType = 'arraybuffer';
|
|
1939
|
+
ws.onmessage = (payload) => this.emit('message', payload);
|
|
1940
|
+
ws.onclose = (payload) => this.emit('close', { event: payload });
|
|
1941
|
+
ws.onopen = (payload) => this.emit('open', payload);
|
|
1942
|
+
ws.onerror = (err) => {
|
|
1943
|
+
reject(err);
|
|
1944
|
+
};
|
|
1945
|
+
this.webSocket = ws;
|
|
1946
|
+
// Reset the status
|
|
1947
|
+
this.status = exports.WebSocketStatus.Connecting;
|
|
1948
|
+
this.emit('status', { status: exports.WebSocketStatus.Connecting });
|
|
1949
|
+
// Store resolve/reject for later use
|
|
1950
|
+
this.connectionAttempt = {
|
|
1951
|
+
resolve,
|
|
1952
|
+
reject,
|
|
1953
|
+
};
|
|
1954
|
+
});
|
|
1894
1955
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1956
|
+
onMessage(event) {
|
|
1957
|
+
this.resolveConnectionAttempt();
|
|
1958
|
+
}
|
|
1959
|
+
resolveConnectionAttempt() {
|
|
1960
|
+
if (this.connectionAttempt) {
|
|
1961
|
+
this.connectionAttempt.resolve();
|
|
1962
|
+
this.connectionAttempt = null;
|
|
1963
|
+
this.status = exports.WebSocketStatus.Connected;
|
|
1964
|
+
this.emit('status', { status: exports.WebSocketStatus.Connected });
|
|
1965
|
+
this.emit('connect');
|
|
1898
1966
|
}
|
|
1899
|
-
this.unsyncedChanges += 1;
|
|
1900
|
-
this.send(UpdateMessage, { update, documentName: this.configuration.name }, true);
|
|
1901
1967
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
this.send(AwarenessMessage, {
|
|
1905
|
-
awareness: this.awareness,
|
|
1906
|
-
clients: changedClients,
|
|
1907
|
-
documentName: this.configuration.name,
|
|
1908
|
-
}, true);
|
|
1968
|
+
stopConnectionAttempt() {
|
|
1969
|
+
this.connectionAttempt = null;
|
|
1909
1970
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1971
|
+
rejectConnectionAttempt() {
|
|
1972
|
+
var _a;
|
|
1973
|
+
(_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
|
|
1974
|
+
this.connectionAttempt = null;
|
|
1912
1975
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1976
|
+
checkConnection() {
|
|
1977
|
+
var _a;
|
|
1978
|
+
// Don’t check the connection when it’s not even established
|
|
1979
|
+
if (this.status !== exports.WebSocketStatus.Connected) {
|
|
1915
1980
|
return;
|
|
1916
1981
|
}
|
|
1917
|
-
|
|
1918
|
-
this.
|
|
1919
|
-
|
|
1982
|
+
// Don’t close then connection while waiting for the first message
|
|
1983
|
+
if (!this.lastMessageReceived) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
// Don’t close the connection when a message was received recently
|
|
1987
|
+
if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
// No message received in a long time, not even your own
|
|
1991
|
+
// Awareness updates, which are updated every 15 seconds.
|
|
1992
|
+
(_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
|
|
1920
1993
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1994
|
+
registerEventListeners() {
|
|
1995
|
+
if (typeof window === 'undefined') {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
window.addEventListener('online', this.boundConnect);
|
|
1923
1999
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
2000
|
+
// Ensure that the URL always ends with /
|
|
2001
|
+
get serverUrl() {
|
|
2002
|
+
while (this.configuration.url[this.configuration.url.length - 1] === '/') {
|
|
2003
|
+
return this.configuration.url.slice(0, this.configuration.url.length - 1);
|
|
2004
|
+
}
|
|
2005
|
+
return this.configuration.url;
|
|
1926
2006
|
}
|
|
1927
|
-
|
|
1928
|
-
this.
|
|
1929
|
-
this.
|
|
2007
|
+
get url() {
|
|
2008
|
+
const encodedParams = encodeQueryParams(this.configuration.parameters);
|
|
2009
|
+
return `${this.serverUrl}${encodedParams.length === 0 ? '' : `?${encodedParams}`}`;
|
|
1930
2010
|
}
|
|
1931
|
-
|
|
1932
|
-
this.
|
|
1933
|
-
if (this.
|
|
1934
|
-
this.send(AuthenticationMessage, {
|
|
1935
|
-
token: await this.getToken(),
|
|
1936
|
-
documentName: this.configuration.name,
|
|
1937
|
-
});
|
|
2011
|
+
disconnect() {
|
|
2012
|
+
this.shouldConnect = false;
|
|
2013
|
+
if (this.webSocket === null) {
|
|
1938
2014
|
return;
|
|
1939
2015
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
async getToken() {
|
|
1943
|
-
if (typeof this.configuration.token === 'function') {
|
|
1944
|
-
const token = await this.configuration.token();
|
|
1945
|
-
return token;
|
|
2016
|
+
try {
|
|
2017
|
+
this.webSocket.close();
|
|
1946
2018
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
startSync() {
|
|
1950
|
-
this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
|
|
1951
|
-
if (this.awareness.getLocalState() !== null) {
|
|
1952
|
-
this.send(AwarenessMessage, {
|
|
1953
|
-
awareness: this.awareness,
|
|
1954
|
-
clients: [this.document.clientID],
|
|
1955
|
-
documentName: this.configuration.name,
|
|
1956
|
-
});
|
|
2019
|
+
catch {
|
|
2020
|
+
//
|
|
1957
2021
|
}
|
|
1958
2022
|
}
|
|
1959
|
-
send(message
|
|
1960
|
-
|
|
1961
|
-
|
|
2023
|
+
send(message) {
|
|
2024
|
+
var _a;
|
|
2025
|
+
if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === common.WsReadyStates.Open) {
|
|
2026
|
+
this.webSocket.send(message);
|
|
1962
2027
|
}
|
|
1963
|
-
const messageSender = new MessageSender(message, args);
|
|
1964
|
-
this.emit('outgoingMessage', { message: messageSender.message });
|
|
1965
|
-
messageSender.send(this.configuration.websocketProvider);
|
|
1966
2028
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2029
|
+
onClose({ event }) {
|
|
2030
|
+
this.webSocket = null;
|
|
2031
|
+
if (this.status === exports.WebSocketStatus.Connected) {
|
|
2032
|
+
this.status = exports.WebSocketStatus.Disconnected;
|
|
2033
|
+
this.emit('status', { status: exports.WebSocketStatus.Disconnected });
|
|
2034
|
+
this.emit('disconnect', { event });
|
|
1972
2035
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2036
|
+
if (event.code === common.Unauthorized.code) {
|
|
2037
|
+
if (event.reason === common.Unauthorized.reason) {
|
|
2038
|
+
console.warn('[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.');
|
|
2039
|
+
}
|
|
2040
|
+
else {
|
|
2041
|
+
console.warn(`[HocuspocusProvider] Connection closed with status Unauthorized: ${event.reason}`);
|
|
2042
|
+
}
|
|
2043
|
+
this.shouldConnect = false;
|
|
2044
|
+
}
|
|
2045
|
+
if (event.code === common.Forbidden.code) {
|
|
2046
|
+
if (!this.configuration.quiet) {
|
|
2047
|
+
console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
|
|
2048
|
+
return; // TODO REMOVE ME
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (this.connectionAttempt) {
|
|
2052
|
+
// That connection attempt failed.
|
|
2053
|
+
this.rejectConnectionAttempt();
|
|
2054
|
+
}
|
|
2055
|
+
else if (this.shouldConnect) {
|
|
2056
|
+
// The connection was closed by the server. Let’s just try again.
|
|
2057
|
+
this.connect();
|
|
2058
|
+
}
|
|
2059
|
+
// If we’ll reconnect, we’re done for now.
|
|
2060
|
+
if (this.shouldConnect) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
// The status is set correctly already.
|
|
2064
|
+
if (this.status === exports.WebSocketStatus.Disconnected) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
// Let’s update the connection status.
|
|
2068
|
+
this.status = exports.WebSocketStatus.Disconnected;
|
|
2069
|
+
this.emit('status', { status: exports.WebSocketStatus.Disconnected });
|
|
2070
|
+
this.emit('disconnect', { event });
|
|
1982
2071
|
}
|
|
1983
2072
|
destroy() {
|
|
1984
2073
|
this.emit('destroy');
|
|
1985
2074
|
if (this.intervals.forceSync) {
|
|
1986
2075
|
clearInterval(this.intervals.forceSync);
|
|
1987
2076
|
}
|
|
1988
|
-
|
|
2077
|
+
clearInterval(this.intervals.connectionChecker);
|
|
2078
|
+
// If there is still a connection attempt outstanding then we should stop
|
|
2079
|
+
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
2080
|
+
// handler and trigger a retry
|
|
2081
|
+
this.stopConnectionAttempt();
|
|
1989
2082
|
this.disconnect();
|
|
1990
|
-
this.awareness.off('update', this.awarenessUpdateHandler);
|
|
1991
|
-
this.document.off('update', this.documentUpdateHandler);
|
|
1992
2083
|
this.removeAllListeners();
|
|
1993
2084
|
if (typeof window === 'undefined') {
|
|
1994
2085
|
return;
|
|
1995
2086
|
}
|
|
1996
|
-
window.removeEventListener('
|
|
1997
|
-
}
|
|
1998
|
-
permissionDeniedHandler(reason) {
|
|
1999
|
-
this.emit('authenticationFailed', { reason });
|
|
2000
|
-
this.isAuthenticated = false;
|
|
2001
|
-
this.disconnect();
|
|
2002
|
-
this.status = exports.WebSocketStatus.Disconnected;
|
|
2003
|
-
}
|
|
2004
|
-
authenticatedHandler() {
|
|
2005
|
-
this.isAuthenticated = true;
|
|
2006
|
-
this.emit('authenticated');
|
|
2007
|
-
this.startSync();
|
|
2008
|
-
}
|
|
2009
|
-
get broadcastChannel() {
|
|
2010
|
-
return `${this.configuration.name}`;
|
|
2011
|
-
}
|
|
2012
|
-
broadcastChannelSubscriber(data) {
|
|
2013
|
-
this.mux(() => {
|
|
2014
|
-
const message = new IncomingMessage(data);
|
|
2015
|
-
const documentName = message.readVarString();
|
|
2016
|
-
message.writeVarString(documentName);
|
|
2017
|
-
new MessageReceiver(message)
|
|
2018
|
-
.setBroadcasted(true)
|
|
2019
|
-
.apply(this, false);
|
|
2020
|
-
});
|
|
2087
|
+
window.removeEventListener('online', this.boundConnect);
|
|
2021
2088
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
this.
|
|
2028
|
-
|
|
2029
|
-
this.broadcast(SyncStepTwoMessage, { document: this.document });
|
|
2030
|
-
this.broadcast(QueryAwarenessMessage, { document: this.document });
|
|
2031
|
-
this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], document: this.document });
|
|
2032
|
-
});
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
class StatelessMessage extends OutgoingMessage {
|
|
2092
|
+
constructor() {
|
|
2093
|
+
super(...arguments);
|
|
2094
|
+
this.type = exports.MessageType.Stateless;
|
|
2095
|
+
this.description = 'A stateless message';
|
|
2033
2096
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
this.
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
documentName: this.configuration.name,
|
|
2041
|
-
}, true);
|
|
2042
|
-
if (this.subscribedToBroadcastChannel) {
|
|
2043
|
-
unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
|
|
2044
|
-
this.subscribedToBroadcastChannel = false;
|
|
2045
|
-
}
|
|
2097
|
+
get(args) {
|
|
2098
|
+
var _a;
|
|
2099
|
+
writeVarString(this.encoder, args.documentName);
|
|
2100
|
+
writeVarUint(this.encoder, this.type);
|
|
2101
|
+
writeVarString(this.encoder, (_a = args.payload) !== null && _a !== void 0 ? _a : '');
|
|
2102
|
+
return this.encoder;
|
|
2046
2103
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
new MessageSender(Message, args).broadcast(this.broadcastChannel);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
class CloseMessage extends OutgoingMessage {
|
|
2107
|
+
constructor() {
|
|
2108
|
+
super(...arguments);
|
|
2109
|
+
this.type = exports.MessageType.CLOSE;
|
|
2110
|
+
this.description = 'Ask the server to close the connection';
|
|
2055
2111
|
}
|
|
2056
|
-
|
|
2057
|
-
this.
|
|
2112
|
+
get(args) {
|
|
2113
|
+
writeVarString(this.encoder, args.documentName);
|
|
2114
|
+
writeVarUint(this.encoder, this.type);
|
|
2115
|
+
return this.encoder;
|
|
2058
2116
|
}
|
|
2059
2117
|
}
|
|
2060
2118
|
|
|
2061
|
-
|
|
2062
|
-
* Utility module to work with urls.
|
|
2063
|
-
*
|
|
2064
|
-
* @module url
|
|
2065
|
-
*/
|
|
2066
|
-
|
|
2067
|
-
/**
|
|
2068
|
-
* @param {Object<string,string>} params
|
|
2069
|
-
* @return {string}
|
|
2070
|
-
*/
|
|
2071
|
-
const encodeQueryParams = params =>
|
|
2072
|
-
map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
|
|
2073
|
-
|
|
2074
|
-
class HocuspocusProviderWebsocket extends EventEmitter {
|
|
2119
|
+
class HocuspocusProvider extends EventEmitter {
|
|
2075
2120
|
constructor(configuration) {
|
|
2076
2121
|
super();
|
|
2077
2122
|
this.configuration = {
|
|
2078
|
-
|
|
2123
|
+
name: '',
|
|
2079
2124
|
// @ts-ignore
|
|
2080
2125
|
document: undefined,
|
|
2081
2126
|
// @ts-ignore
|
|
2082
2127
|
awareness: undefined,
|
|
2083
|
-
|
|
2128
|
+
token: null,
|
|
2084
2129
|
parameters: {},
|
|
2085
|
-
connect: true,
|
|
2086
2130
|
broadcast: true,
|
|
2087
2131
|
forceSyncInterval: false,
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
// 1 second
|
|
2091
|
-
delay: 1000,
|
|
2092
|
-
// instant
|
|
2093
|
-
initialDelay: 0,
|
|
2094
|
-
// double the delay each time
|
|
2095
|
-
factor: 2,
|
|
2096
|
-
// unlimited retries
|
|
2097
|
-
maxAttempts: 0,
|
|
2098
|
-
// wait at least 1 second
|
|
2099
|
-
minDelay: 1000,
|
|
2100
|
-
// at least every 30 seconds
|
|
2101
|
-
maxDelay: 30000,
|
|
2102
|
-
// randomize
|
|
2103
|
-
jitter: true,
|
|
2104
|
-
// retry forever
|
|
2105
|
-
timeout: 0,
|
|
2132
|
+
onAuthenticated: () => null,
|
|
2133
|
+
onAuthenticationFailed: () => null,
|
|
2106
2134
|
onOpen: () => null,
|
|
2107
2135
|
onConnect: () => null,
|
|
2108
2136
|
onMessage: () => null,
|
|
2109
2137
|
onOutgoingMessage: () => null,
|
|
2110
2138
|
onStatus: () => null,
|
|
2139
|
+
onSynced: () => null,
|
|
2111
2140
|
onDisconnect: () => null,
|
|
2112
2141
|
onClose: () => null,
|
|
2113
2142
|
onDestroy: () => null,
|
|
2114
2143
|
onAwarenessUpdate: () => null,
|
|
2115
2144
|
onAwarenessChange: () => null,
|
|
2145
|
+
onStateless: () => null,
|
|
2116
2146
|
quiet: false,
|
|
2117
2147
|
};
|
|
2118
2148
|
this.subscribedToBroadcastChannel = false;
|
|
2119
|
-
this.
|
|
2120
|
-
this.
|
|
2149
|
+
this.isSynced = false;
|
|
2150
|
+
this.unsyncedChanges = 0;
|
|
2121
2151
|
this.status = exports.WebSocketStatus.Disconnected;
|
|
2122
|
-
this.
|
|
2152
|
+
this.isAuthenticated = false;
|
|
2123
2153
|
this.mux = createMutex();
|
|
2124
2154
|
this.intervals = {
|
|
2125
2155
|
forceSync: null,
|
|
2126
|
-
connectionChecker: null,
|
|
2127
2156
|
};
|
|
2128
|
-
this.
|
|
2129
|
-
this.
|
|
2130
|
-
this.
|
|
2131
|
-
this.boundConnect = this.connect.bind(this);
|
|
2157
|
+
this.isConnected = true;
|
|
2158
|
+
this.boundBeforeUnload = this.beforeUnload.bind(this);
|
|
2159
|
+
this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
|
|
2132
2160
|
this.setConfiguration(configuration);
|
|
2133
|
-
this.configuration.
|
|
2161
|
+
this.configuration.document = configuration.document ? configuration.document : new Y__namespace.Doc();
|
|
2162
|
+
this.configuration.awareness = configuration.awareness ? configuration.awareness : new Awareness(this.document);
|
|
2134
2163
|
this.on('open', this.configuration.onOpen);
|
|
2135
|
-
this.on('open', this.onOpen.bind(this));
|
|
2136
|
-
this.on('connect', this.configuration.onConnect);
|
|
2137
2164
|
this.on('message', this.configuration.onMessage);
|
|
2138
2165
|
this.on('outgoingMessage', this.configuration.onOutgoingMessage);
|
|
2139
|
-
this.on('
|
|
2140
|
-
this.on('status', this.onStatus.bind(this));
|
|
2141
|
-
this.on('disconnect', this.configuration.onDisconnect);
|
|
2142
|
-
this.on('close', this.configuration.onClose);
|
|
2166
|
+
this.on('synced', this.configuration.onSynced);
|
|
2143
2167
|
this.on('destroy', this.configuration.onDestroy);
|
|
2144
2168
|
this.on('awarenessUpdate', this.configuration.onAwarenessUpdate);
|
|
2145
2169
|
this.on('awarenessChange', this.configuration.onAwarenessChange);
|
|
2146
|
-
this.on('
|
|
2147
|
-
this.on('
|
|
2170
|
+
this.on('stateless', this.configuration.onStateless);
|
|
2171
|
+
this.on('authenticated', this.configuration.onAuthenticated);
|
|
2172
|
+
this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
|
|
2173
|
+
this.configuration.websocketProvider.on('connect', this.configuration.onConnect);
|
|
2174
|
+
this.configuration.websocketProvider.on('connect', (e) => this.emit('connect', e));
|
|
2175
|
+
this.configuration.websocketProvider.on('open', this.onOpen.bind(this));
|
|
2176
|
+
this.configuration.websocketProvider.on('open', (e) => this.emit('open', e));
|
|
2177
|
+
this.configuration.websocketProvider.on('message', this.onMessage.bind(this));
|
|
2178
|
+
this.configuration.websocketProvider.on('close', this.onClose.bind(this));
|
|
2179
|
+
this.configuration.websocketProvider.on('close', this.configuration.onClose);
|
|
2180
|
+
this.configuration.websocketProvider.on('close', (e) => this.emit('close', e));
|
|
2181
|
+
this.configuration.websocketProvider.on('status', this.onStatus.bind(this));
|
|
2182
|
+
this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect);
|
|
2183
|
+
this.configuration.websocketProvider.on('disconnect', (e) => this.emit('disconnect', e));
|
|
2184
|
+
this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy);
|
|
2185
|
+
this.configuration.websocketProvider.on('destroy', (e) => this.emit('destroy', e));
|
|
2186
|
+
this.awareness.on('update', () => {
|
|
2187
|
+
this.emit('awarenessUpdate', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
|
|
2188
|
+
});
|
|
2189
|
+
this.awareness.on('change', () => {
|
|
2190
|
+
this.emit('awarenessChange', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
|
|
2191
|
+
});
|
|
2192
|
+
this.document.on('update', this.documentUpdateHandler.bind(this));
|
|
2193
|
+
this.awareness.on('update', this.awarenessUpdateHandler.bind(this));
|
|
2148
2194
|
this.registerEventListeners();
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
this.shouldConnect = configuration.connect;
|
|
2195
|
+
if (this.configuration.forceSyncInterval) {
|
|
2196
|
+
this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
|
|
2152
2197
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2198
|
+
this.configuration.websocketProvider.attach(this);
|
|
2199
|
+
}
|
|
2200
|
+
onStatus({ status }) {
|
|
2201
|
+
this.status = status;
|
|
2202
|
+
this.configuration.onStatus({ status });
|
|
2203
|
+
this.emit('status', { status });
|
|
2204
|
+
}
|
|
2205
|
+
setConfiguration(configuration = {}) {
|
|
2206
|
+
if (!configuration.websocketProvider && configuration.url) {
|
|
2207
|
+
this.configuration.websocketProvider = new HocuspocusProviderWebsocket({ url: configuration.url });
|
|
2155
2208
|
}
|
|
2156
|
-
this.
|
|
2209
|
+
this.configuration = { ...this.configuration, ...configuration };
|
|
2157
2210
|
}
|
|
2158
|
-
|
|
2159
|
-
this.
|
|
2211
|
+
get document() {
|
|
2212
|
+
return this.configuration.document;
|
|
2160
2213
|
}
|
|
2161
|
-
|
|
2162
|
-
this.
|
|
2214
|
+
get awareness() {
|
|
2215
|
+
return this.configuration.awareness;
|
|
2163
2216
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
provider.onOpen(this.receivedOnOpenPayload);
|
|
2167
|
-
}
|
|
2168
|
-
if (this.receivedOnStatusPayload) {
|
|
2169
|
-
provider.onStatus(this.receivedOnStatusPayload);
|
|
2170
|
-
}
|
|
2217
|
+
get hasUnsyncedChanges() {
|
|
2218
|
+
return this.unsyncedChanges > 0;
|
|
2171
2219
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2220
|
+
forceSync() {
|
|
2221
|
+
this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
|
|
2174
2222
|
}
|
|
2175
|
-
|
|
2176
|
-
this.
|
|
2223
|
+
beforeUnload() {
|
|
2224
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'window unload');
|
|
2177
2225
|
}
|
|
2178
|
-
|
|
2179
|
-
if (
|
|
2226
|
+
registerEventListeners() {
|
|
2227
|
+
if (typeof window === 'undefined') {
|
|
2180
2228
|
return;
|
|
2181
2229
|
}
|
|
2182
|
-
|
|
2183
|
-
if (this.cancelWebsocketRetry) {
|
|
2184
|
-
this.cancelWebsocketRetry();
|
|
2185
|
-
this.cancelWebsocketRetry = undefined;
|
|
2186
|
-
}
|
|
2187
|
-
this.shouldConnect = true;
|
|
2188
|
-
const abortableRetry = () => {
|
|
2189
|
-
let cancelAttempt = false;
|
|
2190
|
-
const retryPromise = attempt.retry(this.createWebSocketConnection.bind(this), {
|
|
2191
|
-
delay: this.configuration.delay,
|
|
2192
|
-
initialDelay: this.configuration.initialDelay,
|
|
2193
|
-
factor: this.configuration.factor,
|
|
2194
|
-
maxAttempts: this.configuration.maxAttempts,
|
|
2195
|
-
minDelay: this.configuration.minDelay,
|
|
2196
|
-
maxDelay: this.configuration.maxDelay,
|
|
2197
|
-
jitter: this.configuration.jitter,
|
|
2198
|
-
timeout: this.configuration.timeout,
|
|
2199
|
-
beforeAttempt: context => {
|
|
2200
|
-
if (!this.shouldConnect || cancelAttempt) {
|
|
2201
|
-
context.abort();
|
|
2202
|
-
}
|
|
2203
|
-
},
|
|
2204
|
-
}).catch((error) => {
|
|
2205
|
-
// If we aborted the connection attempt then don’t throw an error
|
|
2206
|
-
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
2207
|
-
if (error && error.code !== 'ATTEMPT_ABORTED') {
|
|
2208
|
-
throw error;
|
|
2209
|
-
}
|
|
2210
|
-
});
|
|
2211
|
-
return {
|
|
2212
|
-
retryPromise,
|
|
2213
|
-
cancelFunc: () => {
|
|
2214
|
-
cancelAttempt = true;
|
|
2215
|
-
},
|
|
2216
|
-
};
|
|
2217
|
-
};
|
|
2218
|
-
const { retryPromise, cancelFunc } = abortableRetry();
|
|
2219
|
-
this.cancelWebsocketRetry = cancelFunc;
|
|
2220
|
-
return retryPromise;
|
|
2221
|
-
}
|
|
2222
|
-
createWebSocketConnection() {
|
|
2223
|
-
return new Promise((resolve, reject) => {
|
|
2224
|
-
if (this.webSocket) {
|
|
2225
|
-
this.webSocket.close();
|
|
2226
|
-
this.webSocket = null;
|
|
2227
|
-
}
|
|
2228
|
-
// Init the WebSocket connection
|
|
2229
|
-
const ws = new this.configuration.WebSocketPolyfill(this.url);
|
|
2230
|
-
ws.binaryType = 'arraybuffer';
|
|
2231
|
-
ws.onmessage = (payload) => this.emit('message', payload);
|
|
2232
|
-
ws.onclose = (payload) => this.emit('close', { event: payload });
|
|
2233
|
-
ws.onopen = (payload) => this.emit('open', payload);
|
|
2234
|
-
ws.onerror = (err) => {
|
|
2235
|
-
reject(err);
|
|
2236
|
-
};
|
|
2237
|
-
this.webSocket = ws;
|
|
2238
|
-
// Reset the status
|
|
2239
|
-
this.status = exports.WebSocketStatus.Connecting;
|
|
2240
|
-
this.emit('status', { status: exports.WebSocketStatus.Connecting });
|
|
2241
|
-
// Store resolve/reject for later use
|
|
2242
|
-
this.connectionAttempt = {
|
|
2243
|
-
resolve,
|
|
2244
|
-
reject,
|
|
2245
|
-
};
|
|
2246
|
-
});
|
|
2230
|
+
window.addEventListener('beforeunload', this.boundBeforeUnload);
|
|
2247
2231
|
}
|
|
2248
|
-
|
|
2249
|
-
this.
|
|
2232
|
+
sendStateless(payload) {
|
|
2233
|
+
this.send(StatelessMessage, { documentName: this.configuration.name, payload });
|
|
2250
2234
|
}
|
|
2251
|
-
|
|
2252
|
-
if (this
|
|
2253
|
-
|
|
2254
|
-
this.connectionAttempt = null;
|
|
2255
|
-
this.status = exports.WebSocketStatus.Connected;
|
|
2256
|
-
this.emit('status', { status: exports.WebSocketStatus.Connected });
|
|
2257
|
-
this.emit('connect');
|
|
2235
|
+
documentUpdateHandler(update, origin) {
|
|
2236
|
+
if (origin === this) {
|
|
2237
|
+
return;
|
|
2258
2238
|
}
|
|
2239
|
+
this.unsyncedChanges += 1;
|
|
2240
|
+
this.send(UpdateMessage, { update, documentName: this.configuration.name }, true);
|
|
2259
2241
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2242
|
+
awarenessUpdateHandler({ added, updated, removed }, origin) {
|
|
2243
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
2244
|
+
this.send(AwarenessMessage, {
|
|
2245
|
+
awareness: this.awareness,
|
|
2246
|
+
clients: changedClients,
|
|
2247
|
+
documentName: this.configuration.name,
|
|
2248
|
+
}, true);
|
|
2262
2249
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
(_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
|
|
2266
|
-
this.connectionAttempt = null;
|
|
2250
|
+
get synced() {
|
|
2251
|
+
return this.isSynced;
|
|
2267
2252
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
// Don’t check the connection when it’s not even established
|
|
2271
|
-
if (this.status !== exports.WebSocketStatus.Connected) {
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2274
|
-
// Don’t close then connection while waiting for the first message
|
|
2275
|
-
if (!this.lastMessageReceived) {
|
|
2276
|
-
return;
|
|
2277
|
-
}
|
|
2278
|
-
// Don’t close the connection when a message was received recently
|
|
2279
|
-
if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) {
|
|
2253
|
+
set synced(state) {
|
|
2254
|
+
if (this.isSynced === state) {
|
|
2280
2255
|
return;
|
|
2281
2256
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2257
|
+
this.isSynced = state;
|
|
2258
|
+
this.emit('synced', { state });
|
|
2259
|
+
this.emit('sync', { state });
|
|
2285
2260
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
return;
|
|
2289
|
-
}
|
|
2290
|
-
window.addEventListener('online', this.boundConnect);
|
|
2261
|
+
receiveStateless(payload) {
|
|
2262
|
+
this.emit('stateless', { payload });
|
|
2291
2263
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
while (this.configuration.url[this.configuration.url.length - 1] === '/') {
|
|
2295
|
-
return this.configuration.url.slice(0, this.configuration.url.length - 1);
|
|
2296
|
-
}
|
|
2297
|
-
return this.configuration.url;
|
|
2264
|
+
get isAuthenticationRequired() {
|
|
2265
|
+
return !!this.configuration.token && !this.isAuthenticated;
|
|
2298
2266
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
return
|
|
2267
|
+
// not needed, but provides backward compatibility with e.g. lexicla/yjs
|
|
2268
|
+
async connect() {
|
|
2269
|
+
return this.configuration.websocketProvider.connect();
|
|
2302
2270
|
}
|
|
2303
2271
|
disconnect() {
|
|
2304
|
-
this.
|
|
2305
|
-
|
|
2306
|
-
return;
|
|
2307
|
-
}
|
|
2308
|
-
try {
|
|
2309
|
-
this.webSocket.close();
|
|
2310
|
-
}
|
|
2311
|
-
catch {
|
|
2312
|
-
//
|
|
2313
|
-
}
|
|
2272
|
+
this.disconnectBroadcastChannel();
|
|
2273
|
+
this.configuration.websocketProvider.detach(this);
|
|
2314
2274
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
if (
|
|
2318
|
-
this.
|
|
2275
|
+
async onOpen(event) {
|
|
2276
|
+
this.emit('open', { event });
|
|
2277
|
+
if (this.isAuthenticationRequired) {
|
|
2278
|
+
this.send(AuthenticationMessage, {
|
|
2279
|
+
token: await this.getToken(),
|
|
2280
|
+
documentName: this.configuration.name,
|
|
2281
|
+
});
|
|
2319
2282
|
}
|
|
2283
|
+
this.startSync();
|
|
2320
2284
|
}
|
|
2321
|
-
|
|
2322
|
-
this.
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
this.emit('status', { status: exports.WebSocketStatus.Disconnected });
|
|
2326
|
-
this.emit('disconnect', { event });
|
|
2327
|
-
}
|
|
2328
|
-
if (event.code === common.Unauthorized.code) {
|
|
2329
|
-
if (!this.configuration.quiet) {
|
|
2330
|
-
console.warn('[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.');
|
|
2331
|
-
}
|
|
2332
|
-
this.shouldConnect = false;
|
|
2333
|
-
}
|
|
2334
|
-
if (event.code === common.Forbidden.code) {
|
|
2335
|
-
if (!this.configuration.quiet) {
|
|
2336
|
-
console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
|
|
2337
|
-
return; // TODO REMOVE ME
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
if (this.connectionAttempt) {
|
|
2341
|
-
// That connection attempt failed.
|
|
2342
|
-
this.rejectConnectionAttempt();
|
|
2285
|
+
async getToken() {
|
|
2286
|
+
if (typeof this.configuration.token === 'function') {
|
|
2287
|
+
const token = await this.configuration.token();
|
|
2288
|
+
return token;
|
|
2343
2289
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2290
|
+
return this.configuration.token;
|
|
2291
|
+
}
|
|
2292
|
+
startSync() {
|
|
2293
|
+
this.send(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
|
|
2294
|
+
if (this.awareness.getLocalState() !== null) {
|
|
2295
|
+
this.send(AwarenessMessage, {
|
|
2296
|
+
awareness: this.awareness,
|
|
2297
|
+
clients: [this.document.clientID],
|
|
2298
|
+
documentName: this.configuration.name,
|
|
2299
|
+
});
|
|
2347
2300
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2301
|
+
}
|
|
2302
|
+
send(message, args, broadcast = false) {
|
|
2303
|
+
if (!this.isConnected)
|
|
2350
2304
|
return;
|
|
2305
|
+
if (broadcast) {
|
|
2306
|
+
this.mux(() => { this.broadcast(message, args); });
|
|
2351
2307
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2308
|
+
const messageSender = new MessageSender(message, args);
|
|
2309
|
+
this.emit('outgoingMessage', { message: messageSender.message });
|
|
2310
|
+
messageSender.send(this.configuration.websocketProvider);
|
|
2311
|
+
}
|
|
2312
|
+
onMessage(event) {
|
|
2313
|
+
const message = new IncomingMessage(event.data);
|
|
2314
|
+
const documentName = message.readVarString();
|
|
2315
|
+
if (documentName !== this.configuration.name) {
|
|
2316
|
+
return; // message is meant for another provider
|
|
2355
2317
|
}
|
|
2356
|
-
|
|
2357
|
-
this.
|
|
2358
|
-
|
|
2359
|
-
|
|
2318
|
+
message.writeVarString(documentName);
|
|
2319
|
+
this.emit('message', { event, message: new IncomingMessage(event.data) });
|
|
2320
|
+
new MessageReceiver(message).apply(this);
|
|
2321
|
+
}
|
|
2322
|
+
onClose(event) {
|
|
2323
|
+
this.isAuthenticated = false;
|
|
2324
|
+
this.synced = false;
|
|
2325
|
+
// update awareness (all users except local left)
|
|
2326
|
+
removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter(client => client !== this.document.clientID), this);
|
|
2360
2327
|
}
|
|
2361
2328
|
destroy() {
|
|
2362
2329
|
this.emit('destroy');
|
|
2363
2330
|
if (this.intervals.forceSync) {
|
|
2364
2331
|
clearInterval(this.intervals.forceSync);
|
|
2365
2332
|
}
|
|
2366
|
-
|
|
2367
|
-
// If there is still a connection attempt outstanding then we should stop
|
|
2368
|
-
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
2369
|
-
// handler and trigger a retry
|
|
2370
|
-
this.stopConnectionAttempt();
|
|
2333
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
|
|
2371
2334
|
this.disconnect();
|
|
2335
|
+
this.awareness.off('update', this.awarenessUpdateHandler);
|
|
2336
|
+
this.document.off('update', this.documentUpdateHandler);
|
|
2372
2337
|
this.removeAllListeners();
|
|
2338
|
+
this.send(CloseMessage, { documentName: this.configuration.name });
|
|
2339
|
+
this.isConnected = false;
|
|
2373
2340
|
if (typeof window === 'undefined') {
|
|
2374
2341
|
return;
|
|
2375
2342
|
}
|
|
2376
|
-
window.removeEventListener('
|
|
2343
|
+
window.removeEventListener('beforeunload', this.boundBeforeUnload);
|
|
2344
|
+
}
|
|
2345
|
+
permissionDeniedHandler(reason) {
|
|
2346
|
+
this.emit('authenticationFailed', { reason });
|
|
2347
|
+
this.isAuthenticated = false;
|
|
2348
|
+
this.disconnect();
|
|
2349
|
+
this.status = exports.WebSocketStatus.Disconnected;
|
|
2350
|
+
}
|
|
2351
|
+
authenticatedHandler() {
|
|
2352
|
+
this.isAuthenticated = true;
|
|
2353
|
+
this.emit('authenticated');
|
|
2354
|
+
this.startSync();
|
|
2355
|
+
}
|
|
2356
|
+
get broadcastChannel() {
|
|
2357
|
+
return `${this.configuration.name}`;
|
|
2358
|
+
}
|
|
2359
|
+
broadcastChannelSubscriber(data) {
|
|
2360
|
+
this.mux(() => {
|
|
2361
|
+
const message = new IncomingMessage(data);
|
|
2362
|
+
const documentName = message.readVarString();
|
|
2363
|
+
message.writeVarString(documentName);
|
|
2364
|
+
new MessageReceiver(message)
|
|
2365
|
+
.setBroadcasted(true)
|
|
2366
|
+
.apply(this, false);
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
subscribeToBroadcastChannel() {
|
|
2370
|
+
if (!this.subscribedToBroadcastChannel) {
|
|
2371
|
+
subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
|
|
2372
|
+
this.subscribedToBroadcastChannel = true;
|
|
2373
|
+
}
|
|
2374
|
+
this.mux(() => {
|
|
2375
|
+
this.broadcast(SyncStepOneMessage, { document: this.document });
|
|
2376
|
+
this.broadcast(SyncStepTwoMessage, { document: this.document });
|
|
2377
|
+
this.broadcast(QueryAwarenessMessage, { document: this.document });
|
|
2378
|
+
this.broadcast(AwarenessMessage, { awareness: this.awareness, clients: [this.document.clientID], document: this.document });
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
disconnectBroadcastChannel() {
|
|
2382
|
+
// broadcast message with local awareness state set to null (indicating disconnect)
|
|
2383
|
+
this.send(AwarenessMessage, {
|
|
2384
|
+
awareness: this.awareness,
|
|
2385
|
+
clients: [this.document.clientID],
|
|
2386
|
+
states: new Map(),
|
|
2387
|
+
documentName: this.configuration.name,
|
|
2388
|
+
}, true);
|
|
2389
|
+
if (this.subscribedToBroadcastChannel) {
|
|
2390
|
+
unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
|
|
2391
|
+
this.subscribedToBroadcastChannel = false;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
broadcast(Message, args) {
|
|
2395
|
+
if (!this.configuration.broadcast) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
if (!this.subscribedToBroadcastChannel) {
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
new MessageSender(Message, args).broadcast(this.broadcastChannel);
|
|
2402
|
+
}
|
|
2403
|
+
setAwarenessField(key, value) {
|
|
2404
|
+
this.awareness.setLocalStateField(key, value);
|
|
2377
2405
|
}
|
|
2378
2406
|
}
|
|
2379
2407
|
|