UNPKG

codelabs.y-socket.io

Version:

Socket IO Connector for Yjs (Inspired by y-websocket)

241 lines (237 loc) 9.36 kB
import * as Y from 'yjs'; import { Namespace, Server } from 'socket.io'; import * as AwarenessProtocol from 'y-protocols/awareness'; import { Observable } from 'lib0/observable'; /** * Document callbacks. Here you can set: * - onUpdate: Set a callback that will be triggered when the document is updated * - onChangeAwareness: Set a callback that will be triggered when the awareness is updated * - onDestroy: Set a callback that will be triggered when the document is destroyed */ interface Callbacks { /** * Set a callback that will be triggered when the document is updated */ onUpdate?: (doc: Document, docUpdate: Uint8Array) => void; /** * Set a callback that will be triggered when the awareness is updated */ onChangeAwareness?: (doc: Document, awarenessUpdate: Uint8Array) => void; /** * Set a callback that will be triggered when the document is destroyed */ onDestroy?: (doc: Document) => Promise<void>; } /** * YSocketIO document */ declare class Document extends Y.Doc { /** * The document name * @type {string} */ name: string; /** * The socket connection * @type {Namespace} * @private */ private readonly namespace; /** * The document awareness * @type {Awareness} */ awareness: AwarenessProtocol.Awareness; /** * The document callbacks * @type {Callbacks} * @private */ private readonly callbacks?; /** * Document constructor. * @constructor * @param {string} name Name for the document * @param {Namespace} namespace The namespace connection * @param {Callbacks} callbacks The document callbacks */ constructor(name: string, namespace: Namespace, callbacks?: Callbacks); /** * Handles the document's update and emit eht changes to clients. * @type {(update: Uint8Array) => void} * @param {Uint8Array} update * @private */ private readonly onUpdateDoc; /** * Handles the awareness update and emit the changes to clients. * @type {({ added, updated, removed }: { added: number[], updated: number[], removed: number[] }, _socket: Socket | null) => void} * @param {AwarenessChange} awarenessChange * @param {Socket | null} _socket * @private */ private readonly onUpdateAwareness; /** * Destroy the document and remove the listeners. * @type {() => Promise<void>} */ destroy(): Promise<void>; } /** * Level db persistence object */ interface Persistence { bindState: (docName: string, ydoc: Document) => void; writeState: (docName: string, ydoc: Document) => Promise<any>; provider: any; } /** * YSocketIO instance cofiguration. Here you can configure: * - gcEnabled: Enable/Disable garbage collection (default: gc=true) * - levelPersistenceDir: The directory path where the persistent Level database will be stored * - authenticate: The callback to authenticate the client connection */ interface YSocketIOConfiguration { /** * Enable/Disable garbage collection (default: gc=true) */ gcEnabled?: boolean; /** * The directory path where the persistent Level database will be stored */ levelPersistenceDir?: string; /** * Callback to authenticate the client connection. * * It can be a promise and if it returns true, the connection is allowed; otherwise, if it returns false, the connection is rejected. * @param handshake Provided from the handshake attribute of the socket io */ authenticate?: (handshake: { [key: string]: any; }) => Promise<boolean> | boolean; } /** * YSocketIO class. This handles document synchronization. */ declare class YSocketIO extends Observable<string> { /** * @type {Map<string, Document>} */ private readonly _documents; /** * @type {Server} */ private readonly io; /** * @type {string | undefined | null} */ private readonly _levelPersistenceDir; /** * @type {Persistence | null} */ private persistence; /** * @type {YSocketIOConfiguration} */ private readonly configuration?; /** * YSocketIO constructor. * @constructor * @param {Server} io Server instance from Socket IO * @param {YSocketIOConfiguration} configuration (Optional) The YSocketIO configuration */ constructor(io: Server, configuration?: YSocketIOConfiguration); /** * YSocketIO initialization. * * This method set ups a dynamic namespace manager for namespaces that match with the regular expression `/^\/yjs\|.*$/` * and adds the connection authentication middleware to the dynamics namespaces. * * It also starts socket connection listeners. * @type {() => void} */ initialize(): void; /** * The document map's getter. If you want to delete a document externally, make sure you don't delete * the document directly from the map, instead use the "destroy" method of the document you want to delete, * this way when you destroy the document you are also closing any existing connection on the document. * @type {Map<string, Document>} */ get documents(): Map<string, Document>; /** * This method creates a yjs document if it doesn't exist in the document map. If the document exists, get the map document. * * - If document is created: * - Binds the document to LevelDB if LevelDB persistence is enabled. * - Adds the new document to the documents map. * - Emit the `document-loaded` event * @private * @param {string} name The name for the document * @param {Namespace} namespace The namespace of the document * @param {boolean} gc Enable/Disable garbage collection (default: gc=true) * @returns {Promise<Document>} The document */ private initDocument; /** * This method sets persistence if enabled. * @private * @param {string} levelPersistenceDir The directory path where the persistent Level database is stored */ private initLevelDB; /** * This function initializes the socket event listeners to synchronize document changes. * * The synchronization protocol is as follows: * - A client emits the sync step one event (`sync-step-1`) which sends the document as a state vector * and the sync step two callback as an acknowledgment according to the socket io acknowledgments. * - When the server receives the `sync-step-1` event, it executes the `syncStep2` acknowledgment callback and sends * the difference between the received state vector and the local document (this difference is called an update). * - The second step of the sync is to apply the update sent in the `syncStep2` callback parameters from the server * to the document on the client side. * - There is another event (`sync-update`) that is emitted from the client, which sends an update for the document, * and when the server receives this event, it applies the received update to the local document. * - When an update is applied to a document, it will fire the document's "update" event, which * sends the update to clients connected to the document's namespace. * @private * @type {(socket: Socket, doc: Document) => void} * @param {Socket} socket The socket connection * @param {Document} doc The document */ private readonly initSyncListeners; /** * This function initializes socket event listeners to synchronize awareness changes. * * The awareness protocol is as follows: * - A client emits the `awareness-update` event by sending the awareness update. * - The server receives that event and applies the received update to the local awareness. * - When an update is applied to awareness, the awareness "update" event will fire, which * sends the update to clients connected to the document namespace. * @private * @type {(socket: Socket, doc: Document) => void} * @param {Socket} socket The socket connection * @param {Document} doc The document */ private readonly initAwarenessListeners; /** * This function initializes socket event listeners for general purposes. * * When a client has been disconnected, check the clients connected to the document namespace, * if no connection remains, emit the `all-document-connections-closed` event * parameters and if LevelDB persistence is enabled, persist the document in LevelDB and destroys it. * @private * @type {(socket: Socket, doc: Document) => void} * @param {Socket} socket The socket connection * @param {Document} doc The document */ private readonly initSocketListeners; /** * This function is called when a client connects and it emit the `sync-step-1` and `awareness-update` * events to the client to start the sync. * @private * @type {(socket: Socket, doc: Document) => void} * @param {Socket} socket The socket connection * @param {Document} doc The document */ private readonly startSynchronization; } export { Callbacks, Document, Persistence, YSocketIO, YSocketIOConfiguration };