@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
JavaScript
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
};