UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

137 lines (123 loc) 4.51 kB
import type { RxCollection, RxDatabase, RxReplicationHandler } from '../../types/index.d.ts'; import type { WebSocket, ServerOptions } from 'isomorphic-ws'; import pkg from 'isomorphic-ws'; const { WebSocketServer } = pkg; import type { WebsocketMessageResponseType, WebsocketMessageType, WebsocketServerOptions, WebsocketServerState } from './websocket-types.ts'; import { rxStorageInstanceToReplicationHandler } from '../../replication-protocol/index.ts'; import { PROMISE_RESOLVE_VOID, getFromMapOrCreate } from '../../plugins/utils/index.ts'; import { Subject } from 'rxjs'; export function startSocketServer(options: ServerOptions): WebsocketServerState { const wss = new WebSocketServer(options); let closed = false; function closeServer() { if (closed) { return PROMISE_RESOLVE_VOID; } closed = true; onConnection$.complete(); return new Promise<void>((res, rej) => { /** * We have to close all client connections, * otherwise wss.close() will never call the callback. * @link https://github.com/websockets/ws/issues/1288#issuecomment-360594458 */ for (const ws of wss.clients) { ws.close(); } wss.close((err: any) => { if (err) { rej(err); } else { res(); } }); }); } const onConnection$ = new Subject<WebSocket>(); wss.on('connection', (ws: any) => onConnection$.next(ws)); return { server: wss, close: closeServer, onConnection$: onConnection$.asObservable() }; } const REPLICATION_HANDLER_BY_COLLECTION: WeakMap<RxCollection, RxReplicationHandler<any, any>> = new Map(); export function getReplicationHandlerByCollection<RxDocType>( database: RxDatabase<any>, collectionName: string ): RxReplicationHandler<RxDocType, any> { if (!database.collections[collectionName]) { throw new Error('collection ' + collectionName + ' does not exist'); } const collection = database.collections[collectionName]; const handler = getFromMapOrCreate<RxCollection, RxReplicationHandler<RxDocType, any>>( REPLICATION_HANDLER_BY_COLLECTION, collection, () => { return rxStorageInstanceToReplicationHandler( collection.storageInstance, collection.conflictHandler, database.token ); } ); return handler; } export function startWebsocketServer(options: WebsocketServerOptions): WebsocketServerState { const { database, ...wsOptions } = options; const serverState = startSocketServer(wsOptions); // auto close when the database gets closed database.onClose.push(() => serverState.close()); serverState.onConnection$.subscribe(ws => { const onCloseHandlers: Function[] = []; ws.onclose = () => { onCloseHandlers.map(fn => fn()); }; ws.on('message', async (messageString: string) => { const message: WebsocketMessageType = JSON.parse(messageString); const handler = getReplicationHandlerByCollection(database, message.collection); if (message.method === 'auth') { return; } const method = handler[message.method]; /** * If it is not a function, * it means that the client requested the masterChangeStream$ */ if (typeof method !== 'function') { const changeStreamSub = handler.masterChangeStream$.subscribe(ev => { const streamResponse: WebsocketMessageResponseType = { id: 'stream', collection: message.collection, result: ev }; ws.send(JSON.stringify(streamResponse)); }); onCloseHandlers.push(() => changeStreamSub.unsubscribe()); return; } const result = await (method as any)(...message.params); const response: WebsocketMessageResponseType = { id: message.id, collection: message.collection, result }; ws.send(JSON.stringify(response)); }); }); return serverState; }