feat(framework): 原生桥 WVJB + window.settings(NativeBridge facade)
严格对齐原 gameabc 项目 05_Func.js:2627+ 与 2467-2471 的接口契约: - 14 个 handler 名白名单(getVideoinfo/sharelogin/sharesuccess/gameui_play_voice/ gameui_stop_voice/getphoneinfo/getAddressBook/phonestate/appservice/getaudiourl/ getBattery/getwifiLevel/getnetwork/shakeEnd) - 字面量联合类型 WVJBHandlerName:registerHandler/callHandler 入参编译期校验 - 白名单外名字显式抛错(第二准则:不静默兜底) - bridge 未就绪时 register/call 显式抛错(引导业务先初始化) - window.WVJBCallbacks 接入 setupWebViewJavascriptBridge 入口 - getSetting(name) 同步取 window.settings.getothername(name) Node 单测用 mock bridge + triggerReady() 模拟 WebView bridge 就绪。 Cocos 侧走相同 facade,原生侧 WVJB handler 零改动对接。 143/143 tests pass, typecheck exit 0。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 原生↔H5 异步桥(WebViewJavascriptBridge,WVJB)+ window.settings 同步取值。
|
||||
*
|
||||
* 严格对齐原 gameabc 项目(05_Func.js:2627 起)的接口名、数据格式、回调约定,
|
||||
* 让原生侧零改动即可对接。
|
||||
*
|
||||
* 架构边界:本模块只暴露受限 facade,不允许 framework 业务代码直接读 window/bridge 内部。
|
||||
*/
|
||||
|
||||
/** window 上的原生注入对象(05_Func.js:2467-2471 同步取值)。) */
|
||||
declare global {
|
||||
interface Window {
|
||||
settings?: {
|
||||
/** 同步取一个原生配置项,无则返回空串 */
|
||||
getothername(name: string): string;
|
||||
};
|
||||
/** WVJB 启动前的全局回调队列 */
|
||||
WVJBCallbacks?: Array<(bridge: WVJB) => void>;
|
||||
/** WebView 注入 WVJB 的事件名 */
|
||||
WebViewJavascriptBridgeReady?: Array<() => void>;
|
||||
}
|
||||
}
|
||||
|
||||
/** WVJB bridge 实例的最小契约(只用到 framework 需要的子集) */
|
||||
export interface WVJB {
|
||||
/** H5 注册供原生侧调用的 handler */
|
||||
registerHandler(name: string, handler: WVJBHandler): void;
|
||||
/** H5 主动调用原生侧 */
|
||||
callHandler(name: string, data: unknown, responseCallback?: (resp: unknown) => void): void;
|
||||
}
|
||||
|
||||
/** handler 函数:H5 注册供原生调用时,函数返回 Promise 化值 */
|
||||
export type WVJBHandler = (data: unknown, responseCallback: WVJBCallback) => unknown;
|
||||
|
||||
/** 响应回调:原生侧通过此把结果回传给 H5 */
|
||||
export type WVJBCallback = (responseData: unknown) => void;
|
||||
|
||||
/**
|
||||
* 13+ 已注册 handler 名白名单(05_Func.js:2651+ 原项目实装)。
|
||||
* 字面量联合类型:registerHandler/callHandler 入参必须在此集合内,否则显式抛错(第二准则:不静默吞包)。
|
||||
*/
|
||||
export const WVJB_HANDLER_NAMES = [
|
||||
'getVideoinfo',
|
||||
'sharelogin',
|
||||
'sharesuccess',
|
||||
'gameui_play_voice',
|
||||
'gameui_stop_voice',
|
||||
'getphoneinfo',
|
||||
'getAddressBook',
|
||||
'phonestate',
|
||||
'appservice',
|
||||
'getaudiourl',
|
||||
'getBattery',
|
||||
'getwifiLevel',
|
||||
'getnetwork',
|
||||
'shakeEnd',
|
||||
] as const;
|
||||
|
||||
export type WVJBHandlerName = (typeof WVJB_HANDLER_NAMES)[number];
|
||||
|
||||
function isKnownHandler(name: string): name is WVJBHandlerName {
|
||||
return (WVJB_HANDLER_NAMES as readonly string[]).includes(name);
|
||||
}
|
||||
|
||||
/** createNativeBridge 初始化选项 */
|
||||
export interface NativeBridgeOptions {
|
||||
/** bridge 就绪回调 */
|
||||
onReady(bridge: WVJB): void;
|
||||
/**
|
||||
* bridge 初始化失败的兜底(可省略)。
|
||||
* 原生侧可能永不触发 WebViewJavascriptBridgeReady,业务可能想超时降级。
|
||||
*/
|
||||
onTimeout?(reason: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 受限 NativeBridge facade。
|
||||
*
|
||||
* 创建后立即尝试初始化:
|
||||
* 1. 若 window.WVJBCallbacks 已存在 → push 入队(Cocos/原生侧约定的初始化入口)
|
||||
* 2. 否则保留空方法,业务侧可调用 isReady() 查询状态
|
||||
*
|
||||
* 一旦 bridge 就绪,onReady 被调用一次。
|
||||
*/
|
||||
export interface NativeBridge {
|
||||
/** 当前是否已就绪 */
|
||||
isReady(): boolean;
|
||||
/** H5 注册供原生侧调用(白名单校验) */
|
||||
registerHandler(name: WVJBHandlerName, handler: WVJBHandler): void;
|
||||
/** H5 调用原生侧(白名单校验) */
|
||||
callHandler(name: WVJBHandlerName, data: unknown, cb?: WVJBCallback): void;
|
||||
}
|
||||
|
||||
export function createNativeBridge(opts: NativeBridgeOptions): NativeBridge {
|
||||
const g = globalThis as any;
|
||||
// 原生侧约定的初始化入口:把 setup cb push 到 window.WVJBCallbacks
|
||||
if (!g.WVJBCallbacks) g.WVJBCallbacks = [];
|
||||
let ready = false;
|
||||
g.WVJBCallbacks.push((bridge: WVJB) => {
|
||||
ready = true;
|
||||
// 存到 globalThis 供 callHandler 委托
|
||||
g.__nbBridge = bridge;
|
||||
opts.onReady(bridge);
|
||||
});
|
||||
|
||||
return {
|
||||
isReady() { return ready; },
|
||||
registerHandler(name, handler) {
|
||||
if (!isKnownHandler(name)) {
|
||||
throw new Error(`registerHandler: handler "${name}" 不在白名单(第二准则:不静默兜底)`);
|
||||
}
|
||||
if (!ready) throw new Error('NativeBridge not ready: bridge 未初始化');
|
||||
g.__nbBridge.registerHandler(name, handler);
|
||||
},
|
||||
callHandler(name, data, cb) {
|
||||
if (!isKnownHandler(name)) {
|
||||
throw new Error(`callHandler: handler "${name}" 不在白名单(第二准则:不静默兜底)`);
|
||||
}
|
||||
if (!ready) throw new Error('NativeBridge not ready: bridge 未初始化');
|
||||
g.__nbBridge.callHandler(name, data, cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 window 取原生注入的配置项(05_Func.js:2467-2471 的 getothername)。
|
||||
*
|
||||
* @param name 配置项名(渠道/包信息/gameserver 覆盖等)
|
||||
* @returns 原生侧返回值;缺失时返回空串(对齐原项目:无则空字符串而非 undefined)
|
||||
*/
|
||||
export function getSetting(name: string): string {
|
||||
const settings = (globalThis as any).window?.settings;
|
||||
if (!settings || typeof settings.getothername !== 'function') {
|
||||
throw new Error(
|
||||
`getSetting("${name}"): window.settings 未注入(原生侧未加载或非 WebView 环境)`,
|
||||
);
|
||||
}
|
||||
return settings.getothername(name);
|
||||
}
|
||||
Reference in New Issue
Block a user