PageSourceSearch

https://smartcone.ai/src/shell/common/shellWss.js

js smartcone.ai collected 2026-10-05 15:06:07 UTC 6,430 bytes, 137 lines download raw bytes

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.