diff --git a/docs b/docs index 077a803..9141c24 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 077a8032c58ca3837e3753e0195ab29b36f9b706 +Subproject commit 9141c24e391f4dcf2ef25762c13326b0f0829458 diff --git a/web/components/kefu/widget-demo.tsx b/web/components/kefu/widget-demo.tsx index ce6465e..564ed4c 100644 --- a/web/components/kefu/widget-demo.tsx +++ b/web/components/kefu/widget-demo.tsx @@ -29,7 +29,9 @@ declare global { CSAgentWidget?: { mount: (config: KefuWidgetHostConfig) => void destroy: () => void + open: () => Promise close: () => void + getChatUrl: () => Promise } } } @@ -86,14 +88,16 @@ function injectWidget(config: KefuWidgetHostConfig) { document.body.appendChild(script) } -function buildWidgetConfig(config: WidgetDemoConfig, userToken: string): WidgetDemoConfig { - return { - ...config, +function buildWidgetConfig(config: WidgetDemoConfig): KefuWidgetHostConfig { + const nextConfig: KefuWidgetHostConfig = { channelId: config.channelId.trim(), baseUrl: "", apiBaseUrl: "", - userToken, } + if (config.authMode === "jwt") { + nextConfig.getUserToken = () => signUserToken(config) + } + return nextConfig } async function signUserToken(config: WidgetDemoConfig) { @@ -134,19 +138,23 @@ export function KefuWidgetDemo() { const [status, setStatus] = useState("请填写 channelId") const [origin, setOrigin] = useState("") const [generatedToken, setGeneratedToken] = useState("") + const [latestDirectChatUrl, setLatestDirectChatUrl] = useState("") const [copied, setCopied] = useState(false) const [snippetCopied, setSnippetCopied] = useState(false) async function mountWidget(configToMount: WidgetDemoConfig) { - let userToken = "" - if (configToMount.authMode === "jwt") { - userToken = await signUserToken(configToMount) + const cleanConfig = { + ...configToMount, + channelId: configToMount.channelId.trim(), + baseUrl: "", + apiBaseUrl: "", + getUserToken: undefined, } - - const nextConfig = buildWidgetConfig(configToMount, userToken) - setConfig(nextConfig) - setGeneratedToken(userToken) - window.localStorage.setItem(STORAGE_KEY, JSON.stringify(nextConfig)) + const nextConfig = buildWidgetConfig(cleanConfig) + setConfig(cleanConfig) + setGeneratedToken("") + setLatestDirectChatUrl("") + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(cleanConfig)) if (!nextConfig.channelId) { removeMountedWidget() @@ -156,7 +164,7 @@ export function KefuWidgetDemo() { injectWidget(nextConfig) setStatus( - nextConfig.authMode === "jwt" + cleanConfig.authMode === "jwt" ? "Widget 已挂载:JWT 用户模式" : "Widget 已挂载:访客模式" ) @@ -173,7 +181,7 @@ export function KefuWidgetDemo() { void mountWidget(initialConfig).catch((error) => { removeMountedWidget() setGeneratedToken("") - setStatus(error instanceof Error ? error.message : "生成 userToken 失败") + setStatus(error instanceof Error ? error.message : "挂载 Widget 失败") }) } }, 0) @@ -191,7 +199,11 @@ export function KefuWidgetDemo() { const configLines = [` channelId: "${config.channelId || ""}"`] if (config.authMode === "jwt") { - configLines.push(` userToken: "${generatedToken || "业务系统后端签发的 JWT"}"`) + configLines.push(` async getUserToken() { + const res = await fetch("/api/kefu/user-token", { credentials: "include" }); + const data = await res.json(); + return data.userToken; + }`) } return ` ` - }, [config, generatedToken, origin]) - - const directChatUrl = useMemo(() => { - const base = origin || "" - const channelId = (config.channelId || "").trim() - if (!base || !channelId) { - return "" - } - - const url = new URL("/kefu/chat/", base) - url.searchParams.set("channelId", channelId) - if (config.authMode === "jwt" && generatedToken) { - url.searchParams.set("userToken", generatedToken) - } - return url.toString() - }, [config.authMode, config.channelId, generatedToken, origin]) + }, [config.authMode, config.channelId, origin]) function updateField( key: K, @@ -230,17 +227,42 @@ ${configLines.join(",\n")} } catch (error) { removeMountedWidget() setGeneratedToken("") - setStatus(error instanceof Error ? error.message : "生成 userToken 失败") + setStatus(error instanceof Error ? error.message : "挂载 Widget 失败") } } async function handleCopyDirectUrl() { - if (!directChatUrl || typeof navigator === "undefined") { + if (!window.CSAgentWidget || typeof navigator === "undefined") { return } - await navigator.clipboard.writeText(directChatUrl) - setCopied(true) - window.setTimeout(() => setCopied(false), 1600) + try { + const url = await window.CSAgentWidget.getChatUrl() + setLatestDirectChatUrl(url) + if (config.authMode === "jwt") { + setGeneratedToken(new URL(url).searchParams.get("userToken") || "") + } + await navigator.clipboard.writeText(url) + setCopied(true) + window.setTimeout(() => setCopied(false), 1600) + } catch (error) { + setStatus(error instanceof Error ? error.message : "生成客服链接失败") + } + } + + async function handleOpenDirectChat() { + if (!window.CSAgentWidget) { + return + } + try { + const url = await window.CSAgentWidget.getChatUrl() + setLatestDirectChatUrl(url) + if (config.authMode === "jwt") { + setGeneratedToken(new URL(url).searchParams.get("userToken") || "") + } + window.open(url, "_blank", "noopener,noreferrer") + } catch (error) { + setStatus(error instanceof Error ? error.message : "生成客服链接失败") + } } async function handleCopySnippet() { @@ -353,13 +375,13 @@ ${configLines.join(",\n")}