|
|
"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<HostBridgeContextValue | null>(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<ChajiaModuleId | null>(
|
|
|
initialModule,
|
|
|
);
|
|
|
const [fill, setFill] = useState<HostFillState | null>(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 (
|
|
|
<HostBridgeContext.Provider value={value}>
|
|
|
{children}
|
|
|
</HostBridgeContext.Provider>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export { moduleToHubRoute };
|