"use client" import { SignJWT } from "jose" import { useEffect, useMemo, useState } from "react" import type { KefuWidgetHostConfig } from "@/lib/kefu-widget-config" const STORAGE_KEY = "cs-agent-web-widget-test-config" const DEFAULT_JWT_TTL_MINUTES = "30" const INITIAL_CONFIG: KefuWidgetHostConfig = { channelId: "", baseUrl: "", apiBaseUrl: "", } type AuthMode = "guest" | "jwt" type WidgetDemoConfig = KefuWidgetHostConfig & { authMode?: AuthMode jwtSecret?: string jwtUserId?: string jwtName?: string jwtTtlMinutes?: string } declare global { interface Window { CSAgentWidget?: { mount: (config: KefuWidgetHostConfig) => void destroy: () => void open: () => void close: () => void } } } function getDefaultConfig(): WidgetDemoConfig { if (typeof window === "undefined") { return INITIAL_CONFIG } const savedText = window.localStorage.getItem(STORAGE_KEY) const savedConfig = savedText ? (JSON.parse(savedText) as Partial) : {} const query = new URLSearchParams(window.location.search) return { channelId: query.get("channelId") ?? savedConfig.channelId ?? "", baseUrl: "", apiBaseUrl: "", authMode: (query.get("authMode") as AuthMode | null) ?? savedConfig.authMode ?? "guest", jwtSecret: savedConfig.jwtSecret ?? "", jwtUserId: query.get("userId") ?? savedConfig.jwtUserId ?? "demo-user-001", jwtName: query.get("name") ?? savedConfig.jwtName ?? "测试用户", jwtTtlMinutes: savedConfig.jwtTtlMinutes ?? DEFAULT_JWT_TTL_MINUTES, } } function removeMountedWidget() { if (typeof window === "undefined") { return } window.CSAgentWidget?.destroy() document .querySelectorAll( '[data-cs-agent-widget="launcher"], [data-cs-agent-widget="frame"], [data-cs-agent-widget="script"]' ) .forEach((node) => node.remove()) delete window.CSAgentConfig delete window.__CS_AGENT_WIDGET_CONFIG__ delete window.__CS_AGENT_WIDGET_STATE__ delete window.CSAgentWidget } function injectWidget(config: KefuWidgetHostConfig) { removeMountedWidget() window.CSAgentConfig = config const script = document.createElement("script") script.async = true script.src = `${window.location.origin}/sdk/cs-ai-agent-sdk.min.js` script.dataset.csAgentWidget = "script" document.body.appendChild(script) } function buildWidgetConfig(config: WidgetDemoConfig, userToken: string): WidgetDemoConfig { return { ...config, channelId: config.channelId.trim(), baseUrl: "", apiBaseUrl: "", userToken, } } async function signUserToken(config: WidgetDemoConfig) { const userId = (config.jwtUserId || "").trim() const name = (config.jwtName || "").trim() const secret = (config.jwtSecret || "").trim() const ttl = Number(config.jwtTtlMinutes || DEFAULT_JWT_TTL_MINUTES) if (!userId) { throw new Error("请填写 userId") } if (!name) { throw new Error("请填写用户名称") } if (!secret) { throw new Error("请填写 JWT Secret") } if (!Number.isFinite(ttl) || ttl <= 0) { throw new Error("有效期必须大于 0") } return new SignJWT({ userId, name }) .setProtectedHeader({ alg: "HS256", typ: "JWT" }) .setIssuedAt() .setExpirationTime(`${ttl}m`) .sign(new TextEncoder().encode(secret)) } export function KefuWidgetDemo() { const [config, setConfig] = useState({ ...INITIAL_CONFIG, authMode: "guest", jwtSecret: "", jwtUserId: "demo-user-001", jwtName: "测试用户", jwtTtlMinutes: DEFAULT_JWT_TTL_MINUTES, }) const [status, setStatus] = useState("请填写 channelId") const [origin, setOrigin] = useState("") const [generatedToken, setGeneratedToken] = useState("") async function mountWidget(configToMount: WidgetDemoConfig) { let userToken = "" if (configToMount.authMode === "jwt") { userToken = await signUserToken(configToMount) } const nextConfig = buildWidgetConfig(configToMount, userToken) setConfig(nextConfig) setGeneratedToken(userToken) window.localStorage.setItem(STORAGE_KEY, JSON.stringify(nextConfig)) if (!nextConfig.channelId) { removeMountedWidget() setStatus("请填写 channelId") return } injectWidget(nextConfig) setStatus( nextConfig.authMode === "jwt" ? "Widget 已挂载:JWT 用户模式" : "Widget 已挂载:访客模式" ) } useEffect(() => { const timer = window.setTimeout(() => { const initialConfig = getDefaultConfig() setOrigin(window.location.origin) setConfig(initialConfig) setStatus(initialConfig.channelId ? "Widget 已挂载" : "请填写 channelId") if (initialConfig.channelId) { void mountWidget(initialConfig).catch((error) => { removeMountedWidget() setGeneratedToken("") setStatus(error instanceof Error ? error.message : "生成 userToken 失败") }) } }, 0) return () => { window.clearTimeout(timer) removeMountedWidget() } }, []) const snippet = useMemo(() => { const scriptSrc = origin ? `${origin}/sdk/cs-ai-agent-sdk.min.js` : "/sdk/cs-ai-agent-sdk.min.js" const configLines = [` channelId: "${config.channelId || ""}"`] if (config.authMode === "jwt") { configLines.push(` userToken: "${generatedToken || "业务系统后端签发的 JWT"}"`) } return ` ` }, [config, generatedToken, origin]) function updateField( key: K, value: WidgetDemoConfig[K] ) { setConfig((current) => ({ ...current, [key]: value })) } async function handleMount() { try { await mountWidget(config) } catch (error) { removeMountedWidget() setGeneratedToken("") setStatus(error instanceof Error ? error.message : "生成 userToken 失败") } } return (
Widget 挂载测试
{status}
updateField("channelId", value)} /> updateField("authMode", value)} options={[ { label: "访客", value: "guest" }, { label: "JWT 用户", value: "jwt" }, ]} /> {config.authMode === "jwt" ? (
updateField("jwtUserId", value)} /> updateField("jwtName", value)} /> updateField("jwtSecret", value)} type="password" /> updateField("jwtTtlMinutes", value)} type="number" />
) : null}
接入代码
{config.authMode === "jwt" ? (
当前页面仅用于本地模拟。正式接入时,userToken 应由业务系统后端签发。
) : null}
            {snippet}
          
{generatedToken ? (
当前 userToken