This commit is contained in:
@@ -1,19 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useSyncExternalStore,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { syncPreferredLanguage } from "@/i18n";
|
||||
|
||||
const I18nHydrationContext = createContext(false);
|
||||
|
||||
function subscribeI18nHydration(onStoreChange: () => void): () => void {
|
||||
queueMicrotask(() => {
|
||||
syncPreferredLanguage();
|
||||
onStoreChange();
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
|
||||
function getI18nHydratedSnapshot(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getI18nHydratedServerSnapshot(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** hydration 完成前保持 DEFAULT_LANGUAGE,与 SSR 一致;完成后再同步用户偏好语言。 */
|
||||
export function I18nHydrationProvider({ children }: { children: ReactNode }) {
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
syncPreferredLanguage();
|
||||
setHydrated(true);
|
||||
}, []);
|
||||
const hydrated = useSyncExternalStore(
|
||||
subscribeI18nHydration,
|
||||
getI18nHydratedSnapshot,
|
||||
getI18nHydratedServerSnapshot,
|
||||
);
|
||||
|
||||
return (
|
||||
<I18nHydrationContext.Provider value={hydrated}>{children}</I18nHydrationContext.Provider>
|
||||
|
||||
@@ -10,6 +10,33 @@ import {
|
||||
resolvePostMessageTargetOrigin,
|
||||
} from "@/lib/iframe-origins";
|
||||
|
||||
function sanitizeUrlForParent(href: string): string {
|
||||
try {
|
||||
const url = new URL(href);
|
||||
url.searchParams.delete("token");
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
} catch {
|
||||
return href;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSafeInAppPath(path: string): string | null {
|
||||
if (!path.startsWith("/") || path.startsWith("//")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(path, window.location.origin);
|
||||
if (url.origin !== window.location.origin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iframe 通信桥接组件
|
||||
*
|
||||
@@ -49,7 +76,7 @@ export function IframeBridge({ children }: { children: ReactNode }): ReactNode {
|
||||
*/
|
||||
const notifyReady = useCallback((): void => {
|
||||
sendToParent("READY", {
|
||||
url: window.location.href,
|
||||
url: sanitizeUrlForParent(window.location.href),
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
}, [sendToParent]);
|
||||
@@ -147,7 +174,10 @@ export function IframeBridge({ children }: { children: ReactNode }): ReactNode {
|
||||
// 主站导航请求
|
||||
case "MAIN_NAVIGATE":
|
||||
if (data.path && typeof data.path === "string") {
|
||||
window.history.pushState({}, "", data.path);
|
||||
const nextPath = resolveSafeInAppPath(data.path);
|
||||
if (nextPath) {
|
||||
window.history.pushState({}, "", nextPath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user