"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode, } from "react"; import { isChajiaModuleId, listTopLevelKeys, moduleToHubRoute, parseChajiaPostMessage, postToHost, type ChajiaFillAckPayload, type ChajiaModuleId, type ChajiaQuoteResultPayload, type ChajiaQuoteSavePayload, type MsGuestFillPayload, type MsLoggedInFillPayload, } from "@/lib/embed/host-bridge"; export type HostFillState = { module: ChajiaModuleId; requestId?: string; form: unknown; seq: number; }; type HostBridgeContextValue = { activeModule: ChajiaModuleId | null; fill: HostFillState | null; setActiveModule: (m: ChajiaModuleId) => void; clearFill: () => void; reportQuoteResult: (payload: ChajiaQuoteResultPayload, requestId?: string) => void; reportQuoteSave: (payload: ChajiaQuoteSavePayload, requestId?: string) => void; reportError: (code: string, message: string, module?: ChajiaModuleId) => void; /** MS_GUEST 表单预填快照 */ msGuestPrefill: MsGuestFillPayload | null; /** MS_LOGGED_IN 表单预填快照(可晚到 / 可重复) */ msLoggedInPrefill: MsLoggedInFillPayload | null; }; const HostBridgeContext = createContext(null); export function useHostBridge(): HostBridgeContextValue { const ctx = useContext(HostBridgeContext); if (!ctx) { throw new Error("useHostBridge 须在 HostBridgeProvider 内使用"); } return ctx; } /** 非嵌入场景可空 */ export function useHostBridgeOptional(): HostBridgeContextValue | null { return useContext(HostBridgeContext); } export function HostBridgeProvider({ children, initialModule = null, }: { children: ReactNode; /** URL module/entry 直进模块 */ initialModule?: ChajiaModuleId | null; }) { const [activeModule, setActiveModuleState] = useState( initialModule, ); const [fill, setFill] = useState(null); const initialAppliedRef = useRef(false); const setActiveModule = useCallback((m: ChajiaModuleId) => { setActiveModuleState(m); postToHost("chajia:module-changed", { module: m, payload: { module: m } }); }, []); const clearFill = useCallback(() => setFill(null), []); const reportQuoteResult = useCallback( (payload: ChajiaQuoteResultPayload, requestId?: string) => { postToHost("chajia:quote-result", { request_id: requestId ?? payload.request_id, module: payload.module, payload, }); }, [], ); const reportQuoteSave = useCallback( (payload: ChajiaQuoteSavePayload, requestId?: string) => { postToHost("chajia:quote-save", { request_id: requestId ?? payload.request_id, module: payload.module, payload, }); }, [], ); const reportError = useCallback( (code: string, message: string, module?: ChajiaModuleId) => { postToHost("chajia:error", { module, payload: { code, message, module }, }); }, [], ); // URL 直进:挂载后通知宿主一次(不重复刷 module-changed) useEffect(() => { if (!initialModule || initialAppliedRef.current) return; initialAppliedRef.current = true; setActiveModuleState(initialModule); postToHost("chajia:module-changed", { module: initialModule, payload: { module: initialModule, from: "url" }, }); }, [initialModule]); useEffect(() => { postToHost("chajia:ready", { payload: { modules: [ "MS_GUEST", "MS_LOGGED_IN", "FLOCK_GUEST", "FLOCK_LOGGED_IN", ], capabilities: [ "select-module", "fill", "fill-cache", "quote-result", "quote-save", "scroll-quotes", "scroll-top", "url-module", ], active_module: initialModule, }, }); const onMessage = (ev: MessageEvent) => { const msg = parseChajiaPostMessage(ev.data); if (!msg) return; if (msg.type === "chajia:ping") { postToHost("chajia:pong", { request_id: msg.request_id }); return; } // 宿主请求滚到承运商报价列表(私卡询价弹窗) if (msg.type === "chajia:scroll-quotes") { window.dispatchEvent(new CustomEvent("chajia-host-scroll-quotes")); return; } if (msg.type === "chajia:select-module") { const m = msg.module ?? (msg.payload && typeof msg.payload === "object" && isChajiaModuleId((msg.payload as { module?: unknown }).module) ? (msg.payload as { module: ChajiaModuleId }).module : null); if (!m) { postToHost("chajia:error", { request_id: msg.request_id, payload: { code: "INVALID_MODULE", message: "select-module 缺少有效 module", }, }); return; } setActiveModule(m); return; } if (msg.type === "chajia:fill") { const m = msg.module; if (!m) { postToHost("chajia:error", { request_id: msg.request_id, payload: { code: "INVALID_MODULE", message: "fill 必须带 module(MS_GUEST|MS_LOGGED_IN|FLOCK_GUEST|FLOCK_LOGGED_IN)", }, }); return; } const raw = msg.payload; const form = raw && typeof raw === "object" && "form" in (raw as object) && (raw as { form: unknown }).form != null ? (raw as { form: unknown }).form : raw; const selectModule = !( raw && typeof raw === "object" && (raw as { select_module?: boolean }).select_module === false ); if (selectModule) { setActiveModule(m); } const nextSeq = Date.now(); // 始终缓存:表单未挂载也可晚到灌表;可重复 fill(seq 递增) setFill({ module: m, requestId: msg.request_id, form, seq: nextSeq, }); const ack: ChajiaFillAckPayload = { ok: true, module: m, applied_keys: listTopLevelKeys(form), message: "已缓存预填(可重复)。已切入对应模块;表单挂载后写入搜索框/货物(地址须用户点选联想确认)。", }; postToHost("chajia:fill-ack", { request_id: msg.request_id, module: m, payload: ack, }); } }; window.addEventListener("message", onMessage); return () => window.removeEventListener("message", onMessage); }, [setActiveModule, initialModule]); const msGuestPrefill = useMemo((): MsGuestFillPayload | null => { if (!fill || fill.module !== "MS_GUEST") return null; return (fill.form ?? null) as MsGuestFillPayload | null; }, [fill]); const msLoggedInPrefill = useMemo((): MsLoggedInFillPayload | null => { if (!fill || fill.module !== "MS_LOGGED_IN") return null; return (fill.form ?? null) as MsLoggedInFillPayload | null; }, [fill]); const value = useMemo( () => ({ activeModule, fill, setActiveModule, clearFill, reportQuoteResult, reportQuoteSave, reportError, msGuestPrefill, msLoggedInPrefill, }), [ activeModule, fill, setActiveModule, clearFill, reportQuoteResult, reportQuoteSave, reportError, msGuestPrefill, msLoggedInPrefill, ], ); return ( {children} ); } export { moduleToHubRoute };