UNPKG

@kitn.ai/chat

Version:

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

241 lines (240 loc) 8.08 kB
const F = "kitn-card"; function J(t, e) { return function(r) { return { protocol: F, version: t, nonce: e, message: r }; }; } function q(t, e) { if (typeof t != "object" || t === null) return !1; const i = t, r = i.message; return i.protocol === F && typeof i.version == "string" && typeof i.nonce == "string" && typeof r == "object" && r !== null && r.dir === e; } function H(t, e) { return typeof t == "string" && t.length > 0 && t === e; } const K = /* @__PURE__ */ new Set(["protocol", "version", "dir", "kind", "mode", "locale", "edge", "code", "acceptedVersion", "supportedVersions", "cardId", "type", "id", "height", "target"]), P = /* @__PURE__ */ new Set(["authToken", "nonce"]); function I(t) { if (Array.isArray(t)) return t.map(I); if (t && typeof t == "object") { const e = {}; for (const [i, r] of Object.entries(t)) P.has(i) ? e[i] = "[redacted]" : typeof r == "object" && r !== null ? e[i] = I(r) : e[i] = K.has(i) ? r : "[redacted]"; return e; } return t; } function D(t) { return /^[0-9]+$/.test(t); } function z(t, e) { const i = new Set(e.filter(D)), r = t.filter((o) => D(o) && i.has(o)); return r.length === 0 ? null : r.sort((o, u) => Number(u) - Number(o))[0]; } const B = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]); function L(t) { if (Array.isArray(t)) return t.some(L); if (t && typeof t == "object") { for (const e of Object.keys(t)) if (B.has(e) || L(t[e])) return !0; } return !1; } const W = 1; function Y(t, e) { let i = -1; const r = new ResizeObserver((o) => { var c; const u = ((c = o[o.length - 1]) == null ? void 0 : c.contentRect.height) ?? t.getBoundingClientRect().height; (i < 0 || Math.abs(u - i) > W) && (i = u, e(u)); }); return r.observe(t), () => r.disconnect(); } function N(t) { return t === null ? "null" : Array.isArray(t) ? "array" : typeof t; } function G(t, e) { switch (e) { case "integer": return typeof t == "number" && Number.isInteger(t); case "number": return typeof t == "number" && Number.isFinite(t); case "array": return Array.isArray(t); case "null": return t === null; case "object": return N(t) === "object"; default: return typeof t === e; } } function j(t, e, i, r) { const o = i || "(root)"; if (t.type && !G(e, t.type)) { r.push(`${o}: expected ${t.type}, got ${N(e)}`); return; } if ("const" in t && JSON.stringify(e) !== JSON.stringify(t.const) && r.push(`${o}: must equal const ${JSON.stringify(t.const)}`), t.enum && !t.enum.some((u) => JSON.stringify(u) === JSON.stringify(e)) && r.push(`${o}: must be one of ${JSON.stringify(t.enum)}`), typeof e == "number" && (t.minimum !== void 0 && e < t.minimum && r.push(`${o}: < minimum ${t.minimum}`), t.maximum !== void 0 && e > t.maximum && r.push(`${o}: > maximum ${t.maximum}`), t.exclusiveMinimum !== void 0 && e <= t.exclusiveMinimum && r.push(`${o}: <= exclusiveMinimum`), t.exclusiveMaximum !== void 0 && e >= t.exclusiveMaximum && r.push(`${o}: >= exclusiveMaximum`)), typeof e == "string" && (t.minLength !== void 0 && e.length < t.minLength && r.push(`${o}: shorter than minLength ${t.minLength}`), t.maxLength !== void 0 && e.length > t.maxLength && r.push(`${o}: longer than maxLength ${t.maxLength}`), t.pattern !== void 0 && !new RegExp(t.pattern).test(e) && r.push(`${o}: does not match pattern`)), Array.isArray(e) && (t.minItems !== void 0 && e.length < t.minItems && r.push(`${o}: fewer than minItems ${t.minItems}`), t.maxItems !== void 0 && e.length > t.maxItems && r.push(`${o}: more than maxItems ${t.maxItems}`), t.uniqueItems && new Set(e.map((c) => JSON.stringify(c))).size !== e.length && r.push(`${o}: items not unique`), t.items && e.forEach((u, c) => j(t.items, u, `${o}[${c}]`, r))), N(e) === "object") { const u = e; for (const c of t.required ?? []) (!(c in u) || u[c] === void 0) && r.push(`${o}.${c}: required`); if (t.properties) for (const [c, a] of Object.entries(t.properties)) c in u && u[c] !== void 0 && j(a, u[c], `${o}.${c}`, r); } } function Q(t, e) { const i = []; return j(t, e, "", i), { valid: i.length === 0, errors: i }; } const X = ["1"]; function Z() { return { theme: { mode: "light" }, locale: "en" }; } function v(t) { const { root: e, renderers: i } = t, r = t.supportedVersions ?? X, o = new Map(i.map((n) => [n.type, n])); let u = null, c = null, a = null, y = null, $ = null, x = !1, b = null, p = null, h = null, m = null, f = null, k = null; const C = (n) => JSON.stringify((n == null ? void 0 : n.theme) ?? null); function l(n) { try { console.warn("[kc-remote]", I(n)); } catch { } } function w(n) { if (!(!$ || !c)) try { parent.postMessage($(n), c); } catch { } } const O = { context: () => b ?? Z(), // HOST CONTRACT: the host MUST validate inbound event.cardId against the active card, // because the provider cannot fence a replaced/stale renderer's host.emit calls. emit: (n) => w({ dir: "up", kind: "event", event: n }) }; function S() { if (m) try { m(); } catch { } m = null, p = null, h = null, k = null; } function E() { f == null || f(), f = null, f = Y(e, (n) => { p && O.emit({ kind: "resize", cardId: p, height: n }); }); } function A(n, s) { S(), e.replaceChildren(); const d = document.createElement("div"); d.setAttribute("role", "alert"), d.textContent = `Unsupported card: ${n.type}`, e.appendChild(d), p = n.id, E(), O.emit({ kind: "error", cardId: n.id, message: s }); } function M(n) { if (L(n.data)) { A(n, "rejected: payload contains a prohibited key"); return; } const s = o.get(n.type); if (!s) { A(n, `no renderer registered for type "${n.type}"`); return; } if (s.schema) { const d = Q(s.schema, n.data); if (!d.valid) { A(n, `invalid card data: ${d.errors.join("; ")}`); return; } } S(), e.replaceChildren(); try { m = s.mount(e, n, O) ?? (() => { }), p = n.id, h = n, k = C(b); } catch { m = null, p = n.id, w({ dir: "up", kind: "fault", code: "render-failed", message: "card failed to render" }); return; } E(); } function R(n) { if (x) return; const s = n.data; if (u === null) { if (!q(s, "down") || s.message.kind !== "hello" || n.source !== window.parent) return; const d = s.message.supportedVersions, g = z(d, r), T = s.nonce, _ = n.origin; if (g === null) { try { parent.postMessage( J(r[0], T)({ dir: "up", kind: "fault", code: "version-unsupported", message: "no compatible protocol version" }), _ ); } catch { } x = !0; return; } u = n.source, c = _, a = T, y = g, $ = J(y, a), w({ dir: "up", kind: "ready", acceptedVersion: y, capabilities: { types: i.map((U) => U.type) } }); return; } if (n.source !== u) { l(s); return; } if (!H(n.origin, c)) { l(s); return; } if (!q(s, "down")) { l(s); return; } if (s.nonce !== a) { l(s); return; } if (s.version !== y) { l(s); return; } try { const d = s.message; switch (d.kind) { case "hello": return; case "context": { const g = d.context; b = g, h && C(g) !== k && M(h); return; } case "render": M(d.envelope); return; case "teardown": V(); return; default: l(s); return; } } catch { l(s); } } function V() { S(), f && (f(), f = null), e.replaceChildren(); } return { start() { window.addEventListener("message", R); }, stop() { x = !0, window.removeEventListener("message", R), V(); } }; } export { v as createCardBridge };