import { getUrlParams } from "@scripts/utils/Helper"; import { WebView } from "cc"; import { ANDROID, IOS } from "cc/env"; const HISTORY_STATE_KEY = '__game_webview_locked__'; export interface IWebViewMsg { cmd: string; url?: string; [key: string]: string | undefined; } export interface IWebEventHandler { onWebLoaded?: () => void, onWebLoading?: () => void, onWebError?: () => void, onHandleMsg?: (data: IWebViewMsg) => void; } export class WebViewHandler { private disposed = false; private origin: string; constructor(private readonly webView: WebView, private readonly url: string, private readonly eventer: IWebEventHandler, scheme: string) { if (ANDROID || IOS) { const onSchemeCallback = (target, url: string) => { if (!url.startsWith(scheme)) return; const params = getUrlParams(url) as IWebViewMsg; this.handleWebEvent(params); } this.webView.setJavascriptInterfaceScheme(scheme); //@ts-ignore this.webView.setOnJSCallback(onSchemeCallback); url = `${url}&scheme=${scheme}`; } this.webView.url = url; this.origin = (new URL(url)).origin; webView.node.on(WebView.EventType.LOADED, this.onWebViewLoaded, this); webView.node.on(WebView.EventType.LOADING, this.onWebViewLoading, this); webView.node.on(WebView.EventType.ERROR, this.onWebViewError, this); window?.addEventListener('message', this.onWindowMessage); window?.addEventListener('popstate', this.onBrowserBack); window?.history?.pushState( { ...window.history.state, [HISTORY_STATE_KEY]: true }, '', window.location.href, ); } public clear() { if (this.disposed) return; this.disposed = true; this.webView?.node.off(WebView.EventType.LOADED, this.onWebViewLoaded, this); this.webView?.node.off(WebView.EventType.LOADING, this.onWebViewLoading, this); this.webView?.node.off(WebView.EventType.ERROR, this.onWebViewError, this); window?.removeEventListener('message', this.onWindowMessage); window?.removeEventListener('popstate', this.onBrowserBack); } public sendMessage(message: IWebViewMsg) { if (this.disposed || !this.webView?.isValid || !this.webView.node?.isValid) return; if (ANDROID || IOS) { const payload = JSON.stringify(message); this.webView?.evaluateJS(` (() => { const message = ${payload}; window.postMessage(message, '*'); })(); `); } else { const iframe = this.webView?.nativeWebView; iframe?.contentWindow?.postMessage(message, '*'); } } private onWebViewLoaded() { this.eventer.onWebLoaded && this.eventer.onWebLoaded(); } private onWebViewLoading() { this.eventer.onWebLoading && this.eventer.onWebLoading(); } private onWebViewError() { this.eventer.onWebError && this.eventer.onWebError(); } private readonly onBrowserBack = (): void => { if (!this.webView.isValid || typeof window === 'undefined') return; // 浏览器的返回无法被取消,因此立即补回当前游戏页历史记录。 window.history?.pushState( { ...window.history.state, [HISTORY_STATE_KEY]: true }, '', window.location.href, ); }; private readonly onWindowMessage = (event: MessageEvent): void => { if (this.disposed) return; if (event.origin !== this.origin) return; this.handleWebEvent(event?.data); }; private handleWebEvent(data: IWebViewMsg) { if (!!data.cmd) { this.eventer.onHandleMsg && this.eventer.onHandleMsg(data); } } }