Files
tape-springboot-sysadmin/src/iview/urlEmbed/urlEmbedClient.js
iqudoo eb4e8f1a04 init
2026-06-05 17:22:32 +08:00

219 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { URL_EMBED_MSG } from "./urlEmbedMessages.js";
import { isWeChatBrowser } from "./urlEmbedEnv.js";
import {
cancelUrlEmbedClientFocusBurst,
restoreUrlEmbedClientFocus,
scheduleUrlEmbedClientRestoreForEnv,
} from "./urlEmbedWechatFocus.js";
export {
restoreUrlEmbedClientFocus,
scheduleUrlEmbedClientRestoreForEnv,
} from "./urlEmbedWechatFocus.js";
function resolveUrl(url) {
try {
return new URL(url, typeof window !== "undefined" ? window.location.href : undefined);
} catch {
return null;
}
}
/** 是否运行在 urlEmbed 抽屉 iframe 内 */
export function isUrlEmbedFrame() {
if (typeof window === "undefined") return false;
try {
return window.parent !== window;
} catch {
return true;
}
}
function isSafeHttpUrl(url) {
const parsed = resolveUrl(url);
if (!parsed) return false;
return parsed.protocol === "http:" || parsed.protocol === "https:";
}
/**
* 请求父页面打开 URLiframe 内 window.open / target=_blank 常被拦截时使用)
* @param {string} url
* @param {{
* title?: string,
* showToolbar?: boolean,
* openInNewWindow?: boolean,
* showNewWindowButton?: boolean,
* }} [options]
* @returns {boolean} 是否已发出请求或已在顶层直接打开
*/
export function requestOpenUrl(url, options = {}) {
const nextUrl = typeof url === "string" ? url.trim() : "";
if (!nextUrl || !isSafeHttpUrl(nextUrl)) return false;
if (!isUrlEmbedFrame()) {
if (options.openInNewWindow === true) {
window.open(nextUrl, "_blank", "noopener,noreferrer");
return true;
}
window.location.href = nextUrl;
return true;
}
window.parent.postMessage(
{
type: URL_EMBED_MSG.OPEN_URL,
url: nextUrl,
title: options.title,
showToolbar: options.showToolbar,
openInNewWindow: options.openInNewWindow,
showNewWindowButton: options.showNewWindowButton,
},
"*",
);
return true;
}
function handleNavMessage(data, onNav) {
if (data.type !== URL_EMBED_MSG.NAV || typeof onNav !== "function") return;
if (data.action === "back") {
onNav("back");
return;
}
if (data.action === "forward") {
onNav("forward");
}
}
function createLinkClickHandler(getOpenOptions) {
return function onDocumentClick(ev) {
const anchor = ev.target?.closest?.("a[href]");
if (!anchor || !isUrlEmbedFrame()) return;
const href = anchor.getAttribute("href");
if (!href || href.startsWith("#") || href.startsWith("javascript:")) return;
const target = (anchor.getAttribute("target") || "").toLowerCase();
const opensExternally =
target === "_blank" || target === "_top" || target === "_parent";
if (!opensExternally) return;
const url = anchor.href;
if (!isSafeHttpUrl(url)) return;
ev.preventDefault();
ev.stopPropagation();
const extra = typeof getOpenOptions === "function" ? getOpenOptions(anchor) : {};
requestOpenUrl(url, {
openInNewWindow: target === "_blank" ? true : undefined,
...extra,
});
};
}
/**
* 嵌入页初始化:响应父页导航查询,并可选拦截链接 / window.open
* @param {{
* onNav?: (action: 'back' | 'forward') => void,
* interceptLinks?: boolean,
* interceptWindowOpen?: boolean,
* getOpenOptions?: (anchor: HTMLAnchorElement) => Record<string, unknown>,
* }} [options]
* @returns {() => void} 卸载监听
*/
export function initUrlEmbedClient(options = {}) {
if (typeof window === "undefined") return () => {};
const onNav = options.onNav;
const getOpenOptions = options.getOpenOptions;
function onWindowMessage(ev) {
const data = ev.data;
if (!data || typeof data !== "object") return;
if (data.type === URL_EMBED_MSG.QUERY_NAV) {
window.parent.postMessage({ type: URL_EMBED_MSG.NAV_AVAILABLE }, "*");
return;
}
if (data.type === URL_EMBED_MSG.RESTORE_FOCUS) {
scheduleUrlEmbedClientRestoreForEnv();
return;
}
handleNavMessage(data, onNav);
}
window.addEventListener("message", onWindowMessage);
if (isUrlEmbedFrame()) {
window.parent.postMessage({ type: URL_EMBED_MSG.NAV_AVAILABLE }, "*");
}
let removeTouchStartListener = () => {};
if (isWeChatBrowser() && document.body) {
const onTouchStart = () => {};
document.body.addEventListener("touchstart", onTouchStart, { passive: true, capture: true });
removeTouchStartListener = () => {
document.body.removeEventListener("touchstart", onTouchStart, { capture: true });
};
}
let removeWechatLifecycleListeners = () => {};
if (isWeChatBrowser() && isUrlEmbedFrame()) {
function onClientPageShow() {
scheduleUrlEmbedClientRestoreForEnv();
}
function onClientPopState() {
scheduleUrlEmbedClientRestoreForEnv();
}
function onClientVisibilityChange() {
if (document.visibilityState !== "visible") return;
scheduleUrlEmbedClientRestoreForEnv();
}
window.addEventListener("pageshow", onClientPageShow);
window.addEventListener("popstate", onClientPopState);
document.addEventListener("visibilitychange", onClientVisibilityChange);
removeWechatLifecycleListeners = () => {
window.removeEventListener("pageshow", onClientPageShow);
window.removeEventListener("popstate", onClientPopState);
document.removeEventListener("visibilitychange", onClientVisibilityChange);
cancelUrlEmbedClientFocusBurst();
};
}
let removeLinkListener = () => {};
if (options.interceptLinks === true) {
const onClick = createLinkClickHandler(getOpenOptions);
document.addEventListener("click", onClick, true);
removeLinkListener = () => document.removeEventListener("click", onClick, true);
}
let restoreWindowOpen = null;
if (options.interceptWindowOpen === true && isUrlEmbedFrame()) {
const nativeOpen = window.open.bind(window);
restoreWindowOpen = nativeOpen;
window.open = (url, target, features) => {
const nextUrl = typeof url === "string" ? url.trim() : "";
if (nextUrl && isSafeHttpUrl(nextUrl)) {
requestOpenUrl(nextUrl, { openInNewWindow: true });
return null;
}
return nativeOpen(url, target, features);
};
}
return function disposeUrlEmbedClient() {
window.removeEventListener("message", onWindowMessage);
removeTouchStartListener();
removeWechatLifecycleListeners();
removeLinkListener();
if (restoreWindowOpen) {
window.open = restoreWindowOpen;
}
};
}