@dino-dev/bridge-core-ts 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/iframe-host.d.ts +24 -0
- package/dist/src/iframe-host.d.ts.map +1 -0
- package/dist/src/iframe-host.js +138 -0
- package/dist/src/iframe-host.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mock-bridge.d.ts +100 -0
- package/dist/src/mock-bridge.d.ts.map +1 -0
- package/dist/src/mock-bridge.js +247 -0
- package/dist/src/mock-bridge.js.map +1 -0
- package/dist/src/mrp-bridge.d.ts +24 -3
- package/dist/src/mrp-bridge.d.ts.map +1 -1
- package/dist/src/mrp-bridge.js +73 -13
- package/dist/src/mrp-bridge.js.map +1 -1
- package/package.json +8 -2
- package/src/iframe-host.ts +169 -0
- package/src/index.ts +7 -0
- package/src/mock-bridge.ts +390 -0
- package/src/mrp-bridge.ts +308 -220
package/dist/src/mrp-bridge.js
CHANGED
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
* await audio.play({ soundId: 'success', volume: 0.8 });
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
+
import { installIframeHostBridge } from './iframe-host';
|
|
20
|
+
const READY_POLL_INTERVAL_MS = 100;
|
|
21
|
+
const READY_MAX_RETRY = 50;
|
|
22
|
+
// 容器在 onLoadStop(window.onload,需等图片/iframe 等子资源加载完)后才注入
|
|
23
|
+
// __FLUTTER_BRIDGE__,弱网下可能远超 5 秒;已在 WebView 内时不能放弃,否则排队消息全部丢失。
|
|
24
|
+
const READY_SLOW_POLL_INTERVAL_MS = 250;
|
|
19
25
|
// ============================================================
|
|
20
26
|
// MrpBridge
|
|
21
27
|
// ============================================================
|
|
@@ -33,6 +39,8 @@ export class MrpBridge {
|
|
|
33
39
|
this._pendingMessages = [];
|
|
34
40
|
this._readyPromiseResolve = null;
|
|
35
41
|
this._retryCount = 0;
|
|
42
|
+
this._readyState = 'pending';
|
|
43
|
+
this._readyTimer = null;
|
|
36
44
|
this._readyPromise = new Promise((resolve) => {
|
|
37
45
|
this._readyPromiseResolve = resolve;
|
|
38
46
|
});
|
|
@@ -54,29 +62,79 @@ export class MrpBridge {
|
|
|
54
62
|
}
|
|
55
63
|
// ---- 就绪检测 ----
|
|
56
64
|
_checkReady() {
|
|
65
|
+
if (this._readyState === 'ready')
|
|
66
|
+
return;
|
|
57
67
|
if (window.__FLUTTER_BRIDGE__) {
|
|
68
|
+
if (this._readyTimer !== null) {
|
|
69
|
+
clearTimeout(this._readyTimer);
|
|
70
|
+
this._readyTimer = null;
|
|
71
|
+
}
|
|
58
72
|
this._ready = true;
|
|
73
|
+
this._readyState = 'ready';
|
|
59
74
|
this._readyPromiseResolve?.();
|
|
60
75
|
this._pendingMessages.forEach((msg) => this._doSend(msg));
|
|
61
76
|
this._pendingMessages = [];
|
|
77
|
+
return;
|
|
62
78
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
this._retryCount++;
|
|
80
|
+
if (this._retryCount < READY_MAX_RETRY) {
|
|
81
|
+
this._readyTimer = setTimeout(() => this._checkReady(), READY_POLL_INTERVAL_MS);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (window.flutter_inappwebview) {
|
|
85
|
+
this._readyTimer = setTimeout(() => this._checkReady(), READY_SLOW_POLL_INTERVAL_MS);
|
|
86
|
+
return;
|
|
71
87
|
}
|
|
88
|
+
this._readyTimer = null;
|
|
89
|
+
this._readyState = 'unavailable';
|
|
90
|
+
console.error([
|
|
91
|
+
'[MRP Bridge] 等待 %ss 后仍未检测到原生容器(window.__FLUTTER_BRIDGE__)。',
|
|
92
|
+
'· bridge.whenReady() 将永不 resolve,依赖它的页面会一直停在加载态;',
|
|
93
|
+
'· bridge.send() 与原生模块调用都会静默返回 null。',
|
|
94
|
+
'如果当前是在浏览器里开发,请安装 Mock Bridge:',
|
|
95
|
+
" import { installMockBridge } from '@h5-container/bridge-core-ts/mock';",
|
|
96
|
+
].join('\n'), ((READY_MAX_RETRY * READY_POLL_INTERVAL_MS) / 1000).toFixed(1));
|
|
72
97
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
/**
|
|
99
|
+
* 重新触发一次就绪检测。
|
|
100
|
+
*
|
|
101
|
+
* 供 Mock Bridge 在注入后立即生效使用;轮询已结束时也能重新唤醒。
|
|
102
|
+
*/
|
|
103
|
+
refreshReady() {
|
|
104
|
+
if (this._readyState === 'ready')
|
|
105
|
+
return;
|
|
106
|
+
this._retryCount = 0;
|
|
107
|
+
this._readyState = 'pending';
|
|
108
|
+
this._checkReady();
|
|
109
|
+
}
|
|
110
|
+
/** 当前就绪状态:等待中 / 已就绪 / 判定为不在容器环境 */
|
|
111
|
+
get readyState() {
|
|
112
|
+
return this._readyState;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 等待 Bridge 就绪。
|
|
116
|
+
*
|
|
117
|
+
* 默认沿用旧语义:未就绪时 Promise 一直挂起、不会 reject。
|
|
118
|
+
* 传入 `timeoutMs` 可在超时后 reject,便于页面给出明确的错误态。
|
|
119
|
+
*/
|
|
120
|
+
whenReady(options) {
|
|
121
|
+
const timeoutMs = options?.timeoutMs;
|
|
122
|
+
if (timeoutMs === undefined)
|
|
123
|
+
return this._readyPromise;
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const timer = setTimeout(() => {
|
|
126
|
+
reject(new Error(`[MRP Bridge] 等待原生容器就绪超时(${timeoutMs}ms)。` +
|
|
127
|
+
'当前可能不在容器环境中,浏览器开发请安装 Mock Bridge。'));
|
|
128
|
+
}, timeoutMs);
|
|
129
|
+
void this._readyPromise.then(() => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
76
134
|
}
|
|
77
135
|
/** 是否在 Flutter WebView 容器中运行 */
|
|
78
136
|
get isInContainer() {
|
|
79
|
-
return !!window.__FLUTTER_BRIDGE__ || !!window.flutter_inappwebview;
|
|
137
|
+
return (!!window.__FLUTTER_BRIDGE__ || !!window.flutter_inappwebview);
|
|
80
138
|
}
|
|
81
139
|
// ---- 消息发送 ----
|
|
82
140
|
/**
|
|
@@ -161,7 +219,8 @@ export class MrpBridge {
|
|
|
161
219
|
// 检查模块访问被拒绝错误
|
|
162
220
|
if (result &&
|
|
163
221
|
typeof result === 'object' &&
|
|
164
|
-
result.__error__ ===
|
|
222
|
+
result.__error__ ===
|
|
223
|
+
'MODULE_ACCESS_DENIED') {
|
|
165
224
|
console.error(`[MRP Bridge] 模块访问被拒绝: ${namespace}.${method}`, result.__message__);
|
|
166
225
|
return null;
|
|
167
226
|
}
|
|
@@ -178,6 +237,7 @@ export class MrpBridge {
|
|
|
178
237
|
// 单例 & 导出
|
|
179
238
|
// ============================================================
|
|
180
239
|
/** Bridge 单例实例 */
|
|
240
|
+
installIframeHostBridge();
|
|
181
241
|
export const bridge = new MrpBridge();
|
|
182
242
|
// 挂载到 window,方便非模块化项目使用
|
|
183
243
|
window.mrpBridge = bridge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mrp-bridge.js","sourceRoot":"","sources":["../../src/mrp-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"mrp-bridge.js","sourceRoot":"","sources":["../../src/mrp-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAyExD,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,wDAAwD;AACxD,+DAA+D;AAC/D,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAExC,+DAA+D;AAC/D,YAAY;AACZ,+DAA+D;AAE/D;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;IAUrB;QATQ,qBAAgB,GAAqB,EAAE,CAAC;QACxC,WAAM,GAAG,KAAK,CAAC;QACf,qBAAgB,GAAqB,EAAE,CAAC;QACxC,yBAAoB,GAAwB,IAAI,CAAC;QAEjD,gBAAW,GAAG,CAAC,CAAC;QAChB,gBAAW,GAAqB,SAAS,CAAC;QAC1C,gBAAW,GAAyC,IAAI,CAAC;QAGhE,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClD,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,qBAAqB;QACrB,MAAM,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,CAAC,CAAC,KAAkB,EAAE,EAAE;YACjE,MAAM,GAAG,GAAG,KAAK,CAAC,MAAuB,CAAC;YAC1C,cAAc;YACd,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAChC,IAAI,CAAC;oBACJ,OAAO,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACZ,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;gBAC1C,CAAC;YACF,CAAC;QACF,CAAC,CAAkB,CAAC,CAAC;QAErB,IAAI,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC;IAED,iBAAiB;IAET,WAAW;QAClB,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO;YAAE,OAAO;QAEzC,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;gBAC/B,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC/B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACzB,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;YAC3B,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;YAC3B,OAAO;QACR,CAAC;QAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,WAAW,GAAG,eAAe,EAAE,CAAC;YACxC,IAAI,CAAC,WAAW,GAAG,UAAU,CAC5B,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EACxB,sBAAsB,CACtB,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAK,MAAc,CAAC,oBAAoB,EAAE,CAAC;YAC1C,IAAI,CAAC,WAAW,GAAG,UAAU,CAC5B,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EACxB,2BAA2B,CAC3B,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC;QACjC,OAAO,CAAC,KAAK,CACZ;YACC,4DAA4D;YAC5D,kDAAkD;YAClD,qCAAqC;YACrC,+BAA+B;YAC/B,0EAA0E;SAC1E,CAAC,IAAI,CAAC,IAAI,CAAC,EACZ,CAAC,CAAC,eAAe,GAAG,sBAAsB,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC9D,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,YAAY;QACX,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO;YAAE,OAAO;QACzC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC;IAED,mCAAmC;IACnC,IAAI,UAAU;QACb,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAA0B;QACnC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,CAAC;QACrC,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC;QAEvD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC5C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC7B,MAAM,CACL,IAAI,KAAK,CACR,2BAA2B,SAAS,MAAM;oBACzC,mCAAmC,CACpC,CACD,CAAC;YACH,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,KAAK,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE;gBACjC,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,IAAI,aAAa;QAChB,OAAO,CACN,CAAC,CAAC,MAAM,CAAC,kBAAkB,IAAI,CAAC,CAAE,MAAc,CAAC,oBAAoB,CACrE,CAAC;IACH,CAAC;IAED,iBAAiB;IAEjB;;;;;OAKG;IACH,IAAI,CACH,IAAY,EACZ,MAAc,EACd,UAAmC,EAAE;QAErC,MAAM,GAAG,GAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QACtD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,IAAS,CAAC,CAAC;IACnC,CAAC;IAEO,OAAO,CAAI,GAAmB;QACrC,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC,kBAAkB,CAAC,IAAI,CACpC,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,OAAO,CACG,CAAC;QACjB,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAS,CAAC,CAAC;IACnC,CAAC;IAED,iBAAiB;IAEjB;;;OAGG;IACH,SAAS,CACR,OAA0B;QAE1B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAyB,CAAC,CAAC;QACtD,OAAO,GAAG,EAAE;YACX,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CACnD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CACpB,CAAC;QACH,CAAC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,EAAE,CACD,IAAY,EACZ,MAAqB,EACrB,OAA0B;QAE1B,MAAM,cAAc,GAAmB,CAAC,GAAG,EAAE,EAAE;YAC9C,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;gBACpE,OAA0B,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC;QACF,CAAC,CAAC;QACF,cAAc,CAAC,SAAS,GAAG,OAAyB,CAAC;QACrD,cAAc,CAAC,KAAK,GAAG,IAAI,CAAC;QAC5B,cAAc,CAAC,OAAO,GAAG,MAAM,CAAC;QAChC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3C,OAAO,GAAG,EAAE;YACX,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CACnD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,cAAc,CAC3B,CAAC;QACH,CAAC,CAAC;IACH,CAAC;IAED,mBAAmB;IAEnB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CACL,SAAiB;QAEjB,OAAO,IAAI,KAAK,CAAC,EAAO,EAAE;YACzB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAuB,EAAE,EAAE;gBACzC,IAAI,OAAO,MAAM,KAAK,QAAQ;oBAAE,OAAO,SAAS,CAAC;gBACjD,OAAO,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;oBACnC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;wBACjD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAC3D,GAAG,IAAI,CACP,CAAC;wBACF,cAAc;wBACd,IACC,MAAM;4BACN,OAAO,MAAM,KAAK,QAAQ;4BACzB,MAAkC,CAAC,SAAS;gCAC5C,sBAAsB,EACtB,CAAC;4BACF,OAAO,CAAC,KAAK,CACZ,yBAAyB,SAAS,IAAI,MAAM,EAAE,EAC7C,MAAkC,CAAC,WAAW,CAC/C,CAAC;4BACF,OAAO,IAAI,CAAC;wBACb,CAAC;wBACD,OAAO,MAAM,CAAC;oBACf,CAAC;oBACD,OAAO,CAAC,IAAI,CACX,8BAA8B,SAAS,IAAI,MAAM,MAAM,CACvD,CAAC;oBACF,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC9B,CAAC,CAAC;YACH,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;CACD;AAED,+DAA+D;AAC/D,UAAU;AACV,+DAA+D;AAE/D,kBAAkB;AAClB,uBAAuB,EAAE,CAAC;AAC1B,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;AAEtC,wBAAwB;AACxB,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC;AAE1B,eAAe,MAAM,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dino-dev/bridge-core-ts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Bridge Core SDK for h5_app_container — H5 ↔ Flutter 通信 + 原生模块类型安全调用(零框架依赖)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"types": "./dist/src/index.d.ts",
|
|
11
11
|
"import": "./dist/src/index.js",
|
|
12
12
|
"default": "./dist/src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./mock": {
|
|
15
|
+
"types": "./dist/src/mock-bridge.d.ts",
|
|
16
|
+
"import": "./dist/src/mock-bridge.js",
|
|
17
|
+
"default": "./dist/src/mock-bridge.js"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
15
20
|
"files": [
|
|
@@ -22,6 +27,7 @@
|
|
|
22
27
|
},
|
|
23
28
|
"scripts": {
|
|
24
29
|
"build": "tsc",
|
|
30
|
+
"test": "node --test test/*.test.mjs",
|
|
25
31
|
"prepublishOnly": "npm run build",
|
|
26
32
|
"release": "npm publish --registry https://registry.npmjs.org",
|
|
27
33
|
"dev": "tsc --watch",
|
|
@@ -42,4 +48,4 @@
|
|
|
42
48
|
"url": "https://github.com/nengli/h5_app_container",
|
|
43
49
|
"directory": "packages/bridge_core_ts"
|
|
44
50
|
}
|
|
45
|
-
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iframe 宿主模式:H5 被嵌在网页 iframe 里(如教研后台的实时预览)时,把父窗口当作原生容器。
|
|
3
|
+
*
|
|
4
|
+
* 触发条件:页面在 iframe 内,且 URL 带 `nl_host=iframe&nl_host_origin=<父窗口 origin>`。
|
|
5
|
+
* 父窗口 origin 由宿主显式声明:只向它发消息、只收它发来的消息,其它来源一律忽略。
|
|
6
|
+
*
|
|
7
|
+
* H5 业务代码无需改动:`bridge.whenReady()` / `bindRenderProtocol` / `bindAgentProtocol`
|
|
8
|
+
* 照常工作。原生模块在预览里不可用(`system` 除外),调用返回 `null` 并通知宿主记录。
|
|
9
|
+
*
|
|
10
|
+
* 线格式(postMessage,均带 `__nlBridge: 1`):
|
|
11
|
+
* - H5 → 宿主:`{ kind: 'send', type, action, payload }`
|
|
12
|
+
* - H5 → 宿主:`{ kind: 'module', namespace, method }`(预览里不可用的原生调用)
|
|
13
|
+
* - 宿主 → H5:`{ kind: 'message', type, action, payload }`
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { BridgeMessage } from './mrp-bridge';
|
|
17
|
+
import type { ArAdaptationInfo } from './system-module';
|
|
18
|
+
|
|
19
|
+
export const IFRAME_HOST_PARAM = 'nl_host';
|
|
20
|
+
export const IFRAME_HOST_ORIGIN_PARAM = 'nl_host_origin';
|
|
21
|
+
export const IFRAME_HOST_MARK = '__nlBridge';
|
|
22
|
+
|
|
23
|
+
const DESIGN_LONG_SIDE = 812;
|
|
24
|
+
|
|
25
|
+
interface HostEnvelope {
|
|
26
|
+
[IFRAME_HOST_MARK]: 1;
|
|
27
|
+
kind: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isHostEnvelope(data: unknown): data is HostEnvelope {
|
|
32
|
+
return (
|
|
33
|
+
typeof data === 'object' &&
|
|
34
|
+
data !== null &&
|
|
35
|
+
(data as Record<string, unknown>)[IFRAME_HOST_MARK] === 1
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveHostOrigin(): string | null {
|
|
40
|
+
if (!window.parent || window.parent === window || !window.location) return null;
|
|
41
|
+
const params = new URLSearchParams(window.location.search);
|
|
42
|
+
if (params.get(IFRAME_HOST_PARAM) !== 'iframe') return null;
|
|
43
|
+
const raw = params.get(IFRAME_HOST_ORIGIN_PARAM);
|
|
44
|
+
if (!raw) return null;
|
|
45
|
+
try {
|
|
46
|
+
const origin = new URL(raw).origin;
|
|
47
|
+
return origin === 'null' ? null : origin;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function currentAdaptation(): ArAdaptationInfo {
|
|
54
|
+
const screenWidth = window.innerWidth;
|
|
55
|
+
const screenHeight = window.innerHeight;
|
|
56
|
+
return {
|
|
57
|
+
designLongSide: DESIGN_LONG_SIDE,
|
|
58
|
+
screenWidth,
|
|
59
|
+
screenHeight,
|
|
60
|
+
scale: Math.max(screenWidth, screenHeight) / DESIGN_LONG_SIDE,
|
|
61
|
+
safeArea: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 满足触发条件时安装 iframe 宿主桥;已在原生容器内或条件不满足时什么都不做。
|
|
67
|
+
*
|
|
68
|
+
* @returns 是否已安装
|
|
69
|
+
*/
|
|
70
|
+
export function installIframeHostBridge(): boolean {
|
|
71
|
+
if (typeof window === 'undefined' || window.__FLUTTER_BRIDGE__) return false;
|
|
72
|
+
const hostOrigin = resolveHostOrigin();
|
|
73
|
+
if (!hostOrigin) return false;
|
|
74
|
+
|
|
75
|
+
const post = (kind: string, body: Record<string, unknown>): void => {
|
|
76
|
+
window.parent.postMessage(
|
|
77
|
+
{ [IFRAME_HOST_MARK]: 1, kind, ...body },
|
|
78
|
+
hostOrigin,
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const dispatch = (
|
|
83
|
+
type: string,
|
|
84
|
+
action: string,
|
|
85
|
+
payload: Record<string, unknown>,
|
|
86
|
+
): void => {
|
|
87
|
+
const message: BridgeMessage = {
|
|
88
|
+
type,
|
|
89
|
+
action,
|
|
90
|
+
payload,
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
};
|
|
93
|
+
window.dispatchEvent(new CustomEvent('flutterMessage', { detail: message }));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
97
|
+
if (event.source !== window.parent || event.origin !== hostOrigin) return;
|
|
98
|
+
const data = event.data;
|
|
99
|
+
if (!isHostEnvelope(data) || data.kind !== 'message') return;
|
|
100
|
+
if (typeof data.type !== 'string' || typeof data.action !== 'string') return;
|
|
101
|
+
const payload =
|
|
102
|
+
typeof data.payload === 'object' && data.payload !== null
|
|
103
|
+
? (data.payload as Record<string, unknown>)
|
|
104
|
+
: {};
|
|
105
|
+
dispatch(data.type, data.action, payload);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
window.addEventListener('resize', () => {
|
|
109
|
+
dispatch(
|
|
110
|
+
'adaptation',
|
|
111
|
+
'changed',
|
|
112
|
+
currentAdaptation() as unknown as Record<string, unknown>,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const systemModule: Record<string, () => unknown> = {
|
|
117
|
+
isModuleAllowed: () => ({ allowed: false, namespace: '' }),
|
|
118
|
+
getAvailableModules: () => ({ modules: [] }),
|
|
119
|
+
getContainerInfo: () => ({
|
|
120
|
+
packageId: null,
|
|
121
|
+
registeredModules: [],
|
|
122
|
+
allowedModules: [],
|
|
123
|
+
adaptation: currentAdaptation(),
|
|
124
|
+
isIframeHost: true,
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const moduleProxy = (namespace: string) =>
|
|
129
|
+
new Proxy({} as Record<string, unknown>, {
|
|
130
|
+
// bridge.module() 用 `NativeModules[ns][method]` 做存在性判断,必须恒为真
|
|
131
|
+
has: () => true,
|
|
132
|
+
get: (_target, method: string | symbol) => {
|
|
133
|
+
if (typeof method !== 'string') return undefined;
|
|
134
|
+
return async () => {
|
|
135
|
+
if (namespace === 'system' && method in systemModule) {
|
|
136
|
+
return systemModule[method]();
|
|
137
|
+
}
|
|
138
|
+
post('module', { namespace, method });
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
window.NativeModules = new Proxy(
|
|
145
|
+
{} as NonNullable<Window['NativeModules']>,
|
|
146
|
+
{
|
|
147
|
+
has: () => true,
|
|
148
|
+
get: (_target, namespace: string | symbol) =>
|
|
149
|
+
typeof namespace === 'string' ? moduleProxy(namespace) : undefined,
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
window.__FLUTTER_BRIDGE__ = {
|
|
154
|
+
send: async (type, action, payload) => {
|
|
155
|
+
post('send', { type, action, payload });
|
|
156
|
+
return null;
|
|
157
|
+
},
|
|
158
|
+
onMessage: (jsonString: string) => {
|
|
159
|
+
try {
|
|
160
|
+
const message = JSON.parse(jsonString) as BridgeMessage;
|
|
161
|
+
dispatch(message.type, message.action, message.payload);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error('[MRP Bridge] iframe 宿主消息解析失败:', e);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return true;
|
|
169
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
export { bridge, MrpBridge } from './mrp-bridge';
|
|
13
13
|
export type { BridgeMessage, MessageHandler, Unsubscribe } from './mrp-bridge';
|
|
14
14
|
|
|
15
|
+
// iframe 宿主模式(网页内嵌预览)的契约常量
|
|
16
|
+
export {
|
|
17
|
+
IFRAME_HOST_MARK,
|
|
18
|
+
IFRAME_HOST_ORIGIN_PARAM,
|
|
19
|
+
IFRAME_HOST_PARAM,
|
|
20
|
+
} from './iframe-host';
|
|
21
|
+
|
|
15
22
|
// 内置系统模块类型 + 工厂函数(容器核心契约)
|
|
16
23
|
export { getSystemModule } from './system-module';
|
|
17
24
|
export type {
|