Files
AFFiNE-Mirror/libs/datasource/jwt-rpc/src/wsprovider.ts
T
2022-09-14 17:32:08 +08:00

132 lines
3.6 KiB
TypeScript

import * as Y from 'yjs';
import { Observable } from 'lib0/observable';
import * as url from 'lib0/url';
import * as awarenessProtocol from 'y-protocols/awareness';
import { handler } from './handler';
import { registerWsUpdateHandler } from './processor';
import { registerWebsocket } from './websocket';
/**
* Websocket Provider for Yjs. Creates a websocket connection to sync the shared document.
* The document name is attached to the provided url. I.e. the following example
* creates a websocket connection to http://localhost:3000/my-document-name
*
* @example
* import * as Y from 'yjs'
* import { WebsocketProvider } from 'jwt-rpc'
* const doc = new Y.Doc()
* const provider = new WebsocketProvider('http://localhost:3000', 'my-document-name', doc)
*/
export class WebsocketProvider extends Observable<string> {
maxBackOffTime: number;
url: string;
roomName: string;
awareness: awarenessProtocol.Awareness;
doc: Y.Doc;
wsUnsuccessfulReconnects: number;
private _synced: boolean;
broadcastChannel: string;
private _broadcast?: {
broadcastMessage: (buf: ArrayBuffer) => void;
disconnect: () => void;
};
private _websocket?: {
broadcastMessage: (buf: ArrayBuffer) => void;
disconnect: () => void;
};
private _updateHandlerDestroy: () => void;
constructor(
token: string,
serverUrl: string,
roomName: string,
doc: Y.Doc,
{
awareness = new awarenessProtocol.Awareness(doc),
params = {},
resyncInterval = -1,
maxBackOffTime = 2500,
} = {}
) {
super();
this.roomName = roomName;
// ensure that url is always ends with /
while (serverUrl[serverUrl.length - 1] === '/') {
serverUrl = serverUrl.slice(0, serverUrl.length - 1);
}
this.broadcastChannel = serverUrl + '/' + roomName;
const encodedParams = url.encodeQueryParams(params);
this.url =
this.broadcastChannel +
(encodedParams.length === 0 ? '' : '?' + encodedParams);
this.awareness = awareness;
this.doc = doc;
this.maxBackOffTime = maxBackOffTime;
this.wsUnsuccessfulReconnects = 0;
this._synced = false;
this._websocket = registerWebsocket(this, token, resyncInterval);
// TODO: The current implementation will cause a broadcast storm, and then re-implement one, or just go websocket synchronization
// this._broadcast = registerBroadcastSubscriber(
// this,
// this.awareness,
// this.doc
// );
this._updateHandlerDestroy = registerWsUpdateHandler(
this,
awareness,
doc,
buf => {
this._websocket?.broadcastMessage(buf);
this._broadcast?.broadcastMessage(buf);
}
);
}
get messageHandlers() {
return handler;
}
get synced() {
return this._synced;
}
set synced(state) {
if (this._synced !== state) {
this._synced = state;
this.emit('synced', [state]);
this.emit('sync', [state]);
}
}
override destroy() {
if (this._broadcast) {
const disconnect = this._broadcast.disconnect;
this._broadcast = undefined;
disconnect();
}
if (this._websocket) {
const disconnect = this._websocket.disconnect;
this._websocket = undefined;
disconnect();
}
this._updateHandlerDestroy?.();
super.destroy();
}
}