@jhh96/light-app-bridge 1.0.4 → 1.0.6

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
@@ -15,16 +15,15 @@
15
15
  | `window.worklink`(含 `invokeModuleMethodSync`) | 发送消息 / 取 webContentsId | preload 注入 |
16
16
  | `window.worklink.plugin.getContentContext` | 解析目标轻应用 webContentsId(**仅 plugin 角色**) | pluginLightAppPreload 注入 |
17
17
 
18
- **收消息(二选一,自动选择,不强依赖 waltz):**
18
+ **收消息(自动选择,不强依赖 waltz):**
19
19
 
20
20
  | 监听后端 | 优先级 | 适用环境 | 说明 |
21
21
  |---------|--------|---------|------|
22
22
  | `window.waltz.event.on('native-call-js')` | 高(优先) | 有 waltz 注入的环境 | 推荐路径,立即可用 |
23
- | `window.handleMessageFromNative` | 低(回退) | 无 waltz 的环境 | hook 此全局方法(waltzBridge 投递消息的统一入口)。**可能晚于构造注入**,SDK 会轮询等待(上限 10s) |
23
+ | `window.handleMessageFromNative` | 低(回退) | 无 waltz 的环境 | 由 SDK 定义此全局方法(该方法原本由 waltz.min.js 注册;无 waltz 时为 undefined,renderer 投递消息前会检查其类型,没人定义则消息被丢弃)。renderer 收到消息后主动调用它传入消息。若该方法已存在则包装透传,不破坏原逻辑 |
24
24
 
25
- - 两者都始终不可用时,`connect()`/`ready()` reject(构造不抛,因监听后端允许稍后出现)
26
25
  - SDK 内部按 waltz → handleMessageFromNative 顺序探测,无需手动指定
27
- - 握手会等监听就绪后才开始,避免早到的 ack/hello 丢失
26
+ - 握手会等监听绑定后才开始,避免早到的 ack/hello 丢失
28
27
 
29
28
  ## 安装(两个项目)
30
29
 
@@ -59,14 +58,21 @@ bridge.onMessage((data, id) => {
59
58
 
60
59
  ```js
61
60
  {
62
- __sdk: 'lightAppBridge', // 标识:在 plugin-message 流中过滤出本 SDK 的消息
63
- id: 'r_xxx', // 关联 id:send 生成,回复原样带回
64
- type: 'req' | 'res', // 请求 / 响应
65
- data: any // 任意业务数据
61
+ __sdk: 'LightAppBridge', // SDK 标识:过滤出本 SDK 的消息,隔离其它消息
62
+ v: 1, // 协议版本号
63
+ id: 'r_xxx', // 关联 id:send 生成,回复原样带回
64
+ type: 'hello'|'ack'|'req'|'res', // 握手 hello/ack;业务 req/res
65
+ data: any // 任意业务数据
66
66
  }
67
67
  ```
68
68
 
69
+ ## 连接与生命周期
70
+
71
+ - 构造即自动握手(插件发 hello 每秒重试;轻应用持续等待、不超时)
72
+ - 握手失败后**再次 `connect()` 可重连**(自动重置状态,无需重建实例)
73
+ - `destroy()` 销毁实例:解绑监听、拒绝未完成请求;销毁后 `send`/`connect` 立即 reject
74
+
69
75
  ## 文件
70
76
 
71
- - `src/lightAppBridge.js` —— SDK 源码(ESM,可直接 import)
72
- - `src/index.js` —— barrel re-export
77
+ - `src/lightAppBridge.ts` —— SDK 源码(TypeScript,构建产物为 ESM)
78
+ - `src/index.ts` —— barrel re-export
@@ -37,7 +37,7 @@ export interface Envelope {
37
37
  export interface BridgeOptions {
38
38
  /** send 默认超时(ms),默认 3000 */
39
39
  timeout?: number;
40
- /** 握手超时(ms),默认 30000 */
40
+ /** 握手超时(ms),默认 30000。仅 plugin 角色生效;lightapp 端持续等待,不超时 */
41
41
  handshakeTimeout?: number;
42
42
  }
43
43
  /** send 单次调用选项 */
@@ -70,6 +70,8 @@ export default class LightAppBridge {
70
70
  private _connectPromise;
71
71
  private _unlisten;
72
72
  private _listenerReady;
73
+ private _connectFailed;
74
+ private _destroyed;
73
75
  /**
74
76
  * @param role 角色,省略时按 window.worklink.plugin 是否存在自动探测
75
77
  * @param options 超时配置
@@ -85,13 +87,21 @@ export default class LightAppBridge {
85
87
  /** 处理一条归一化后的消息 */
86
88
  private _onMessage;
87
89
  /**
88
- * 建立连接(握手)。构造时已自动调用,通常无需手动调;如需重新握手可再次调用。
89
- * - 插件:发起握手(解析目标 id → 发 hello → 每秒重试直到收到 ack)
90
- * - 轻应用:启动握手等待(被动等 hello;若插件始终未来则超时)
91
- * 多次调用幂等,返回同一个连接 Promise。
90
+ * 建立连接(握手)。构造时已自动调用,通常无需手动调。
91
+ * - 已连接 / 握手进行中:幂等,返回同一个 Promise
92
+ * - 上次握手失败:重新发起(内部自动重置状态,无需重建实例——
93
+ * 典型场景:插件先启动、轻应用窗口尚未打开导致超时,对端就绪后再次 connect 即可)
94
+ * - 实例已销毁:reject
95
+ * 行为:
96
+ * - 插件:发起握手(解析目标 id → 发 hello → 每秒重试直到收到 ack 或超时)
97
+ * - 轻应用:被动等待 hello(持续监听,不超时)
92
98
  * @returns 对端 webContentsId
93
99
  */
94
100
  connect(): Promise<number | null>;
101
+ /** 重置连接状态(用于首次握手或失败后重连) */
102
+ private _resetConnectState;
103
+ /** 连接失败:标记失败并 reject(再次 connect 可重试) */
104
+ private _failConnect;
95
105
  /** 发送 hello(携带自身 webContentsId) */
96
106
  private _sendHello;
97
107
  /** 收到 hello(轻应用侧):记录对端、回 ack、完成握手 */
@@ -132,7 +142,7 @@ export default class LightAppBridge {
132
142
  * @returns 调用以取消监听
133
143
  */
134
144
  onMessage(handler: MessageHandler): () => void;
135
- /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试 */
145
+ /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试。销毁后实例不可再用 */
136
146
  destroy(): void;
137
147
  }
138
148
  /**
@@ -163,6 +163,8 @@ export default class LightAppBridge {
163
163
  this._started = false;
164
164
  this._unlisten = null;
165
165
  this._listenerReady = null;
166
+ this._connectFailed = false;
167
+ this._destroyed = false;
166
168
  let r = role === 'plugin' || role === 'lightapp' ? role : 'lightapp';
167
169
  if (role !== 'plugin' && role !== 'lightapp') {
168
170
  const wl = window.worklink;
@@ -204,7 +206,7 @@ export default class LightAppBridge {
204
206
  })
205
207
  .catch((e) => {
206
208
  // 理论上不会发生(无 waltz 时必走定义路径),保险起见让连接失败并给出原因
207
- this._connectDeferred.reject(new Error('LightAppBridge 监听后端绑定失败 (' +
209
+ this._failConnect(new Error('LightAppBridge 监听后端绑定失败 (' +
208
210
  (e && e.message ? e.message : String(e)) + ')'));
209
211
  });
210
212
  }
@@ -236,26 +238,40 @@ export default class LightAppBridge {
236
238
  }
237
239
  // —— 连接 / 握手 —— //
238
240
  /**
239
- * 建立连接(握手)。构造时已自动调用,通常无需手动调;如需重新握手可再次调用。
240
- * - 插件:发起握手(解析目标 id → 发 hello → 每秒重试直到收到 ack)
241
- * - 轻应用:启动握手等待(被动等 hello;若插件始终未来则超时)
242
- * 多次调用幂等,返回同一个连接 Promise。
241
+ * 建立连接(握手)。构造时已自动调用,通常无需手动调。
242
+ * - 已连接 / 握手进行中:幂等,返回同一个 Promise
243
+ * - 上次握手失败:重新发起(内部自动重置状态,无需重建实例——
244
+ * 典型场景:插件先启动、轻应用窗口尚未打开导致超时,对端就绪后再次 connect 即可)
245
+ * - 实例已销毁:reject
246
+ * 行为:
247
+ * - 插件:发起握手(解析目标 id → 发 hello → 每秒重试直到收到 ack 或超时)
248
+ * - 轻应用:被动等待 hello(持续监听,不超时)
243
249
  * @returns 对端 webContentsId
244
250
  */
245
251
  connect() {
246
252
  var _a;
247
- if (this._connectStarted)
253
+ if (this._destroyed) {
254
+ return Promise.reject(new Error('实例已销毁,请重建实例'));
255
+ }
256
+ if (this._connected)
257
+ return this._connectPromise;
258
+ if (this._connectStarted && !this._connectFailed)
248
259
  return this._connectPromise;
260
+ // 首次调用,或上次握手失败:重置连接状态重新发起
261
+ this._resetConnectState();
249
262
  this._connectStarted = true;
250
263
  // 等监听后端绑定完成后再握手,避免对端的 ack/hello 在监听绑定前到达而丢失
251
264
  const listenerReady = (_a = this._listenerReady) !== null && _a !== void 0 ? _a : Promise.resolve();
252
- // 握手超时保护
253
- this._handshakeTimer = setTimeout(() => {
254
- if (!this._connected) {
255
- this._connectDeferred.reject(new Error(`握手超时 (${this.handshakeTimeout}ms),对端未响应`));
256
- this._clearHelloTimer();
257
- }
258
- }, this.handshakeTimeout);
265
+ // 握手超时保护:仅插件(主动方)需要——它发了 hello 却收不到 ack 说明对端异常。
266
+ // 轻应用(被动方)持续等待,不超时:插件可能任意晚才发起连接。
267
+ if (this.role === 'plugin') {
268
+ this._handshakeTimer = setTimeout(() => {
269
+ if (!this._connected) {
270
+ this._failConnect(new Error(`握手超时 (${this.handshakeTimeout}ms),对端未响应`));
271
+ this._clearHelloTimer();
272
+ }
273
+ }, this.handshakeTimeout);
274
+ }
259
275
  listenerReady
260
276
  .then(() => {
261
277
  if (this._connected)
@@ -277,9 +293,31 @@ export default class LightAppBridge {
277
293
  }
278
294
  // lightapp 被动等待 hello(listener 已绑定),收到后在 _onHello 中回 ack 并 resolve
279
295
  })
280
- .catch((e) => this._connectDeferred.reject(e));
296
+ .catch((e) => this._failConnect(e));
281
297
  return this._connectPromise;
282
298
  }
299
+ /** 重置连接状态(用于首次握手或失败后重连) */
300
+ _resetConnectState() {
301
+ this._connectDeferred = deferred();
302
+ this._connectPromise = this._connectDeferred.promise.then((peerId) => {
303
+ this._onConnected(peerId);
304
+ return peerId;
305
+ });
306
+ this._connectFailed = false;
307
+ this._connectStarted = false;
308
+ this._clearHelloTimer();
309
+ if (this._handshakeTimer) {
310
+ clearTimeout(this._handshakeTimer);
311
+ this._handshakeTimer = null;
312
+ }
313
+ }
314
+ /** 连接失败:标记失败并 reject(再次 connect 可重试) */
315
+ _failConnect(e) {
316
+ if (this._destroyed)
317
+ return;
318
+ this._connectFailed = true;
319
+ this._connectDeferred.reject(e instanceof Error ? e : new Error(String(e)));
320
+ }
283
321
  /** 发送 hello(携带自身 webContentsId) */
284
322
  _sendHello() {
285
323
  if (this._target == null)
@@ -288,16 +326,18 @@ export default class LightAppBridge {
288
326
  }
289
327
  /** 收到 hello(轻应用侧):记录对端、回 ack、完成握手 */
290
328
  _onHello(fromId) {
291
- if (fromId != null)
292
- this._recordPeer(fromId);
329
+ if (fromId == null)
330
+ return; // 无法定位对端则忽略(真实环境该字段恒有值),握手不完成
331
+ this._recordPeer(fromId);
293
332
  // 回 ack,携带自身 webContentsId
294
- if (fromId != null)
295
- this._send(fromId, this._envelope('ack', { from: this.getId() }));
333
+ this._send(fromId, this._envelope('ack', { from: this.getId() }));
296
334
  // 握手完成(resolve 幂等,重复 hello 只回 ack)
297
335
  this._connectDeferred.resolve(fromId);
298
336
  }
299
337
  /** 收到 ack(插件侧):确认连接 */
300
338
  _onAck(fromId) {
339
+ if (fromId == null)
340
+ return; // 无法定位对端则忽略
301
341
  this._recordPeer(fromId);
302
342
  this._connectDeferred.resolve(fromId);
303
343
  }
@@ -399,6 +439,8 @@ export default class LightAppBridge {
399
439
  * @returns 对端回调返回的数据
400
440
  */
401
441
  send(data, options = {}) {
442
+ if (this._destroyed)
443
+ return Promise.reject(new Error('实例已销毁'));
402
444
  const timeout = options.timeout != null ? options.timeout : this.timeout;
403
445
  return this.ready().then((targetId) => {
404
446
  const id = genId();
@@ -434,8 +476,11 @@ export default class LightAppBridge {
434
476
  this._messageHandlers.splice(i, 1);
435
477
  };
436
478
  }
437
- /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试 */
479
+ /** 销毁实例:解绑消息监听、清空待处理请求、停止握手重试。销毁后实例不可再用 */
438
480
  destroy() {
481
+ if (this._destroyed)
482
+ return;
483
+ this._destroyed = true;
439
484
  if (this._unlisten) {
440
485
  try {
441
486
  this._unlisten();
@@ -448,6 +493,8 @@ export default class LightAppBridge {
448
493
  clearTimeout(this._handshakeTimer);
449
494
  this._handshakeTimer = null;
450
495
  }
496
+ // 进行中的握手直接失败(已 resolve 的话 reject 是 no-op)
497
+ this._connectDeferred.reject(new Error('实例已销毁'));
451
498
  // 拒绝所有未完成的请求
452
499
  this._pending.forEach((p) => {
453
500
  clearTimeout(p.timer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhh96/light-app-bridge",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "招商随行(WorkLink)插件窗口(plugin)与轻应用窗口(lightapp)之间的双向通信 SDK",
6
6
  "main": "./dist/index.js",