1/** 2 * shellWss.js â Send shell NDJSON to zimserver over the WS_DBA WebSocket. 3 * 4 * Shells call sendShellNdjson() after building their NDJSON document. 5 * The function opens a one-shot authenticated WebSocket to the configured 6 * zimserver endpoint, streams the NDJSON lines one-per-message, collects 7 * any server acknowledgement lines, and resolves once the connection closes 8 * or goes idle. 9 * 10 * ââ Wire protocol âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 11 * 12 * Each NDJSON line is sent as a separate WebSocket text message. The server 13 * processes lines in order (type:"shell" first, then data records, then 14 * type:"eof" last). Response messages from the server are collected into 15 * ackLines and returned to the caller. 16 * 17 * ââ Configuration ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 18 * 19 * Endpoint â resolved via getApiBase(getActiveServerContext()) from 20 * src/shell/shared/serverContext.js. 21 * Token â resolved via getToken() from src/auth/login.js. 22 * 23 * ââ Usage âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 24 * 25 * import { sendShellNdjson, hasWssEndpoint } from "./shellWss.js"; 26 * 27 * if (hasWssEndpoint()) { 28 * const { ok, ackLines, error } = await sendShellNdjson(ndjsonDocument); 29 * if (!ok) console.error("WSS save failed:", error); 30 * } 31 */ 32 33// Updated paths: protocol.js and wssClient.js were moved to src/shell/shared/ 34// during the TitleBlock refactor. References updated from ./common/ to ../shared/. 35import { WS_DBA, buildWsUrl } from "../shared/protocol.js"; 36import { sendNdjsonOverWebSocket } from "../shared/wssClient.js"; 37import { splitNdjsonLines, buildAuthFrame, 38 parseAckLines as _parseAckLines } from "../shared/ndjsonUtils.js"; 39import { getActiveServerContext, getApiBase } from "../shared/serverContext.js"; 40import { getToken } from "../../auth/login.js"; 41 42// ââ Public helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 43 44/** 45 * Return true when a zimserver WSS endpoint is resolvable via the active 46 * server context. Use this to decide whether to show a "Send to Server" 47 * button in the UI. 48 * 49 * @returns {boolean} 50 */ 51export function hasWssEndpoint() { 52 return Boolean((getApiBase(getActiveServerContext()) || "").trim()); 53} 54 55/** 56 * Send a complete shell NDJSON document to zimserver via WS_DBA. 57 * 58 * Each NDJSON line is sent as a separate WebSocket text message. 59 * The shell envelope (type:"shell") is always the first message so the 60 * server can immediately route the stream to the correct handler. 61 * 62 * @param {string} ndjsonText 63 * The full NDJSON document returned by buildShellNdjsonDocument(). 64 * 65 * @param {object} [opts] 66 * @param {string} [opts.wssBase] Override: full wss:// base URL. 67 * Defaults to getApiBase(getActiveServerContext()). 68 * @param {string} [opts.token] Override: Bearer token. 69 * Defaults to getToken() from auth/login.js. 70 * @param {number} [opts.idleMs] ms to wait after last server message (default 5 000). 71 * @param {function} [opts.onLine] Called with each raw response text from the server. 72 * @param {function} [opts.onStatus] Called with WebSocket status strings 73 * ("connecting" | "open" | "closed" | "error"). 74 * 75 * @returns {Promise<{ ok: boolean, ackLines: string[], error: string|null }>} 76 */ 77export async function sendShellNdjson(ndjsonText, { 78 wssBase, 79 token, 80 idleMs, 81 onLine, 82 onStatus, 83} = {}) { 84 // ââ Resolve endpoint + auth ââââââââââââââââââââââââââââââââââââââââââââââ 85 const base = (wssBase || getApiBase(getActiveServerContext()) || "").trim(); 86 const wsUrl = buildWsUrl(base, WS_DBA); 87 const authToken = (token || getToken() || "").trim() || null; 88
89 // ââ Split NDJSON document into individual lines âââââââââââââââââââââââââ 90 // Each line becomes one WebSocket message. 91 // The shell envelope line (type:"shell") is guaranteed to be first because 92 // buildShellNdjsonDocument() always emits it first. 93 const lines = splitNdjsonLines(ndjsonText); 94 95 if (!lines.length) { 96 return { ok: false, ackLines: [], error: "No NDJSON lines to send." }; 97 } 98 99 // ââ Inject auth as the very first message when a token is present ââââââââ 100 // The auth message precedes the NDJSON payload so the server can validate 101 // the session before accepting any data. 102 const authFrame = buildAuthFrame(authToken); 103 const sendLines = authFrame ? [authFrame, ...lines] : lines; 104 105 // ââ Send over WebSocket ââââââââââââââââââââââââââââââââââââââââââââââââââ 106 const ackLines = []; 107 108 try { 109 await sendNdjsonOverWebSocket( 110 wsUrl, 111 sendLines, 112 (text) => { 113 ackLines.push(text); 114 onLine?.(text); 115 }, 116 undefined, // onData â not needed here 117 idleMs, 118 ); 119 return { ok: true, ackLines, error: null }; 120 121 } catch (err) { 122 return { ok: false, ackLines, error: err?.message || String(err) }; 123 } 124} 125 126/** 127 * Parse a zimserver acknowledgement response and return a structured result. 128 * 129 * The server typically responds with one or more NDJSON lines such as: 130 * {"type":"ack","action":"save","appType":"mindmap","status":"ok","saved":12} 131 * {"type":"error","message":"..."} 132 * 133 * @param {string[]} ackLines Lines returned by sendShellNdjson(). 134 * @returns {{ status: "ok"|"error"|"unknown", saved: number, messages: string[] }} 135 */ 136// Implementation lives in ndjsonUtils.js â re-exported here for backward compatibility. 137export { _parseAckLines as parseAckLines };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.