UNPKG

synapse-react-client

Version:

[![npm version](https://badge.fury.io/js/synapse-react-client.svg)](https://badge.fury.io/js/synapse-react-client) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettie

204 lines (203 loc) 6.82 kB
import m from "./utils/json-rx/JsonRx.js"; import p from "./utils/json-rx/JsonRxNotification.js"; import a from "./utils/json-rx/JsonRxRequestComplete.js"; import f from "./utils/json-rx/JsonRxResponse.js"; import g from "./utils/json-rx/JsonRxResponseComplete.js"; import y from "./utils/MessageCounter.js"; import { splitPatch as b } from "./utils/splitPatch.js"; import { Model as S } from "json-joy/lib/json-crdt"; import { decode as k } from "json-joy/lib/json-crdt-patch/codec/compact"; import { Encoder as u } from "json-joy/lib/json-crdt/codec/structural/verbose/Encoder"; import c from "lodash-es/noop"; import C from "lodash-es/throttle"; import { Decoder as M } from "json-joy/lib/json-crdt/codec/indexed/binary/Decoder"; import { decode as P } from "cbor2"; import { fetchWithExponentialTimeout as w } from "@sage-bionetworks/synapse-client"; const v = 30 * 1024, R = 250; class L { socket; model = null; messageCounter; replicaId; verboseEncoder = new u(); maxPayloadSizeBytes; throttledSendPatch; snapshotDecoder = new M(); onModelCreate; onGridReady; onStatusChange; constructor(o) { const { replicaId: e, url: s, onGridReady: t, onStatusChange: n, onModelCreate: r, maxPayloadSizeBytes: d, socket: h, model: i, patchThrottleMs: l } = o; this.messageCounter = new y(), this.replicaId = e, this.maxPayloadSizeBytes = d ?? v, this.socket = h ?? new WebSocket(s), this.onModelCreate = r ?? c, this.onGridReady = t ?? c, this.onStatusChange = n ?? c, i && (this.model = i), this.attachSocketHandlers(), this.throttledSendPatch = C( () => this.sendPatchImmediate(), l ?? R, { leading: !1, trailing: !0 } ); } attachSocketHandlers() { this.socket.onopen = () => { console.debug("Connected to the WebSocket server"), this.onStatusChange(!0, this); }, this.socket.onmessage = (o) => { typeof o.data == "string" ? this.handleMessage(o.data) : console.error("Received non-string message data:", o.data); }, this.socket.onclose = () => { console.debug("Disconnected from the WebSocket server"), this.onStatusChange(!1, this); }, this.socket.onerror = (o) => { console.error("WebSocket error:", o); }; } disconnect() { this.throttledSendPatch.flush(), this.throttledSendPatch.cancel(), this.socket.readyState === WebSocket.OPEN ? (this.socket.close(), console.debug("WebSocket connection closed")) : console.warn("WebSocket is not open. No action taken."); } /** * Flushes the local model clock and sends the resulting patch (or a clock-sync * message) to the server so that both sides converge. */ sendClockSync() { if (!this.model) { console.error("Model is not initialized. Cannot sync model."); return; } if (!this.sendPatchImmediate()) { const e = this.verboseEncoder.encode(this.model); this.sendSyncMessage(e.time); } } handleMessage(o) { const e = m.fromJson(JSON.parse(o)); e instanceof f ? this.handleResponse( e ) : e instanceof g ? this.handleResponseComplete() : e instanceof p ? this.handleNotification(e) : console.warn("Unexpected WebSocket message format:", e); } handleResponse(o) { const e = o.getPayload(); switch (e.type) { case "patch": this.handlePatchPayload([e.body]); break; case "patches": this.handlePatchPayload(e.body); break; case "snapshot": this.handleSnapshotPayload(e.body); break; default: console.warn("Unknown payload type:", e); break; } } handlePatchPayload(o) { try { const e = o.map(k); this.model ? this.model.applyBatch(e) : (this.model = S.fromPatches(e).fork( this.replicaId ), this.onModelCreate(this.model)), this.sendClockSync(); } catch (e) { console.error("Failed to apply patches or send clock:", e); } } async handleSnapshotPayload(o) { console.debug("Received snapshot URL from server:", o); try { const e = await this.fetchAndDecodeSnapshot(o); this.model = e.fork(this.replicaId), this.onModelCreate(this.model), this.sendClockSync(); } catch (e) { console.error("Failed to fetch or decode snapshot", e); } } handleResponseComplete() { this.onGridReady(), console.debug( "Clocks synchronized with server. Incrementing sequence number." ); } handleNotification(o) { const e = o.getMethodName(); switch (console.debug("Notification received from server:", e), e) { case "ping": console.debug("Received ping from server"); break; case "connected": console.debug("Server ready to receive patches"), this.sendSyncMessage( this.model ? this.verboseEncoder.encode(this.model).time : void 0 ); break; case "error": console.warn("Error from server:", o.getPayload()); break; case "new-patch": if (!this.model) { console.warn( "Model is not initialized. Cannot handle 'new-patch' message." ); break; } { const s = this.verboseEncoder.encode(this.model); console.debug("New patch received, syncing data:", s.time); const t = new a( this.messageCounter.getNext(), "synchronize-clock", s.time ); this.sendMessage(t); } break; default: console.warn("Unknown notification method:", e); break; } } sendMessage(o) { this.socket.readyState === WebSocket.OPEN ? this.socket.send(JSON.stringify(o.getJson())) : console.error( "WebSocket is not open. Unable to send message. Current state:", this.socket.readyState ); } sendPatch() { this.throttledSendPatch(); } /** * @returns true if one or more patches were sent */ sendPatchImmediate() { if (!this.model) return console.warn("Model is not initialized. Cannot send patch."), !1; const o = this.model.api.flush(), e = o.ops.length > 0; return e && b(o, this.maxPayloadSizeBytes).forEach((t) => { console.debug("Sending patch to server:", t); const n = new a( this.messageCounter.getNext(), "patch", t ); this.sendMessage(n); }), e; } sendSyncMessage(o) { const e = new a( this.messageCounter.getNext(), "synchronize-clock", o ?? [] ); this.sendMessage(e); } async fetchAndDecodeSnapshot(o) { const t = await (await (await w(o)).blob()).arrayBuffer(), n = P(new Uint8Array(t)); return this.snapshotDecoder.decode( n ); } } export { L as DataGridWebSocket }; //# sourceMappingURL=DataGridWebSocket.js.map