@deeprespond/im-tauri 0.0.0-stage → 1.0.0-alpha

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/engine.js ADDED
@@ -0,0 +1,536 @@
1
+ import { currentLabel, ENGINE_LABEL, unsupported, Bridge, makeLogger, Desktop, createTauriPlatform, invalidState, osFromUserAgent, clickEvent } from './chunk-DMEEI2YZ.js';
2
+ import { DRError, createDRClient } from '@deeprespond/im-web';
3
+ import { displayName, conversationTitle } from '@deeprespond/im-web/render';
4
+
5
+ // src/desktop/ringtone.ts
6
+ var Ringtone = class {
7
+ constructor(src) {
8
+ this.src = src;
9
+ }
10
+ src;
11
+ #audio = null;
12
+ #ctx = null;
13
+ #timer = null;
14
+ get playing() {
15
+ return this.#audio !== null || this.#timer !== null;
16
+ }
17
+ start() {
18
+ if (this.playing) return;
19
+ if (this.src && typeof Audio !== "undefined") {
20
+ const audio = new Audio(this.src);
21
+ audio.loop = true;
22
+ this.#audio = audio;
23
+ void audio.play().catch(() => {
24
+ this.#audio = null;
25
+ this.#synth();
26
+ });
27
+ return;
28
+ }
29
+ this.#synth();
30
+ }
31
+ stop() {
32
+ if (this.#audio) {
33
+ this.#audio.pause();
34
+ this.#audio.src = "";
35
+ this.#audio = null;
36
+ }
37
+ if (this.#timer !== null) {
38
+ clearInterval(this.#timer);
39
+ this.#timer = null;
40
+ }
41
+ if (this.#ctx) {
42
+ void this.#ctx.close().catch(() => {
43
+ });
44
+ this.#ctx = null;
45
+ }
46
+ }
47
+ /** 每 3 秒响一次:两段 0.4 秒的双音(440 Hz 与 480 Hz 叠加,接近电话铃声)。 */
48
+ #synth() {
49
+ const Ctx = globalThis.AudioContext;
50
+ if (!Ctx) return;
51
+ const ctx = new Ctx();
52
+ this.#ctx = ctx;
53
+ const ring = () => {
54
+ const now = ctx.currentTime;
55
+ for (const start of [0, 0.6]) {
56
+ const gain = ctx.createGain();
57
+ gain.gain.setValueAtTime(0, now + start);
58
+ gain.gain.linearRampToValueAtTime(0.2, now + start + 0.02);
59
+ gain.gain.setValueAtTime(0.2, now + start + 0.38);
60
+ gain.gain.linearRampToValueAtTime(0, now + start + 0.4);
61
+ gain.connect(ctx.destination);
62
+ for (const freq of [440, 480]) {
63
+ const osc = ctx.createOscillator();
64
+ osc.frequency.value = freq;
65
+ osc.connect(gain);
66
+ osc.start(now + start);
67
+ osc.stop(now + start + 0.4);
68
+ }
69
+ }
70
+ };
71
+ void ctx.resume?.().catch(() => {
72
+ });
73
+ ring();
74
+ this.#timer = setInterval(ring, 3e3);
75
+ }
76
+ };
77
+
78
+ // src/desktop/texts.ts
79
+ var texts = {
80
+ friendRequest: { title: "新的好友申请", body: (name, message) => message ? `${name}:${message}` : `${name} 请求添加你为好友` },
81
+ groupInvitation: { title: "入群邀请", body: (group) => `邀请你加入“${group}”` },
82
+ groupApplication: { title: "入群申请", body: (group) => `“${group}”有新的入群申请` },
83
+ incomingCall: { title: (name) => name, body: (video) => video ? "邀请你视频通话" : "邀请你语音通话", accept: "接听", decline: "拒绝" },
84
+ sessionEnded: {
85
+ replaced: { title: "已在其他设备登录", body: "你的账号已在其他设备登录,点击打开应用" },
86
+ expired: { title: "登录已失效", body: "请打开应用重新登录" }
87
+ },
88
+ unnamedGroup: "群聊"
89
+ };
90
+
91
+ // src/desktop/alerts.ts
92
+ var SAME_CONVERSATION_QUIET_MS = 3e3;
93
+ var GLOBAL_SOUND_GAP_MS = 1e3;
94
+ var EngineAlerts = class {
95
+ constructor(bridge, client, desktop, host, opts, onWarn) {
96
+ this.bridge = bridge;
97
+ this.client = client;
98
+ this.desktop = desktop;
99
+ this.host = host;
100
+ this.opts = opts;
101
+ this.onWarn = onWarn;
102
+ this.#ringtone = new Ringtone(opts.notifications?.ringtone);
103
+ }
104
+ bridge;
105
+ client;
106
+ desktop;
107
+ host;
108
+ opts;
109
+ onWarn;
110
+ alertHandlers = /* @__PURE__ */ new Set();
111
+ callHandlers = /* @__PURE__ */ new Set();
112
+ #lastSound = /* @__PURE__ */ new Map();
113
+ #lastAnySound = 0;
114
+ #ringtone;
115
+ #ringing = /* @__PURE__ */ new Map();
116
+ #friendUnread = -1;
117
+ #invitations = /* @__PURE__ */ new Set();
118
+ #offs = [];
119
+ get #mode() {
120
+ return this.opts.notifications?.mode ?? "auto";
121
+ }
122
+ start() {
123
+ const c = this.client;
124
+ this.#offs.push(
125
+ c.on("message.received", (p) => void this.#message(p.message, p.notify)),
126
+ c.on("friendRequests.changed", (p) => void this.#friendRequests(p.lists, p.unread_count)),
127
+ c.on("groupRequests.changed", (p) => void this.#groupRequests(p)),
128
+ c.on("call.incoming", (p) => void this.#incoming(p.call, p.alert)),
129
+ c.on("auth.stateChanged", (state) => {
130
+ if (state.kind !== "signed_out" && state.kind !== "ended") return;
131
+ const cleared = this.#signedOut();
132
+ if (state.kind === "ended") void cleared.then(() => this.#sessionEnded(state.code, state.reason));
133
+ })
134
+ );
135
+ }
136
+ stop() {
137
+ for (const off of this.#offs.splice(0)) off();
138
+ for (const stop of this.#ringing.values()) stop();
139
+ this.#ringing.clear();
140
+ this.#ringtone.stop();
141
+ }
142
+ /** 负责提醒的是不是引擎(没有可见的界面窗口)。 */
143
+ #engineAlerts() {
144
+ try {
145
+ return this.host().alertTab().self;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ async #emitAlert(alert) {
151
+ for (const handler of [...this.alertHandlers]) {
152
+ try {
153
+ await handler(alert);
154
+ } catch (err) {
155
+ this.onWarn("App 的 onAlert 出错", err);
156
+ }
157
+ }
158
+ }
159
+ #text(alert, fallback) {
160
+ const format = this.opts.notifications?.format;
161
+ if (!format) return fallback;
162
+ try {
163
+ return format(alert);
164
+ } catch (err) {
165
+ this.onWarn("notifications.format 出错,使用默认文字", err);
166
+ return fallback;
167
+ }
168
+ }
169
+ /** 同一会话 3 秒内不再响;全部通知每秒最多响一次。 */
170
+ #sound(key) {
171
+ if (this.opts.notifications?.sound === false) return false;
172
+ const now = Date.now();
173
+ const last = this.#lastSound.get(key) ?? 0;
174
+ const allowed = now - last >= SAME_CONVERSATION_QUIET_MS && now - this.#lastAnySound >= GLOBAL_SOUND_GAP_MS;
175
+ this.#lastSound.set(key, now);
176
+ if (allowed) this.#lastAnySound = now;
177
+ if (this.#lastSound.size > 500) this.#lastSound.clear();
178
+ return allowed;
179
+ }
180
+ #attention() {
181
+ if (this.opts.attention === false) return;
182
+ void this.bridge.call("attention_request", { critical: false, label: null }).catch(() => {
183
+ });
184
+ }
185
+ async #message(message, decision) {
186
+ if (!decision?.notify) return;
187
+ const host = this.host();
188
+ const alertTab = host.alertTab();
189
+ if (this.#mode === "manual") {
190
+ if (alertTab.self) await this.#emitAlert({ kind: "message", message, notify: decision });
191
+ return;
192
+ }
193
+ if (host.viewing(message.conversation_id)) return;
194
+ if (!alertTab.self && alertTab.focused) return;
195
+ const text = this.#text({ kind: "message", message, notify: decision }, { title: decision.title ?? "", body: decision.body ?? "" });
196
+ if (!text) return;
197
+ await this.desktop.show({ id: `dr:msg:${message.conversation_id}`, kind: "message", title: text.title, body: text.body, conversation_id: message.conversation_id, seq: message.seq ?? void 0, sound: this.#sound(message.conversation_id) }).catch((err) => this.onWarn("显示消息通知失败", err));
198
+ this.#attention();
199
+ }
200
+ async #notice(alert, id) {
201
+ if (this.opts.notifications?.notices === false) return;
202
+ if (this.#mode === "manual") {
203
+ if (this.#engineAlerts()) await this.#emitAlert(alert);
204
+ return;
205
+ }
206
+ const alertTab = this.host().alertTab();
207
+ if (!alertTab.self && alertTab.focused) return;
208
+ const text = this.#text(alert, { title: alert.title, body: alert.body });
209
+ if (!text) return;
210
+ const data = {};
211
+ if (alert.user) data.user = alert.user;
212
+ if (alert.group_id) data.group_id = alert.group_id;
213
+ await this.desktop.show({ id, kind: "notice", title: text.title, body: text.body, data, sound: this.#sound(id) }).catch((err) => this.onWarn("显示通知失败", err));
214
+ this.#attention();
215
+ }
216
+ async #friendRequests(lists, unread) {
217
+ const previous = this.#friendUnread;
218
+ this.#friendUnread = unread;
219
+ if (!lists.includes("received") || previous < 0 || unread <= previous) return;
220
+ let latest;
221
+ try {
222
+ latest = (await this.client.friends.requests.received({ status: "pending", limit: 1 })).items[0];
223
+ } catch {
224
+ return;
225
+ }
226
+ if (!latest) return;
227
+ const name = latest.nickname || latest.username;
228
+ await this.#notice({ kind: "notice", type: "friend_request", title: texts.friendRequest.title, body: texts.friendRequest.body(name, latest.message), user: latest.username }, `dr:notice:friend:${latest.username}`);
229
+ }
230
+ async #groupRequests(p) {
231
+ if (p.list === "invitations" && Array.isArray(p.items)) {
232
+ const fresh = [];
233
+ const seen = /* @__PURE__ */ new Set();
234
+ for (const item of p.items) {
235
+ const id = item.group_id ?? item.group?.group_id;
236
+ if (!id || item.status && item.status !== "pending") continue;
237
+ seen.add(id);
238
+ if (!this.#invitations.has(id)) fresh.push({ group_id: id, name: item.group?.name });
239
+ }
240
+ const first = this.#invitations.size === 0 && fresh.length === seen.size && seen.size > 0 && !this.#primed;
241
+ this.#invitations = seen;
242
+ this.#primed = true;
243
+ if (first) return;
244
+ for (const inv of fresh) {
245
+ const group = inv.name || texts.unnamedGroup;
246
+ await this.#notice({ kind: "notice", type: "group_request", title: texts.groupInvitation.title, body: texts.groupInvitation.body(group), group_id: inv.group_id }, `dr:notice:group:${inv.group_id}`);
247
+ }
248
+ return;
249
+ }
250
+ if (p.list === "pending" && p.group_id) {
251
+ const group = await this.client.groups.get(p.group_id).catch(() => void 0);
252
+ await this.#notice({ kind: "notice", type: "group_request", title: texts.groupApplication.title, body: texts.groupApplication.body(group?.name || texts.unnamedGroup), group_id: p.group_id }, `dr:notice:group:${p.group_id}`);
253
+ }
254
+ }
255
+ #primed = false;
256
+ async #incoming(call, alert) {
257
+ if (!alert) return;
258
+ if ((this.opts.notifications?.calls ?? "auto") === "manual") {
259
+ for (const handler of [...this.callHandlers]) {
260
+ try {
261
+ await handler(call);
262
+ } catch (err) {
263
+ this.onWarn("App 的 onIncomingCall 出错", err);
264
+ }
265
+ }
266
+ return;
267
+ }
268
+ const id = `dr:call:${call.call_id}`;
269
+ if (this.#ringing.has(id)) return;
270
+ const snapshot = call.getSnapshot();
271
+ const inviter = snapshot.inviter?.nickname || snapshot.inviter?.username || (snapshot.call.initiator ? displayName(this.client, snapshot.call.initiator) : "");
272
+ const title = snapshot.group?.name ? `${inviter}(${snapshot.group.name})` : inviter;
273
+ await this.desktop.show({
274
+ id,
275
+ kind: "call",
276
+ title: texts.incomingCall.title(title),
277
+ body: texts.incomingCall.body(snapshot.call.media === "video"),
278
+ data: { call_id: call.call_id },
279
+ sound: false,
280
+ actions: [
281
+ { id: "accept", title: texts.incomingCall.accept },
282
+ { id: "decline", title: texts.incomingCall.decline }
283
+ ]
284
+ }).catch((err) => this.onWarn("显示来电通知失败", err));
285
+ this.#ringtone.start();
286
+ this.#attention();
287
+ const stop = () => {
288
+ unsubscribe();
289
+ this.#ringing.delete(id);
290
+ void this.desktop.clear(id).catch(() => {
291
+ });
292
+ if (this.#ringing.size === 0) this.#ringtone.stop();
293
+ };
294
+ const unsubscribe = call.subscribe(() => {
295
+ const s = call.getSnapshot();
296
+ if (!s.ringing || s.call.status === "ended" || s.self?.state !== "ringing") stop();
297
+ });
298
+ this.#ringing.set(id, stop);
299
+ }
300
+ /** 来电通知上的“拒绝”(插件直接交给引擎)。 */
301
+ async declineCall(callId) {
302
+ const call = this.client.calls.incoming.find((c) => c.call_id === callId);
303
+ if (!call) return;
304
+ await call.reject().catch((err) => this.onWarn("拒绝来电失败", err));
305
+ }
306
+ async #sessionEnded(code, reason) {
307
+ if (this.#mode !== "auto" || !this.#engineAlerts()) return;
308
+ const replaced = code === "session_revoked" && (reason === "replaced" || reason === "device_limit" || reason === "kicked");
309
+ const t = replaced ? texts.sessionEnded.replaced : texts.sessionEnded.expired;
310
+ await this.desktop.show({ id: "dr:session", kind: "session", title: t.title, body: t.body }).catch((err) => this.onWarn("显示通知失败", err));
311
+ }
312
+ /** 退出登录、会话结束:清除本应用已显示的通知,停止铃声(与 Flutter SDK 相同)。 */
313
+ #signedOut() {
314
+ for (const stop of [...this.#ringing.values()]) stop();
315
+ this.#friendUnread = -1;
316
+ this.#invitations.clear();
317
+ this.#primed = false;
318
+ return this.bridge.call("notify_clear", { all: true }).catch(() => {
319
+ });
320
+ }
321
+ /** 会话标题(App 的 format 中可以使用)。 */
322
+ title(view) {
323
+ return conversationTitle(this.client, view);
324
+ }
325
+ };
326
+
327
+ // src/desktop/badge.ts
328
+ var MERGE_MS = 500;
329
+ var UnreadBadge = class {
330
+ constructor(bridge, client, host, opts) {
331
+ this.bridge = bridge;
332
+ this.client = client;
333
+ this.host = host;
334
+ this.opts = opts;
335
+ }
336
+ bridge;
337
+ client;
338
+ host;
339
+ opts;
340
+ #timer = null;
341
+ #last = null;
342
+ #offs = [];
343
+ start() {
344
+ this.#offs.push(
345
+ this.client.on("conversations.unreadChanged", () => this.schedule()),
346
+ this.client.on("push.changed", () => this.schedule()),
347
+ this.client.on("auth.stateChanged", (state) => {
348
+ if (state.kind !== "signed_in" && state.kind !== "suspended") this.#apply({ total: 0, badge: 0 });
349
+ else this.schedule();
350
+ })
351
+ );
352
+ this.schedule();
353
+ }
354
+ stop() {
355
+ for (const off of this.#offs.splice(0)) off();
356
+ if (this.#timer !== null) clearTimeout(this.#timer);
357
+ this.#timer = null;
358
+ }
359
+ schedule() {
360
+ if (this.#timer !== null) return;
361
+ this.#timer = setTimeout(() => {
362
+ this.#timer = null;
363
+ let totals;
364
+ try {
365
+ totals = this.host().unreadTotals();
366
+ } catch {
367
+ return;
368
+ }
369
+ const signedIn = this.client.auth.state.kind === "signed_in" || this.client.auth.state.kind === "suspended";
370
+ const total = signedIn ? totals.total : 0;
371
+ const badge = signedIn ? this.opts.includeMuted ? totals.total : totals.unmuted : 0;
372
+ this.#apply({ total, badge });
373
+ }, MERGE_MS);
374
+ }
375
+ #apply(next) {
376
+ if (this.#last && this.#last.total === next.total && this.#last.badge === next.badge) return;
377
+ this.#last = next;
378
+ if (this.opts.auto) void this.bridge.call("badge_set", { count: next.badge > 0 ? next.badge : null }).catch((err) => this.opts.onWarn("设置角标失败", err));
379
+ void this.bridge.call("unread_report", next).catch(() => {
380
+ });
381
+ try {
382
+ this.host().publish("desktop.unreadChanged", next);
383
+ } catch {
384
+ }
385
+ }
386
+ };
387
+
388
+ // src/engine.ts
389
+ var ALIVE_MS = 3e3;
390
+ var PROBE_TIMEOUT_MS = 5e3;
391
+ var WAKE_LAG_MS = 5e3;
392
+ var DEFAULT_PERSIST_MAX = 200 * 1024 * 1024;
393
+ var pages = /* @__PURE__ */ new WeakMap();
394
+ function createDRTauriEngine(options, env) {
395
+ if ((env ? env.bridge.label : currentLabel()) !== ENGINE_LABEL) throw unsupported("not_engine_window", "createDRTauriEngine 只能在引擎页中调用");
396
+ if (!options || typeof options.appKey !== "string") throw new DRError({ code: "local_validation", message: "appKey", details: { reason: "required", field: "appKey" }, source: "local" });
397
+ const bridge = env?.bridge ?? Bridge.get();
398
+ let engines = pages.get(bridge);
399
+ if (!engines) {
400
+ engines = /* @__PURE__ */ new Map();
401
+ pages.set(bridge, engines);
402
+ wirePage(bridge, engines);
403
+ }
404
+ const logger = makeLogger(bridge, options.logger);
405
+ const warn = (message, err) => logger.sink({ level: "warn", time: Date.now(), module: "tauri", message, code: err instanceof DRError ? err.code : void 0, reason: err instanceof DRError ? err.reason : void 0 });
406
+ let client;
407
+ const desktop = new Desktop(bridge, () => client);
408
+ const tp = createTauriPlatform({
409
+ role: "leader",
410
+ appKey: options.appKey,
411
+ bridge,
412
+ encrypt: options.encryptDatabase === true,
413
+ onWarn: warn,
414
+ extend: (h) => {
415
+ env?.onHost?.(h);
416
+ return { desktop: { value: desktop, enumerable: true } };
417
+ }
418
+ });
419
+ if (env?.overrides) Object.assign(tp.platform, env.overrides);
420
+ tp.platform.ready = bridge.ready.then(async (info) => {
421
+ if (info.another_instance) throw invalidState("another_instance");
422
+ await tp.engineKv.load();
423
+ });
424
+ client = createDRClient({
425
+ appKey: options.appKey,
426
+ apiUrl: options.apiUrl,
427
+ wsUrl: options.wsUrl,
428
+ platform: osFromUserAgent(typeof navigator !== "undefined" ? navigator.userAgent : ""),
429
+ deviceName: options.deviceName,
430
+ appVersion: options.appVersion,
431
+ ticketProvider: options.ticketProvider,
432
+ autoMarkRead: options.autoMarkRead,
433
+ conversationWindow: options.conversationWindow,
434
+ userCacheTtlMs: options.userCacheTtlMs,
435
+ outbox: { maxAgeHours: options.outbox?.maxAgeHours, persistAttachmentMaxBytes: options.outbox?.persistAttachmentMaxBytes ?? DEFAULT_PERSIST_MAX },
436
+ media: options.media,
437
+ logger,
438
+ platformAdapter: tp.platform
439
+ });
440
+ const host = tp.host;
441
+ const alerts = new EngineAlerts(bridge, client, desktop, host, options, warn);
442
+ const badge = new UnreadBadge(bridge, client, host, { auto: (options.badge ?? "auto") === "auto", includeMuted: options.badgeIncludesMuted === true, onWarn: warn });
443
+ const entry = { client, host, alerts, badge, platform: tp };
444
+ engines.get(options.appKey)?.alerts.stop();
445
+ engines.get(options.appKey)?.badge.stop();
446
+ engines.set(options.appKey, entry);
447
+ void tp.platform.ready.then(
448
+ () => {
449
+ const signedIn = () => client.auth.state.kind === "signed_in" || client.auth.state.kind === "suspended";
450
+ const report = () => void bridge.call("engine_ready", { app_key: options.appKey, signed_in: signedIn() }).catch((err) => warn("报告引擎就绪失败", err));
451
+ report();
452
+ client.on("auth.stateChanged", report);
453
+ alerts.start();
454
+ badge.start();
455
+ },
456
+ (err) => warn("引擎客户端没有创建", err)
457
+ );
458
+ return Object.freeze({
459
+ client,
460
+ onAlert: (handler) => {
461
+ alerts.alertHandlers.add(handler);
462
+ return () => alerts.alertHandlers.delete(handler);
463
+ },
464
+ onIncomingCall: (handler) => {
465
+ alerts.callHandlers.add(handler);
466
+ return () => alerts.callHandlers.delete(handler);
467
+ }
468
+ });
469
+ }
470
+ function wirePage(bridge, engines) {
471
+ const each = (fn) => {
472
+ for (const e of engines.values()) {
473
+ try {
474
+ fn(e);
475
+ } catch {
476
+ }
477
+ }
478
+ };
479
+ setInterval(() => void bridge.call("engine_alive").catch(() => {
480
+ }), ALIVE_MS);
481
+ bridge.on(
482
+ "wake",
483
+ () => each((e) => {
484
+ const host = e.host();
485
+ const lag = host.heartbeatLag();
486
+ if (lag > WAKE_LAG_MS) {
487
+ host.log("warn", "tauri", `唤醒检查落后 ${Math.round(lag)} 毫秒,计时器可能被系统节流`);
488
+ host.checkConnection("wake_check", PROBE_TIMEOUT_MS);
489
+ }
490
+ })
491
+ );
492
+ bridge.on(
493
+ "resume",
494
+ (event) => each((e) => {
495
+ const host = e.host();
496
+ host.checkConnection("resume", PROBE_TIMEOUT_MS);
497
+ host.publish("desktop.systemResumed", { slept_ms: Number(event.slept_ms) || 0 });
498
+ })
499
+ );
500
+ bridge.on(
501
+ "network",
502
+ (event) => each((e) => {
503
+ const host = e.host();
504
+ if (event.online !== false) host.checkConnection("network", PROBE_TIMEOUT_MS);
505
+ host.publish("desktop.networkChanged", { online: event.online !== false });
506
+ })
507
+ );
508
+ bridge.on("screen_lock", () => {
509
+ });
510
+ bridge.on("storage_full", (event) => {
511
+ const e = engines.get(String(event.app_key));
512
+ e?.host().publish("storage.full", { freed_users: Number(event.freed_users) || 0 });
513
+ });
514
+ bridge.on("call_action", (event) => {
515
+ if (event.action !== "decline") return;
516
+ each((e) => void e.alerts.declineCall(String(event.call_id)));
517
+ });
518
+ bridge.on("notification_click", (event) => {
519
+ const payload = clickEvent(bridge, event);
520
+ each((e) => e.host().emit("desktop.notificationClicked", payload));
521
+ });
522
+ bridge.on("exit", () => {
523
+ const work = [...engines.values()].map(async (e) => {
524
+ e.alerts.stop();
525
+ e.badge.stop();
526
+ await e.platform.engineKv?.flush();
527
+ await e.host().shutdown();
528
+ });
529
+ void Promise.allSettled(work).then(() => bridge.call("engine_exit_done").catch(() => {
530
+ }));
531
+ });
532
+ }
533
+
534
+ export { createDRTauriEngine };
535
+ //# sourceMappingURL=engine.js.map
536
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/desktop/ringtone.ts","../src/desktop/texts.ts","../src/desktop/alerts.ts","../src/desktop/badge.ts","../src/engine.ts"],"names":[],"mappings":"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","file":"engine.js","sourcesContent":["/**\n * 没有界面窗口时的来电铃声(《2.功能概述文档》第 10 节):在引擎页中播放。给了资源路径的循环播放那个文件,\n * 否则用 Web Audio 合成的双音铃声。Tauri 的 WebView 默认不要求用户手势就能播放(wry 的 autoplay 默认开启)。\n */\nexport class Ringtone {\n #audio: HTMLAudioElement | null = null;\n #ctx: AudioContext | null = null;\n #timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(private readonly src?: string) {}\n\n get playing(): boolean {\n return this.#audio !== null || this.#timer !== null;\n }\n\n start(): void {\n if (this.playing) return;\n if (this.src && typeof Audio !== 'undefined') {\n const audio = new Audio(this.src);\n audio.loop = true;\n this.#audio = audio;\n void audio.play().catch(() => {\n this.#audio = null;\n this.#synth();\n });\n return;\n }\n this.#synth();\n }\n\n stop(): void {\n if (this.#audio) {\n this.#audio.pause();\n this.#audio.src = '';\n this.#audio = null;\n }\n if (this.#timer !== null) {\n clearInterval(this.#timer);\n this.#timer = null;\n }\n if (this.#ctx) {\n void this.#ctx.close().catch(() => {});\n this.#ctx = null;\n }\n }\n\n /** 每 3 秒响一次:两段 0.4 秒的双音(440 Hz 与 480 Hz 叠加,接近电话铃声)。 */\n #synth(): void {\n const Ctx = (globalThis as { AudioContext?: typeof AudioContext }).AudioContext;\n if (!Ctx) return;\n const ctx = new Ctx();\n this.#ctx = ctx;\n const ring = () => {\n const now = ctx.currentTime;\n for (const start of [0, 0.6]) {\n const gain = ctx.createGain();\n gain.gain.setValueAtTime(0, now + start);\n gain.gain.linearRampToValueAtTime(0.2, now + start + 0.02);\n gain.gain.setValueAtTime(0.2, now + start + 0.38);\n gain.gain.linearRampToValueAtTime(0, now + start + 0.4);\n gain.connect(ctx.destination);\n for (const freq of [440, 480]) {\n const osc = ctx.createOscillator();\n osc.frequency.value = freq;\n osc.connect(gain);\n osc.start(now + start);\n osc.stop(now + start + 0.4);\n }\n }\n };\n void ctx.resume?.().catch(() => {});\n ring();\n this.#timer = setInterval(ring, 3000);\n }\n}\n","/** 引擎自动显示的通知中 SDK 自己的文字(消息通知的标题和正文由推送设置的预览方式生成,见 push.shouldNotify)。 */\nexport const texts = {\n friendRequest: { title: '新的好友申请', body: (name: string, message: string) => (message ? `${name}:${message}` : `${name} 请求添加你为好友`) },\n groupInvitation: { title: '入群邀请', body: (group: string) => `邀请你加入“${group}”` },\n groupApplication: { title: '入群申请', body: (group: string) => `“${group}”有新的入群申请` },\n incomingCall: { title: (name: string) => name, body: (video: boolean) => (video ? '邀请你视频通话' : '邀请你语音通话'), accept: '接听', decline: '拒绝' },\n sessionEnded: {\n replaced: { title: '已在其他设备登录', body: '你的账号已在其他设备登录,点击打开应用' },\n expired: { title: '登录已失效', body: '请打开应用重新登录' },\n },\n unnamedGroup: '群聊',\n};\n","import type { CallHandle, DRClient, FriendRequest, MessageView, NotifyDecision } from '@deeprespond/im-web';\nimport { conversationTitle, displayName } from '@deeprespond/im-web/render';\nimport type { PlatformHost } from '@deeprespond/im-web/platform';\nimport type { Bridge } from '../adapter/ipc';\nimport type { DRTauriEngineOptions, EngineAlert } from '../types';\nimport type { Desktop } from './module';\nimport { Ringtone } from './ringtone';\nimport { texts } from './texts';\n\nconst SAME_CONVERSATION_QUIET_MS = 3000;\nconst GLOBAL_SOUND_GAP_MS = 1000;\n\ntype Handler<T> = (value: T) => void | Promise<void>;\n\n/**\n * 引擎的提醒(《2.功能概述文档》第 8 节、第 10 节):\n * - auto:新消息经 push.shouldNotify 判断后,会话不是正被看、负责提醒的窗口没有焦点(或负责提醒的是引擎)时由引擎显示系统通知;\n * 同一会话只保留一条,3 秒内不再响,全部通知每秒最多响一次。好友申请、入群申请同样提醒。\n * - manual:负责提醒的是引擎时交给 App 的 onAlert。\n * - 来电:负责提醒的是引擎时,auto 显示带“接听”“拒绝”的通知并响铃,manual 交给 onIncomingCall。\n * - 账号被挤下线、会话过期:没有可见的界面窗口时显示一条通知。\n */\nexport class EngineAlerts {\n readonly alertHandlers = new Set<Handler<EngineAlert>>();\n readonly callHandlers = new Set<Handler<CallHandle>>();\n #lastSound = new Map<string, number>();\n #lastAnySound = 0;\n #ringtone: Ringtone;\n #ringing = new Map<string, () => void>();\n #friendUnread = -1;\n #invitations = new Set<string>();\n #offs: Array<() => void> = [];\n\n constructor(\n private readonly bridge: Bridge,\n private readonly client: DRClient,\n private readonly desktop: Desktop,\n private readonly host: () => PlatformHost,\n private readonly opts: DRTauriEngineOptions,\n private readonly onWarn: (message: string, err?: unknown) => void,\n ) {\n this.#ringtone = new Ringtone(opts.notifications?.ringtone);\n }\n\n get #mode(): 'auto' | 'manual' {\n return this.opts.notifications?.mode ?? 'auto';\n }\n\n start(): void {\n const c = this.client;\n this.#offs.push(\n c.on('message.received', (p) => void this.#message(p.message, p.notify as NotifyDecision | null)),\n c.on('friendRequests.changed', (p) => void this.#friendRequests(p.lists, p.unread_count)),\n c.on('groupRequests.changed', (p) => void this.#groupRequests(p as { list: string; group_id: string | null; items?: unknown[] })),\n c.on('call.incoming', (p) => void this.#incoming(p.call, p.alert)),\n c.on('auth.stateChanged', (state) => {\n if (state.kind !== 'signed_out' && state.kind !== 'ended') return;\n // 先清除本应用的通知,再显示会话结束的那一条\n const cleared = this.#signedOut();\n if (state.kind === 'ended') void cleared.then(() => this.#sessionEnded(state.code, state.reason));\n }),\n );\n }\n\n stop(): void {\n for (const off of this.#offs.splice(0)) off();\n for (const stop of this.#ringing.values()) stop();\n this.#ringing.clear();\n this.#ringtone.stop();\n }\n\n /** 负责提醒的是不是引擎(没有可见的界面窗口)。 */\n #engineAlerts(): boolean {\n try {\n return this.host().alertTab().self;\n } catch {\n return false;\n }\n }\n\n async #emitAlert(alert: EngineAlert): Promise<void> {\n for (const handler of [...this.alertHandlers]) {\n try {\n await handler(alert);\n } catch (err) {\n this.onWarn('App 的 onAlert 出错', err);\n }\n }\n }\n\n #text(alert: EngineAlert, fallback: { title: string; body: string }): { title: string; body: string } | null {\n const format = this.opts.notifications?.format;\n if (!format) return fallback;\n try {\n return format(alert);\n } catch (err) {\n this.onWarn('notifications.format 出错,使用默认文字', err);\n return fallback;\n }\n }\n\n /** 同一会话 3 秒内不再响;全部通知每秒最多响一次。 */\n #sound(key: string): boolean {\n if (this.opts.notifications?.sound === false) return false;\n const now = Date.now();\n const last = this.#lastSound.get(key) ?? 0;\n const allowed = now - last >= SAME_CONVERSATION_QUIET_MS && now - this.#lastAnySound >= GLOBAL_SOUND_GAP_MS;\n this.#lastSound.set(key, now);\n if (allowed) this.#lastAnySound = now;\n if (this.#lastSound.size > 500) this.#lastSound.clear();\n return allowed;\n }\n\n #attention(): void {\n if (this.opts.attention === false) return;\n void this.bridge.call('attention_request', { critical: false, label: null }).catch(() => {});\n }\n\n async #message(message: MessageView, decision: NotifyDecision | null): Promise<void> {\n if (!decision?.notify) return;\n const host = this.host();\n const alertTab = host.alertTab();\n if (this.#mode === 'manual') {\n if (alertTab.self) await this.#emitAlert({ kind: 'message', message, notify: decision });\n return;\n }\n // 正被看的会话不提醒;负责提醒的窗口正获得焦点时由它的界面提示(《2.功能概述文档》第 8.2 节)\n if (host.viewing(message.conversation_id)) return;\n if (!alertTab.self && alertTab.focused) return;\n const text = this.#text({ kind: 'message', message, notify: decision }, { title: decision.title ?? '', body: decision.body ?? '' });\n if (!text) return;\n await this.desktop\n .show({ id: `dr:msg:${message.conversation_id}`, kind: 'message', title: text.title, body: text.body, conversation_id: message.conversation_id, seq: message.seq ?? undefined, sound: this.#sound(message.conversation_id) })\n .catch((err) => this.onWarn('显示消息通知失败', err));\n this.#attention();\n }\n\n async #notice(alert: Extract<EngineAlert, { kind: 'notice' }>, id: string): Promise<void> {\n if (this.opts.notifications?.notices === false) return;\n if (this.#mode === 'manual') {\n if (this.#engineAlerts()) await this.#emitAlert(alert);\n return;\n }\n const alertTab = this.host().alertTab();\n if (!alertTab.self && alertTab.focused) return;\n const text = this.#text(alert, { title: alert.title, body: alert.body });\n if (!text) return;\n const data: Record<string, string> = {};\n if (alert.user) data.user = alert.user;\n if (alert.group_id) data.group_id = alert.group_id;\n await this.desktop.show({ id, kind: 'notice', title: text.title, body: text.body, data, sound: this.#sound(id) }).catch((err) => this.onWarn('显示通知失败', err));\n this.#attention();\n }\n\n async #friendRequests(lists: string[], unread: number): Promise<void> {\n const previous = this.#friendUnread;\n this.#friendUnread = unread;\n if (!lists.includes('received') || previous < 0 || unread <= previous) return;\n let latest: FriendRequest | undefined;\n try {\n latest = (await this.client.friends.requests.received({ status: 'pending', limit: 1 } as never)).items[0];\n } catch {\n return;\n }\n if (!latest) return;\n const name = latest.nickname || latest.username;\n await this.#notice({ kind: 'notice', type: 'friend_request', title: texts.friendRequest.title, body: texts.friendRequest.body(name, latest.message), user: latest.username }, `dr:notice:friend:${latest.username}`);\n }\n\n async #groupRequests(p: { list: string; group_id: string | null; items?: unknown[] }): Promise<void> {\n if (p.list === 'invitations' && Array.isArray(p.items)) {\n const fresh: Array<{ group_id: string; name?: string }> = [];\n const seen = new Set<string>();\n for (const item of p.items as Array<{ group_id?: string; group?: { group_id?: string; name?: string }; status?: string }>) {\n const id = item.group_id ?? item.group?.group_id;\n if (!id || (item.status && item.status !== 'pending')) continue;\n seen.add(id);\n if (!this.#invitations.has(id)) fresh.push({ group_id: id, name: item.group?.name });\n }\n const first = this.#invitations.size === 0 && fresh.length === seen.size && seen.size > 0 && !this.#primed;\n this.#invitations = seen;\n this.#primed = true;\n // 第一次加载时已有的邀请不提醒\n if (first) return;\n for (const inv of fresh) {\n const group = inv.name || texts.unnamedGroup;\n await this.#notice({ kind: 'notice', type: 'group_request', title: texts.groupInvitation.title, body: texts.groupInvitation.body(group), group_id: inv.group_id }, `dr:notice:group:${inv.group_id}`);\n }\n return;\n }\n if (p.list === 'pending' && p.group_id) {\n const group = (await this.client.groups.get(p.group_id).catch(() => undefined)) as { name?: string } | undefined;\n await this.#notice({ kind: 'notice', type: 'group_request', title: texts.groupApplication.title, body: texts.groupApplication.body(group?.name || texts.unnamedGroup), group_id: p.group_id }, `dr:notice:group:${p.group_id}`);\n }\n }\n\n #primed = false;\n\n async #incoming(call: CallHandle, alert: boolean): Promise<void> {\n // 有可见的界面窗口时由那个窗口的 App 代码处理\n if (!alert) return;\n if ((this.opts.notifications?.calls ?? 'auto') === 'manual') {\n for (const handler of [...this.callHandlers]) {\n try {\n await handler(call);\n } catch (err) {\n this.onWarn('App 的 onIncomingCall 出错', err);\n }\n }\n return;\n }\n const id = `dr:call:${call.call_id}`;\n if (this.#ringing.has(id)) return;\n const snapshot = call.getSnapshot();\n const inviter = snapshot.inviter?.nickname || snapshot.inviter?.username || (snapshot.call.initiator ? displayName(this.client, snapshot.call.initiator) : '');\n const title = snapshot.group?.name ? `${inviter}(${snapshot.group.name})` : inviter;\n await this.desktop\n .show({\n id,\n kind: 'call',\n title: texts.incomingCall.title(title),\n body: texts.incomingCall.body(snapshot.call.media === 'video'),\n data: { call_id: call.call_id },\n sound: false,\n actions: [\n { id: 'accept', title: texts.incomingCall.accept },\n { id: 'decline', title: texts.incomingCall.decline },\n ],\n })\n .catch((err) => this.onWarn('显示来电通知失败', err));\n this.#ringtone.start();\n this.#attention();\n // 来电被接听、拒绝、取消,或在本人的其他设备上处理后,清除通知并停止铃声(《2.功能概述文档》第 8.3 节)\n const stop = () => {\n unsubscribe();\n this.#ringing.delete(id);\n void this.desktop.clear(id).catch(() => {});\n if (this.#ringing.size === 0) this.#ringtone.stop();\n };\n const unsubscribe = call.subscribe(() => {\n const s = call.getSnapshot();\n if (!s.ringing || s.call.status === 'ended' || s.self?.state !== 'ringing') stop();\n });\n this.#ringing.set(id, stop);\n }\n\n /** 来电通知上的“拒绝”(插件直接交给引擎)。 */\n async declineCall(callId: string): Promise<void> {\n const call = this.client.calls.incoming.find((c) => c.call_id === callId);\n if (!call) return;\n await call.reject().catch((err) => this.onWarn('拒绝来电失败', err));\n }\n\n async #sessionEnded(code: string, reason: string | undefined): Promise<void> {\n if (this.#mode !== 'auto' || !this.#engineAlerts()) return;\n // 在其他设备登录、超过设备数被挤下线、被本人在其他设备上踢下线(《sdk-web/2.功能概述文档》第 3.5 节的吊销原因)\n const replaced = code === 'session_revoked' && (reason === 'replaced' || reason === 'device_limit' || reason === 'kicked');\n const t = replaced ? texts.sessionEnded.replaced : texts.sessionEnded.expired;\n await this.desktop.show({ id: 'dr:session', kind: 'session', title: t.title, body: t.body }).catch((err) => this.onWarn('显示通知失败', err));\n }\n\n /** 退出登录、会话结束:清除本应用已显示的通知,停止铃声(与 Flutter SDK 相同)。 */\n #signedOut(): Promise<void> {\n for (const stop of [...this.#ringing.values()]) stop();\n this.#friendUnread = -1;\n this.#invitations.clear();\n this.#primed = false;\n return this.bridge.call<void>('notify_clear', { all: true }).catch(() => {});\n }\n\n /** 会话标题(App 的 format 中可以使用)。 */\n title(view: Parameters<typeof conversationTitle>[1]): string {\n return conversationTitle(this.client, view);\n }\n}\n","import type { DRClient } from '@deeprespond/im-web';\nimport type { PlatformHost } from '@deeprespond/im-web/platform';\nimport type { Bridge } from '../adapter/ipc';\n\nconst MERGE_MS = 500;\n\n/**\n * 角标与未读(《2.功能概述文档》第 8.4 节):引擎在未读合计变化后 500 毫秒内合并一次,\n * auto 模式下显示在系统的位置,并向 Rust(on_unread_changed)和各窗口(desktop.unreadChanged)报告。\n */\nexport class UnreadBadge {\n #timer: ReturnType<typeof setTimeout> | null = null;\n #last: { total: number; badge: number } | null = null;\n #offs: Array<() => void> = [];\n\n constructor(\n private readonly bridge: Bridge,\n private readonly client: DRClient,\n private readonly host: () => PlatformHost,\n private readonly opts: { auto: boolean; includeMuted: boolean; onWarn: (message: string, err?: unknown) => void },\n ) {}\n\n start(): void {\n this.#offs.push(\n this.client.on('conversations.unreadChanged', () => this.schedule()),\n this.client.on('push.changed', () => this.schedule()),\n this.client.on('auth.stateChanged', (state) => {\n if (state.kind !== 'signed_in' && state.kind !== 'suspended') this.#apply({ total: 0, badge: 0 });\n else this.schedule();\n }),\n );\n this.schedule();\n }\n\n stop(): void {\n for (const off of this.#offs.splice(0)) off();\n if (this.#timer !== null) clearTimeout(this.#timer);\n this.#timer = null;\n }\n\n schedule(): void {\n if (this.#timer !== null) return;\n this.#timer = setTimeout(() => {\n this.#timer = null;\n let totals: { total: number; unmuted: number };\n try {\n totals = this.host().unreadTotals();\n } catch {\n return;\n }\n const signedIn = this.client.auth.state.kind === 'signed_in' || this.client.auth.state.kind === 'suspended';\n const total = signedIn ? totals.total : 0;\n const badge = signedIn ? (this.opts.includeMuted ? totals.total : totals.unmuted) : 0;\n this.#apply({ total, badge });\n }, MERGE_MS);\n }\n\n #apply(next: { total: number; badge: number }): void {\n if (this.#last && this.#last.total === next.total && this.#last.badge === next.badge) return;\n this.#last = next;\n if (this.opts.auto) void this.bridge.call('badge_set', { count: next.badge > 0 ? next.badge : null }).catch((err) => this.opts.onWarn('设置角标失败', err));\n void this.bridge.call('unread_report', next).catch(() => {});\n try {\n this.host().publish('desktop.unreadChanged', next);\n } catch {\n // 客户端已关闭\n }\n }\n}\n","import { DRError, createDRClient, type CallHandle, type DRClient } from '@deeprespond/im-web';\nimport type { PlatformHost } from '@deeprespond/im-web/platform';\nimport { createTauriPlatform, osFromUserAgent, type TauriPlatform } from './adapter';\nimport { Bridge, ENGINE_LABEL, currentLabel } from './adapter/ipc';\nimport { clickEvent, invalidState, makeLogger, unsupported } from './common';\nimport { EngineAlerts } from './desktop/alerts';\nimport { UnreadBadge } from './desktop/badge';\nimport { Desktop } from './desktop/module';\nimport type { DREngine, DRTauriEngineClient, DRTauriEngineOptions, EngineAlert } from './types';\n\nexport type { DREngine, DRTauriEngineClient, DRTauriEngineOptions, EngineAlert, DesktopModule, DesktopEvents } from './types';\n\nconst ALIVE_MS = 3000;\nconst PROBE_TIMEOUT_MS = 5000;\nconst WAKE_LAG_MS = 5000;\nconst DEFAULT_PERSIST_MAX = 200 * 1024 * 1024;\n\ninterface EngineEntry {\n client: DRClient;\n host: () => PlatformHost;\n alerts: EngineAlerts;\n badge: UnreadBadge;\n platform: TauriPlatform;\n}\n\n/** 引擎页中的引擎客户端(每个 AppKey 一个,《3.接口设计》第 3 节),按页面的插件连接分开(测试中一个进程有多个页面)。 */\nconst pages = new WeakMap<Bridge, Map<string, EngineEntry>>();\n\n/** 测试用:指定页面的插件连接(不对 App 公开)。 */\nexport interface PageEnv {\n bridge: Bridge;\n /** 替换平台的部分成员(测试中的假服务端)。 */\n overrides?: Partial<import('@deeprespond/im-web/platform').PlatformAdapter>;\n /** 取得核心接口(测试中模拟事件)。 */\n onHost?: (host: PlatformHost) => void;\n}\n\n/**\n * 创建引擎客户端(引擎页中调用,《3.接口设计》第 3 节):Web SDK 的核心作为固定的“主标签页”,持有长连接和令牌、写本地存储、\n * 处理界面窗口转来的调用;没有界面窗口时负责提醒和来电。\n */\nexport function createDRTauriEngine(options: DRTauriEngineOptions, env?: PageEnv): DREngine {\n if ((env ? env.bridge.label : currentLabel()) !== ENGINE_LABEL) throw unsupported('not_engine_window', 'createDRTauriEngine 只能在引擎页中调用');\n if (!options || typeof options.appKey !== 'string') throw new DRError({ code: 'local_validation', message: 'appKey', details: { reason: 'required', field: 'appKey' }, source: 'local' });\n const bridge = env?.bridge ?? Bridge.get();\n let engines = pages.get(bridge);\n if (!engines) {\n engines = new Map();\n pages.set(bridge, engines);\n wirePage(bridge, engines);\n }\n const logger = makeLogger(bridge, options.logger);\n const warn = (message: string, err?: unknown) =>\n logger.sink({ level: 'warn', time: Date.now(), module: 'tauri', message, code: err instanceof DRError ? err.code : undefined, reason: err instanceof DRError ? err.reason : undefined });\n let client!: DRClient;\n const desktop = new Desktop(bridge, () => client);\n const tp = createTauriPlatform({\n role: 'leader',\n appKey: options.appKey,\n bridge,\n encrypt: options.encryptDatabase === true,\n onWarn: warn,\n extend: (h) => {\n env?.onHost?.(h);\n return { desktop: { value: desktop, enumerable: true } };\n },\n });\n if (env?.overrides) Object.assign(tp.platform, env.overrides);\n // 读入保存的会话之后客户端才读取登录状态;另一个进程正在使用同一份数据时不创建(《2.功能概述文档》第 4.6 节)\n tp.platform.ready = bridge.ready.then(async (info) => {\n if (info.another_instance) throw invalidState('another_instance');\n await tp.engineKv!.load();\n });\n client = createDRClient({\n appKey: options.appKey,\n apiUrl: options.apiUrl,\n wsUrl: options.wsUrl,\n platform: osFromUserAgent(typeof navigator !== 'undefined' ? navigator.userAgent : ''),\n deviceName: options.deviceName,\n appVersion: options.appVersion,\n ticketProvider: options.ticketProvider,\n autoMarkRead: options.autoMarkRead,\n conversationWindow: options.conversationWindow,\n userCacheTtlMs: options.userCacheTtlMs,\n outbox: { maxAgeHours: options.outbox?.maxAgeHours, persistAttachmentMaxBytes: options.outbox?.persistAttachmentMaxBytes ?? DEFAULT_PERSIST_MAX },\n media: options.media,\n logger,\n platformAdapter: tp.platform,\n });\n const host = tp.host;\n const alerts = new EngineAlerts(bridge, client, desktop, host, options, warn);\n const badge = new UnreadBadge(bridge, client, host, { auto: (options.badge ?? 'auto') === 'auto', includeMuted: options.badgeIncludesMuted === true, onWarn: warn });\n const entry: EngineEntry = { client, host, alerts, badge, platform: tp };\n // 同一 AppKey 已有客户端时 createDRClient 以 invalid_state(client_exists)拒绝;destroy 之后再创建的替换旧的\n engines.get(options.appKey)?.alerts.stop();\n engines.get(options.appKey)?.badge.stop();\n engines.set(options.appKey, entry);\n void tp.platform.ready!.then(\n () => {\n const signedIn = () => client.auth.state.kind === 'signed_in' || client.auth.state.kind === 'suspended';\n const report = () => void bridge.call('engine_ready', { app_key: options.appKey, signed_in: signedIn() }).catch((err) => warn('报告引擎就绪失败', err));\n report();\n client.on('auth.stateChanged', report);\n alerts.start();\n badge.start();\n },\n (err) => warn('引擎客户端没有创建', err),\n );\n return Object.freeze({\n client: client as unknown as DRTauriEngineClient,\n onAlert: (handler: (alert: EngineAlert) => void | Promise<void>) => {\n alerts.alertHandlers.add(handler);\n return () => alerts.alertHandlers.delete(handler);\n },\n onIncomingCall: (handler: (call: CallHandle) => void | Promise<void>) => {\n alerts.callHandlers.add(handler);\n return () => alerts.callHandlers.delete(handler);\n },\n });\n}\n\n/** 引擎页一次性的接线:存活报告、唤醒检查、系统事件、退出收尾、通知点击。 */\nfunction wirePage(bridge: Bridge, engines: Map<string, EngineEntry>): void {\n const each = (fn: (e: EngineEntry) => void) => {\n for (const e of engines.values()) {\n try {\n fn(e);\n } catch {\n // 客户端已关闭\n }\n }\n };\n setInterval(() => void bridge.call('engine_alive').catch(() => {}), ALIVE_MS);\n // 唤醒检查(《2.功能概述文档》第 4.5 节):心跳计划落后 5 秒以上的立即补发并记警告\n bridge.on('wake', () =>\n each((e) => {\n const host = e.host();\n const lag = host.heartbeatLag();\n if (lag > WAKE_LAG_MS) {\n host.log('warn', 'tauri', `唤醒检查落后 ${Math.round(lag)} 毫秒,计时器可能被系统节流`);\n host.checkConnection('wake_check', PROBE_TIMEOUT_MS);\n }\n }),\n );\n // 系统唤醒、网络变化后立即检查连接(《2.功能概述文档》第 7 节)\n bridge.on('resume', (event) =>\n each((e) => {\n const host = e.host();\n host.checkConnection('resume', PROBE_TIMEOUT_MS);\n host.publish('desktop.systemResumed', { slept_ms: Number(event.slept_ms) || 0 });\n }),\n );\n bridge.on('network', (event) =>\n each((e) => {\n const host = e.host();\n if (event.online !== false) host.checkConnection('network', PROBE_TIMEOUT_MS);\n host.publish('desktop.networkChanged', { online: event.online !== false });\n }),\n );\n bridge.on('screen_lock', () => {\n // 锁屏只影响“正被看”:界面窗口自己重新报告可见性\n });\n bridge.on('storage_full', (event) => {\n const e = engines.get(String(event.app_key));\n e?.host().publish('storage.full', { freed_users: Number(event.freed_users) || 0 });\n });\n bridge.on('call_action', (event) => {\n if (event.action !== 'decline') return;\n each((e) => void e.alerts.declineCall(String(event.call_id)));\n });\n bridge.on('notification_click', (event) => {\n const payload = clickEvent(bridge, event);\n each((e) => e.host().emit('desktop.notificationClicked', payload));\n });\n // 退出前的收尾(《2.功能概述文档》第 4.3 节):停止接收调用、挂断通话、正常关闭长连接、关闭数据库,最多 2 秒\n bridge.on('exit', () => {\n const work = [...engines.values()].map(async (e) => {\n e.alerts.stop();\n e.badge.stop();\n await e.platform.engineKv?.flush();\n await e.host().shutdown();\n });\n void Promise.allSettled(work).then(() => bridge.call('engine_exit_done').catch(() => {}));\n });\n}\n"]}