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