@jhh96/light-app-bridge 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -8,12 +8,22 @@
8
8
 
9
9
  ## 依赖(构造时校验,缺失直接抛异常)
10
10
 
11
+ **必需:**
12
+
11
13
  | 依赖 | 用途 | 来源 |
12
14
  |------|------|------|
13
- | `window.waltz.event.on` | 接收消息 | preload 注入的 waltz SDK |
14
15
  | `window.worklink`(含 `invokeModuleMethodSync`) | 发送消息 / 取 webContentsId | preload 注入 |
15
16
  | `window.worklink.plugin.getContentContext` | 解析目标轻应用 webContentsId(**仅 plugin 角色**) | pluginLightAppPreload 注入 |
16
17
 
18
+ **收消息(二选一,自动选择,不强依赖 waltz):**
19
+
20
+ | 监听后端 | 优先级 | 适用环境 | 说明 |
21
+ |---------|--------|---------|------|
22
+ | `window.waltz.event.on('native-call-js')` | 高(优先) | 有 waltz 注入的环境 | 推荐路径 |
23
+ | `window.handleMessageFromNative` | 低(回退) | 无 waltz 的环境 | hook 此全局方法(waltzBridge 投递消息的统一入口,`sendMessageToPluginLightAppById` 最终会调它) |
24
+
25
+ 两者都不可用时构造抛异常。SDK 内部按 waltz → handleMessageFromNative 顺序探测,无需手动指定。
26
+
17
27
  ## 安装(两个项目)
18
28
 
19
29
  共享包位于仓库根 `packages/lightAppBridge`(与 hkvs-llm-chat、vue-talent-cockpit 同级),两个项目以 `file:` 相对路径引用(npm 7+ 软链式安装,源码改动实时生效)。
@@ -6,10 +6,15 @@
6
6
  * 2. 请求/响应:基于 id 关联,对端 onMessage 回调的「返回值」带相同 id 回复。
7
7
  * 3. SDK 标识:所有消息携带 __sdk 标识 + 版本号,隔离其它消息,防止混淆。
8
8
  *
9
- * 依赖(构造时校验,缺失直接抛异常,请确保依赖注入完成后再 new):
10
- * - window.waltz.event.on 收消息
11
- * - window.worklink 发消息 / 取 webContentsId
12
- * - window.worklink.plugin.getContentContext (仅 plugin 角色需要)
9
+ * 依赖(构造时校验,缺失直接抛异常):
10
+ * - window.worklink 发消息 / 取 webContentsId(必需)
11
+ * - window.worklink.plugin.getContentContext (仅 plugin 角色需要,必需)
12
+ *
13
+ * 收消息不强依赖 waltz,按以下优先级自动选择监听后端:
14
+ * 1. window.waltz.event.on('native-call-js') —— 有 waltz 的环境(推荐)
15
+ * 2. window.handleMessageFromNative —— 无 waltz 时 hook 此全局方法
16
+ * (waltzBridge 投递消息的统一入口,sendMessageToPluginLightAppById 最终会调它)
17
+ * 两者都不可用则抛异常。
13
18
  *
14
19
  * 消息信封:
15
20
  * { __sdk: 'LightAppBridge', v: 1, id, type, data }
@@ -62,20 +67,21 @@ export default class LightAppBridge {
62
67
  private _started;
63
68
  private _connectDeferred;
64
69
  private _connectPromise;
70
+ private _unlisten;
65
71
  /**
66
72
  * @param role 角色,省略时按 window.worklink.plugin 是否存在自动探测
67
73
  * @param options 超时配置
68
- * @throws 依赖未就绪时抛出(window.waltz / window.worklink)
74
+ * @throws 依赖未就绪时抛出(window.worklink / 监听后端)
69
75
  */
70
76
  constructor(role?: BridgeRole, options?: BridgeOptions);
71
77
  /** 校验依赖是否就绪,缺失抛出明确异常 */
72
78
  private _checkDeps;
73
- /** 绑定 native-call-js 监听 */
79
+ /** 绑定消息监听:优先 waltz,回退 ipc(自动选择) */
74
80
  private _bindListener;
75
81
  /** 构造消息信封 */
76
82
  private _envelope;
77
- /** 处理一条原生消息 */
78
- private _onRaw;
83
+ /** 处理一条归一化后的消息 */
84
+ private _onMessage;
79
85
  /**
80
86
  * 建立连接(握手)。构造时已自动调用,通常无需手动调;如需重新握手可再次调用。
81
87
  * - 插件:发起握手(解析目标 id → 发 hello → 每秒重试直到收到 ack)
@@ -124,6 +130,8 @@ export default class LightAppBridge {
124
130
  * @returns 调用以取消监听
125
131
  */
126
132
  onMessage(handler: MessageHandler): () => void;
133
+ /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试 */
134
+ destroy(): void;
127
135
  }
128
136
  /**
129
137
  * 工厂函数
@@ -6,10 +6,15 @@
6
6
  * 2. 请求/响应:基于 id 关联,对端 onMessage 回调的「返回值」带相同 id 回复。
7
7
  * 3. SDK 标识:所有消息携带 __sdk 标识 + 版本号,隔离其它消息,防止混淆。
8
8
  *
9
- * 依赖(构造时校验,缺失直接抛异常,请确保依赖注入完成后再 new):
10
- * - window.waltz.event.on 收消息
11
- * - window.worklink 发消息 / 取 webContentsId
12
- * - window.worklink.plugin.getContentContext (仅 plugin 角色需要)
9
+ * 依赖(构造时校验,缺失直接抛异常):
10
+ * - window.worklink 发消息 / 取 webContentsId(必需)
11
+ * - window.worklink.plugin.getContentContext (仅 plugin 角色需要,必需)
12
+ *
13
+ * 收消息不强依赖 waltz,按以下优先级自动选择监听后端:
14
+ * 1. window.waltz.event.on('native-call-js') —— 有 waltz 的环境(推荐)
15
+ * 2. window.handleMessageFromNative —— 无 waltz 时 hook 此全局方法
16
+ * (waltzBridge 投递消息的统一入口,sendMessageToPluginLightAppById 最终会调它)
17
+ * 两者都不可用则抛异常。
13
18
  *
14
19
  * 消息信封:
15
20
  * { __sdk: 'LightAppBridge', v: 1, id, type, data }
@@ -47,11 +52,91 @@ function getInvoke() {
47
52
  }
48
53
  return null;
49
54
  }
55
+ /** waltz 路径是否可用 */
56
+ function hasWaltz() {
57
+ const w = window.waltz;
58
+ return !!(w && w.event && typeof w.event.on === 'function');
59
+ }
60
+ /**
61
+ * 选择可用的监听后端:优先 waltz,回退 hook window.handleMessageFromNative。
62
+ * 两者都不可用时抛异常。
63
+ */
64
+ function pickMessageBackend() {
65
+ // 1. waltz 路径:有 waltz 的环境
66
+ if (hasWaltz()) {
67
+ return (cb) => {
68
+ const handler = (raw) => {
69
+ const msg = parseHandleMessageFromNative(raw);
70
+ if (msg)
71
+ cb(msg);
72
+ };
73
+ window.waltz.event.on('native-call-js', handler);
74
+ return () => {
75
+ const ev = window.waltz?.event;
76
+ if (ev && typeof ev.off === 'function')
77
+ ev.off('native-call-js', handler);
78
+ else if (ev && typeof ev.removeListener === 'function')
79
+ ev.removeListener('native-call-js', handler);
80
+ };
81
+ };
82
+ }
83
+ // 2. hook handleMessageFromNative 路径:无 waltz 时
84
+ // waltzBridge 投递消息的统一入口:handleMessageFromNative(JSON.stringify({responseId, responseData}))
85
+ // 其中 responseData = {code, body:{eventName, params:{message, fromWebContentsId}}, msg}
86
+ if (typeof window.handleMessageFromNative === 'function') {
87
+ return (cb) => {
88
+ const w = window;
89
+ const prev = w.handleMessageFromNative;
90
+ const wrapped = function (raw) {
91
+ const msg = parseHandleMessageFromNative(raw);
92
+ if (msg)
93
+ cb(msg);
94
+ // 透传给原 handler(若存在),保持 waltz/业务自身逻辑不被破坏
95
+ if (typeof prev === 'function')
96
+ return prev.apply(this, arguments);
97
+ };
98
+ w.handleMessageFromNative = wrapped;
99
+ return () => {
100
+ // 还原:仅当仍为自身 wrapped 时才还原,避免覆盖后续可能的重新赋值
101
+ if (w.handleMessageFromNative === wrapped)
102
+ w.handleMessageFromNative = prev;
103
+ };
104
+ };
105
+ }
106
+ throw new Error('LightAppBridge 无可用监听后端:window.waltz.event 与 window.handleMessageFromNative 均不可用');
107
+ }
108
+ /**
109
+ * 解析 handleMessageFromNative / waltz native-call-js 的统一数据格式。
110
+ * - handleMessageFromNative 入参可能是 JSON 字符串 {responseId, responseData},
111
+ * responseData = {code, body:{eventName, params:{message, fromWebContentsId}}, msg}
112
+ * - waltz native-call-js 回调直接收到上述 responseData 对象
113
+ * 两者最终都归一为 { envelope, fromWebContentsId }。
114
+ */
115
+ function parseHandleMessageFromNative(raw) {
116
+ try {
117
+ let responseData = raw;
118
+ if (typeof raw === 'string') {
119
+ const parsed = JSON.parse(raw);
120
+ // handleMessageFromNative 入参带 responseId 包装
121
+ responseData = parsed && parsed.responseData != null ? parsed.responseData : parsed;
122
+ }
123
+ if (!responseData || !responseData.body || responseData.body.eventName !== 'plugin-message') {
124
+ return null;
125
+ }
126
+ const params = responseData.body.params || {};
127
+ const envelope = params.message;
128
+ const fromWebContentsId = params.fromWebContentsId != null ? params.fromWebContentsId : null;
129
+ return { envelope, fromWebContentsId };
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
50
135
  export default class LightAppBridge {
51
136
  /**
52
137
  * @param role 角色,省略时按 window.worklink.plugin 是否存在自动探测
53
138
  * @param options 超时配置
54
- * @throws 依赖未就绪时抛出(window.waltz / window.worklink)
139
+ * @throws 依赖未就绪时抛出(window.worklink / 监听后端)
55
140
  */
56
141
  constructor(role, options = {}) {
57
142
  this._pending = new Map();
@@ -64,6 +149,7 @@ export default class LightAppBridge {
64
149
  this._helloTimer = null;
65
150
  this._handshakeTimer = null;
66
151
  this._started = false;
152
+ this._unlisten = null;
67
153
  let r = role === 'plugin' || role === 'lightapp' ? role : 'lightapp';
68
154
  if (role !== 'plugin' && role !== 'lightapp') {
69
155
  const wl = window.worklink;
@@ -79,40 +165,38 @@ export default class LightAppBridge {
79
165
  return peerId;
80
166
  });
81
167
  this._checkDeps(); // 依赖校验,缺失直接抛异常
82
- this._bindListener(); // 绑定 native-call-js 监听
168
+ this._bindListener(); // 绑定消息监听(waltz 或 ipc,自动选择)
83
169
  this.connect(); // 构造即自动发起握手
84
170
  }
85
171
  /** 校验依赖是否就绪,缺失抛出明确异常 */
86
172
  _checkDeps() {
87
173
  const w = window;
88
- if (!(w.waltz && w.waltz.event) || typeof w.waltz.event.on !== 'function') {
89
- throw new Error('LightAppBridge 依赖 window.waltz.event.on,当前环境不可用');
90
- }
91
174
  if (!getInvoke()) {
92
175
  throw new Error('LightAppBridge 依赖 window.worklink.invokeModuleMethodSync,当前环境不可用');
93
176
  }
94
177
  if (this.role === 'plugin' && typeof (w.worklink.plugin && w.worklink.plugin.getContentContext) !== 'function') {
95
178
  throw new Error('LightAppBridge(plugin) 依赖 window.worklink.plugin.getContentContext,当前环境不可用');
96
179
  }
180
+ if (!hasWaltz() && typeof w.handleMessageFromNative !== 'function') {
181
+ throw new Error('LightAppBridge 无可用监听后端:window.waltz.event 与 window.handleMessageFromNative 均不可用');
182
+ }
97
183
  }
98
- /** 绑定 native-call-js 监听 */
184
+ /** 绑定消息监听:优先 waltz,回退 ipc(自动选择) */
99
185
  _bindListener() {
100
186
  if (this._started)
101
187
  return;
102
188
  this._started = true;
103
- window.waltz.event.on('native-call-js', (raw) => this._onRaw(raw));
189
+ const backend = pickMessageBackend();
190
+ this._unlisten = backend((msg) => this._onMessage(msg));
104
191
  }
105
192
  /** 构造消息信封 */
106
193
  _envelope(type, data, id = genId()) {
107
194
  return { __sdk: SDK_ID, v: PROTOCOL_VERSION, id, type, data };
108
195
  }
109
- /** 处理一条原生消息 */
110
- _onRaw(raw) {
111
- if (!raw || !raw.body || raw.body.eventName !== 'plugin-message')
112
- return;
113
- const params = raw.body.params || {};
114
- const envelope = params.message;
115
- const fromId = params.fromWebContentsId != null ? params.fromWebContentsId : null;
196
+ /** 处理一条归一化后的消息 */
197
+ _onMessage(msg) {
198
+ const envelope = msg.envelope;
199
+ const fromId = msg.fromWebContentsId;
116
200
  if (!isBridgeMsg(envelope))
117
201
  return;
118
202
  switch (envelope.type) {
@@ -324,6 +408,29 @@ export default class LightAppBridge {
324
408
  this._messageHandlers.splice(i, 1);
325
409
  };
326
410
  }
411
+ /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试 */
412
+ destroy() {
413
+ if (this._unlisten) {
414
+ try {
415
+ this._unlisten();
416
+ }
417
+ catch { /* ignore */ }
418
+ this._unlisten = null;
419
+ }
420
+ this._clearHelloTimer();
421
+ if (this._handshakeTimer) {
422
+ clearTimeout(this._handshakeTimer);
423
+ this._handshakeTimer = null;
424
+ }
425
+ // 拒绝所有未完成的请求
426
+ this._pending.forEach((p) => {
427
+ clearTimeout(p.timer);
428
+ p.reject(new Error('实例已销毁'));
429
+ });
430
+ this._pending.clear();
431
+ this._messageHandlers.length = 0;
432
+ this._connectCbs.length = 0;
433
+ }
327
434
  }
328
435
  /**
329
436
  * 工厂函数
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhh96/light-app-bridge",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "description": "招商随行(WorkLink)插件窗口(plugin)与轻应用窗口(lightapp)之间的双向通信 SDK",
6
6
  "main": "./dist/index.js",