UNPKG

@tldraw/sync-core

Version:

tldraw infinite canvas SDK (multiplayer sync).

119 lines (118 loc) 4.45 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var chunk_exports = {}; __export(chunk_exports, { JsonChunkAssembler: () => JsonChunkAssembler, chunk: () => chunk }); module.exports = __toCommonJS(chunk_exports); const MAX_CLIENT_SENT_MESSAGE_SIZE_BYTES = 1024 * 1024; const MAX_BYTES_PER_CHAR = 4; const MAX_SAFE_MESSAGE_SIZE = MAX_CLIENT_SENT_MESSAGE_SIZE_BYTES / MAX_BYTES_PER_CHAR; function chunk(msg, maxSafeMessageSize = MAX_SAFE_MESSAGE_SIZE) { if (msg.length < maxSafeMessageSize) { return [msg]; } else { const chunks = []; let chunkNumber = 0; let offset = msg.length; while (offset > 0) { const prefix = `${chunkNumber}_`; const chunkSize = Math.max(Math.min(maxSafeMessageSize - prefix.length, offset), 1); chunks.unshift(prefix + msg.slice(offset - chunkSize, offset)); offset -= chunkSize; chunkNumber++; } return chunks; } } const chunkRe = /^(\d+)_(.*)$/; class JsonChunkAssembler { /** * Current assembly state - either 'idle' or tracking chunks being received */ state = "idle"; /** * Processes a single message, which can be either a complete JSON object or a chunk. * For complete JSON objects (starting with '\{'), parses immediately. * For chunks (prefixed with "\{number\}_"), accumulates until all chunks received. * * @param msg - The message to process, either JSON or chunk format * @returns Result object with data/stringified on success, error object on failure, or null for incomplete chunks * - `\{ data: object, stringified: string \}` - Successfully parsed complete message * - `\{ error: Error \}` - Parse error or invalid chunk sequence * - `null` - Chunk received but more chunks expected * * @example * ```ts * const assembler = new JsonChunkAssembler() * * // Complete JSON message * const result = assembler.handleMessage('{"key": "value"}') * if (result && 'data' in result) { * console.log(result.data) // { key: "value" } * } * * // Chunked message sequence * assembler.handleMessage('2_hel') // null - more chunks expected * assembler.handleMessage('1_lo ') // null - more chunks expected * assembler.handleMessage('0_wor') // { data: "hello wor", stringified: "hello wor" } * ``` */ handleMessage(msg) { if (msg.startsWith("{")) { const error = this.state === "idle" ? void 0 : new Error("Unexpected non-chunk message"); this.state = "idle"; return error ? { error } : { data: JSON.parse(msg), stringified: msg }; } else { const match = chunkRe.exec(msg); if (!match) { this.state = "idle"; return { error: new Error("Invalid chunk: " + JSON.stringify(msg.slice(0, 20) + "...")) }; } const numChunksRemaining = Number(match[1]); const data = match[2]; if (this.state === "idle") { this.state = { chunksReceived: [data], totalChunks: numChunksRemaining + 1 }; } else { this.state.chunksReceived.push(data); if (numChunksRemaining !== this.state.totalChunks - this.state.chunksReceived.length) { this.state = "idle"; return { error: new Error(`Chunks received in wrong order`) }; } } if (this.state.chunksReceived.length === this.state.totalChunks) { try { const stringified = this.state.chunksReceived.join(""); const data2 = JSON.parse(stringified); return { data: data2, stringified }; } catch (e) { return { error: e }; } finally { this.state = "idle"; } } return null; } } } //# sourceMappingURL=chunk.js.map