dsh-remote-control 1.0.0 → 1.1.0

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.
Files changed (100) hide show
  1. package/cordis.patch.yml +6 -0
  2. package/dist/bundle/client.cjs +146 -0
  3. package/dist/bundle/index.js +667 -264
  4. package/package.json +13 -6
  5. package/dist/src/core/clock.d.ts +0 -45
  6. package/dist/src/core/clock.js +0 -83
  7. package/dist/src/core/clock.js.map +0 -1
  8. package/dist/src/core/keys.d.ts +0 -98
  9. package/dist/src/core/keys.js +0 -176
  10. package/dist/src/core/keys.js.map +0 -1
  11. package/dist/src/core/pairing-window.d.ts +0 -95
  12. package/dist/src/core/pairing-window.js +0 -92
  13. package/dist/src/core/pairing-window.js.map +0 -1
  14. package/dist/src/core/runtime.d.ts +0 -104
  15. package/dist/src/core/runtime.js +0 -491
  16. package/dist/src/core/runtime.js.map +0 -1
  17. package/dist/src/core/sleep-policy.d.ts +0 -81
  18. package/dist/src/core/sleep-policy.js +0 -134
  19. package/dist/src/core/sleep-policy.js.map +0 -1
  20. package/dist/src/core/window.d.ts +0 -88
  21. package/dist/src/core/window.js +0 -185
  22. package/dist/src/core/window.js.map +0 -1
  23. package/dist/src/index.d.ts +0 -70
  24. package/dist/src/index.js +0 -882
  25. package/dist/src/index.js.map +0 -1
  26. package/dist/src/platform/carrier-services.d.ts +0 -96
  27. package/dist/src/platform/carrier-services.js +0 -982
  28. package/dist/src/platform/carrier-services.js.map +0 -1
  29. package/dist/src/platform/guard.d.ts +0 -60
  30. package/dist/src/platform/guard.js +0 -124
  31. package/dist/src/platform/guard.js.map +0 -1
  32. package/dist/src/platform/mock-kernel.d.ts +0 -95
  33. package/dist/src/platform/mock-kernel.js +0 -180
  34. package/dist/src/platform/mock-kernel.js.map +0 -1
  35. package/dist/src/platform/qr.d.ts +0 -28
  36. package/dist/src/platform/qr.js +0 -80
  37. package/dist/src/platform/qr.js.map +0 -1
  38. package/dist/src/platform/sleep-posix.d.ts +0 -17
  39. package/dist/src/platform/sleep-posix.js +0 -91
  40. package/dist/src/platform/sleep-posix.js.map +0 -1
  41. package/dist/src/ports/index.d.ts +0 -246
  42. package/dist/src/ports/index.js +0 -2
  43. package/dist/src/ports/index.js.map +0 -1
  44. package/dist/src/shell/config.d.ts +0 -56
  45. package/dist/src/shell/config.js +0 -212
  46. package/dist/src/shell/config.js.map +0 -1
  47. package/dist/src/shell/pairing-text.d.ts +0 -38
  48. package/dist/src/shell/pairing-text.js +0 -22
  49. package/dist/src/shell/pairing-text.js.map +0 -1
  50. package/dist/src/shell/status.d.ts +0 -39
  51. package/dist/src/shell/status.js +0 -119
  52. package/dist/src/shell/status.js.map +0 -1
  53. package/dist/src/transport/relay.d.ts +0 -107
  54. package/dist/src/transport/relay.js +0 -359
  55. package/dist/src/transport/relay.js.map +0 -1
  56. package/dist/tests/carrier-events.test.d.ts +0 -1
  57. package/dist/tests/carrier-events.test.js +0 -88
  58. package/dist/tests/carrier-events.test.js.map +0 -1
  59. package/dist/tests/carrier-services.test.d.ts +0 -1
  60. package/dist/tests/carrier-services.test.js +0 -562
  61. package/dist/tests/carrier-services.test.js.map +0 -1
  62. package/dist/tests/carrier-vocabulary.test.d.ts +0 -1
  63. package/dist/tests/carrier-vocabulary.test.js +0 -149
  64. package/dist/tests/carrier-vocabulary.test.js.map +0 -1
  65. package/dist/tests/clock-probe-timers.test.d.ts +0 -1
  66. package/dist/tests/clock-probe-timers.test.js +0 -73
  67. package/dist/tests/clock-probe-timers.test.js.map +0 -1
  68. package/dist/tests/command-result.test.d.ts +0 -1
  69. package/dist/tests/command-result.test.js +0 -185
  70. package/dist/tests/command-result.test.js.map +0 -1
  71. package/dist/tests/config.test.d.ts +0 -1
  72. package/dist/tests/config.test.js +0 -223
  73. package/dist/tests/config.test.js.map +0 -1
  74. package/dist/tests/guard.test.d.ts +0 -1
  75. package/dist/tests/guard.test.js +0 -175
  76. package/dist/tests/guard.test.js.map +0 -1
  77. package/dist/tests/ids-and-keys.test.d.ts +0 -1
  78. package/dist/tests/ids-and-keys.test.js +0 -211
  79. package/dist/tests/ids-and-keys.test.js.map +0 -1
  80. package/dist/tests/pairing-text.test.d.ts +0 -1
  81. package/dist/tests/pairing-text.test.js +0 -62
  82. package/dist/tests/pairing-text.test.js.map +0 -1
  83. package/dist/tests/pairing-window.test.d.ts +0 -1
  84. package/dist/tests/pairing-window.test.js +0 -165
  85. package/dist/tests/pairing-window.test.js.map +0 -1
  86. package/dist/tests/relay-client.test.d.ts +0 -1
  87. package/dist/tests/relay-client.test.js +0 -557
  88. package/dist/tests/relay-client.test.js.map +0 -1
  89. package/dist/tests/runtime.test.d.ts +0 -1
  90. package/dist/tests/runtime.test.js +0 -963
  91. package/dist/tests/runtime.test.js.map +0 -1
  92. package/dist/tests/sleep.test.d.ts +0 -1
  93. package/dist/tests/sleep.test.js +0 -264
  94. package/dist/tests/sleep.test.js.map +0 -1
  95. package/dist/tests/status.test.d.ts +0 -1
  96. package/dist/tests/status.test.js +0 -238
  97. package/dist/tests/status.test.js.map +0 -1
  98. package/dist/tests/window.test.d.ts +0 -1
  99. package/dist/tests/window.test.js +0 -210
  100. package/dist/tests/window.test.js.map +0 -1
@@ -1,211 +0,0 @@
1
- /**
2
- * ids-and-keys.test — `core/keys.ts`:PairingSlots(待配对码槽)与 ConversationBook(会话密钥簿)。
3
- *
4
- * 这两本账是"零知识"能落地的唯一原因:PSK 只在主机内存里,中继只见 6 位码。
5
- * 一旦簿记出错,故障形状永远是同一句话——**配对显示成功,但每一帧都解不开**:
6
- *
7
- * - 取错码(用了"最新那张"而不是"手机实际用那张):旧实现的线上事故
8
- * (取证 docs/legacy-spec/relay-and-wireformat.md §2.3、HANDOFF.md §4.5、
9
- * `git show 6e33017`;`docs/DESIGN.md §2.1` 的 B5 要求两把方向密钥必须不同)。
10
- * - 提前清掉 PSK:`peer-joined` 找不到密钥 → 静默丢 peer → 手机列表永远空白(§2.3 硬约束 1)。
11
- * - 表只有单槽:自动补发的码与手工 `/drc pair` 的码互相挤掉。
12
- * - 配对码/PSK 的**形状**也是契约:小程序只接受 `/^\d{6}$/` 的码(红线 21),
13
- * 并在 `paired` 之后同步 base64 解码 PSK(红线 22:非法 base64 会让它卡在"正在配对…")。
14
- */
15
- import { test } from 'node:test';
16
- import assert from 'node:assert/strict';
17
- import { base64Text, derivePskKey, open, seal } from 'dsh-remote-wire';
18
- import { ConversationBook, PairingSlots } from '../src/core/keys.js';
19
- /** 可推进的假时钟:过期判定必须完全确定,不能靠 sleep(1ms)。 */
20
- function ticking(start = 1_700_000_000_000) {
21
- let at = start;
22
- return {
23
- now: () => at,
24
- advance: (ms) => {
25
- at += ms;
26
- },
27
- };
28
- }
29
- test('一条配对建出两把不同的方向密钥:主机用 h2c 密封、用 c2h 打开(B5)', () => {
30
- const book = new ConversationBook();
31
- const slot = new PairingSlots(ticking().now).create(120_000);
32
- const conversation = book.open({ id: 'c_aaaabbbbcccc', psk: slot.psk, generation: 1, now: 1_700_000_000_000 });
33
- assert.equal(conversation.kC2H.length, 32, '会话密钥必须是 32 字节:seal 会直接抛(KEY_BYTES 校验)');
34
- assert.equal(conversation.kH2C.length, 32, '两个方向的密钥都必须是 32 字节');
35
- assert.equal(conversation.kC2H.some((byte, index) => byte !== conversation.kH2C[index]), true, '两把方向密钥相同 → 客户端可以拿主机的发送密钥反过来解下行,方向分离白做');
36
- assert.deepEqual([...conversation.kC2H], [...derivePskKey(slot.psk, 'c2h', 'c_aaaabbbbcccc')], 'kC2H 必须与协议层派生逐字节一致:差一个字节手机端就解不开');
37
- assert.deepEqual([...conversation.kH2C], [...derivePskKey(slot.psk, 'h2c', 'c_aaaabbbbcccc')], 'kH2C 必须与协议层派生逐字节一致');
38
- // 镜像侧核对:主机密封的下行,手机用同一 PSK 派生的 h2c 密钥必须解得开。
39
- const record = seal(conversation.kH2C, { t: 'ev.run_state', state: 'idle' });
40
- assert.equal(open(derivePskKey('AgAAAAAAAAAAAAAAAAAAAA==', 'h2c', 'c_aaaabbbbcccc'), { ciphertext: record.ciphertext }), null, '换一把 PSK 派生的密钥必须解不开(B3 的失败形状:返回 null 而不是抛)');
41
- assert.equal(open(derivePskKey(slot.psk, 'h2c', 'c_aaaabbbbcccc'), { ciphertext: record.ciphertext }) !== null, true, '手机按冻结 KDF 派生的密钥解不开主机的下行 → "配对成功但每一帧都解不开"');
42
- });
43
- test('同一把 PSK 在不同 convId 下必须派生不同密钥(convId 参与上下文绑定)', () => {
44
- const psk = new PairingSlots(ticking().now).create(120_000).psk;
45
- const one = derivePskKey(psk, 'h2c', 'c_000000000001');
46
- const two = derivePskKey(psk, 'h2c', 'c_000000000002');
47
- assert.equal(one.some((byte, index) => byte !== two[index]), true, 'convId 没参与派生:两条配对通道的流量可以互解,配对之间不再互相隔离');
48
- });
49
- test('closeBefore(generation) 只清掉旧代会话:中继重启不能顺手把新代的一起删了', () => {
50
- const book = new ConversationBook();
51
- book.open({ id: 'c_old_1', psk: 'AAAAAAAAAAAAAAAAAAAAAA==', generation: 1, now: 1 });
52
- book.open({ id: 'c_old_2', psk: 'AAAAAAAAAAAAAAAAAAAAAA==', generation: 1, now: 2 });
53
- book.open({ id: 'c_new_1', psk: 'AAAAAAAAAAAAAAAAAAAAAA==', generation: 2, now: 3 });
54
- const dropped = book.closeBefore(2);
55
- assert.deepEqual(dropped.sort(), ['c_old_1', 'c_old_2'], `清掉的是 ${JSON.stringify(dropped)}:旧代会话必须全部作废(中继重启后没人路由它们)`);
56
- assert.equal(book.size, 1, '新代会话被一起删了 → 刚配好的手机被踢下线,要重新扫码');
57
- assert.deepEqual(book.ids(), ['c_new_1'], '留下的必须恰好是当前代的那一条');
58
- // 幂等:同一条代次再清一次不该再报任何东西。
59
- assert.deepEqual(book.closeBefore(2), [], '重复清理又报了一遍:上层的"配对失效"提示会被刷屏');
60
- assert.deepEqual(book.closeBefore(1), [], '更早的代次不该清掉当前会话');
61
- assert.equal(book.size, 1, 'closeBefore 不该动当代会话');
62
- });
63
- test('close/has/get 对不存在的 id 都是安全的空操作:解配与断连路径不能抛', () => {
64
- const book = new ConversationBook();
65
- assert.equal(book.close('c_none'), false, 'close 不存在的会话返回了 true:调用方会以为真的掉了一条');
66
- assert.equal(book.has('c_none'), false);
67
- assert.equal(book.get('c_none'), undefined);
68
- assert.equal(book.size, 0);
69
- assert.deepEqual(book.ids(), []);
70
- book.open({ id: 'c_real', psk: 'AAAAAAAAAAAAAAAAAAAAAA==', generation: 1, now: 10 });
71
- assert.equal(book.close('c_real'), true, 'close 存在的会话必须返回 true');
72
- assert.equal(book.has('c_real'), false, 'close 之后 has 仍为真:hasPeer 会谎报有对端,审批卡发出去没人收');
73
- assert.equal(book.size, 0, '会话数没降下来');
74
- });
75
- test('同一条 convId 重复 open 是覆盖而不是并出两条:簿记键只能是 convId', () => {
76
- const book = new ConversationBook();
77
- const first = book.open({ id: 'c_dup', psk: 'AAAAAAAAAAAAAAAAAAAAAA==', generation: 1, now: 1 });
78
- const second = book.open({ id: 'c_dup', psk: 'AgAAAAAAAAAAAAAAAAAAAA==', generation: 1, now: 2 });
79
- assert.equal(book.size, 1, `一条 convId 攒出 ${book.size} 条会话:广播会给同一条通道发两遍`);
80
- assert.notEqual(first.psk, second.psk, '夹具自检:两次 PSK 不同');
81
- assert.equal(book.get('c_dup')?.psk, second.psk, '后来的那张码没覆盖:手机用新码配对,主机还攥着旧密钥');
82
- assert.equal(book.get('c_dup')?.createdAt, 2, 'createdAt 要跟着更新:status.json 的码年龄判据(<25s)读它');
83
- });
84
- test('PairingSlots 超上限时淘汰最旧那张而不是拒绝新建:发不出去比少一张更糟', () => {
85
- const clock = ticking();
86
- const slots = new PairingSlots(clock.now, 3);
87
- const created = [];
88
- for (let i = 0; i < 3; i++) {
89
- created.push(slots.create(60_000));
90
- clock.advance(1_000);
91
- }
92
- assert.equal(slots.size, 3, '夹具自检:三张码都该在表里');
93
- const fourth = slots.create(60_000);
94
- assert.equal(slots.size, 3, `超上限后表长到了 ${slots.size}:无界内存(旧实现靠运维狂敲 /drc pair 撑爆过这张表)`);
95
- assert.equal(slots.resolveFor(fourth.token)?.token, fourth.token, '新建的那张被拒了 → 用户扫出来的码主机根本不认,配对永远失败');
96
- const [oldest, middle, newest] = created;
97
- assert.equal(slots.resolveFor(oldest.token), null, `被淘汰的不是最旧那张(最旧=${oldest.token}):Map 的插入序不可靠,必须按 createdAt 排`);
98
- assert.equal(slots.resolveFor(middle.token) !== null, true, '只该淘汰一张:把第二张也挤掉会误杀正在配对的用户');
99
- assert.equal(slots.resolveFor(newest.token) !== null, true, '最新的两张都必须还在');
100
- });
101
- test('resolveFor 过期返回 null:过期就是过期,调用方必须拒绝这条配对而不是猜', () => {
102
- const clock = ticking();
103
- const slots = new PairingSlots(clock.now);
104
- const slot = slots.create(10_000);
105
- assert.equal(slots.resolveFor(slot.token)?.psk, slot.psk, '未到期的码必须取得到 PSK');
106
- clock.advance(10_001);
107
- assert.equal(slots.resolveFor(slot.token), null, '过期的码仍取得到 PSK:本地表会无限膨胀,且中继早就把它扫掉了');
108
- assert.equal(slots.resolveFor(undefined), null, '没有 token 时必须返回 null:退化成"取最新一张"就是那起多码事故');
109
- assert.equal(slots.resolveFor(''), null, '空串同样不许退化成"取最新一张"');
110
- assert.equal(slots.resolveFor('000000'), null, '本地没有这张码就报 null,让调用方拒绝配对');
111
- });
112
- test('applyServerTtl 用服务端权威 TTL 改写本地过期;未知 token 返回 false 并留下痕迹', () => {
113
- const clock = ticking();
114
- const slots = new PairingSlots(clock.now);
115
- const slot = slots.create(1_000); // 本地只给 1 秒
116
- clock.advance(2_000);
117
- assert.equal(slots.resolveFor(slot.token), null, '夹具自检:本地已经判它过期');
118
- assert.equal(slots.applyServerTtl(slot.token, 120_000), true, '服务端 TTL 没能落到这张码上');
119
- assert.equal(slots.resolveFor(slot.token)?.expiresAt, clock.now() + 120_000, '过期时间没按服务端值改写(基线是 pair-ready 到达时刻)');
120
- assert.equal(slots.applyServerTtl('999999', 120_000), false, '为一张本地没有的码改写 TTL 必须返回 false:那是"配对流程出现了第二份真相"的信号');
121
- });
122
- test('prune 只清过期项并如实报数:清多了会误杀正在配对的码', () => {
123
- const clock = ticking();
124
- const slots = new PairingSlots(clock.now);
125
- const short = slots.create(1_000);
126
- clock.advance(500);
127
- const long = slots.create(120_000);
128
- clock.advance(600); // short 已过期(1100>1000),long 还剩
129
- const removed = slots.prune();
130
- assert.equal(removed, 1, `清掉了 ${removed} 张:应为 1 张`);
131
- assert.equal(slots.resolveFor(short.token), null, '过期的那张必须已经没了');
132
- assert.equal(slots.resolveFor(long.token) !== null, true, '未到期的那张被顺手清了:用户刚扫出来的码变成"无效或已过期"');
133
- assert.equal(slots.prune(), 0, '第二次 prune 不该再报东西');
134
- });
135
- test('latest() 只用于展示,取密钥必须走 resolveFor:用 A 码建的会话拿 B 码的 PSK 一定解不开', () => {
136
- const clock = ticking();
137
- const slots = new PairingSlots(clock.now);
138
- const usedByPhone = slots.create(120_000); // 屏幕上那张、手机真正扫的
139
- clock.advance(1_000);
140
- const newer = slots.create(120_000); // 自动补发的另一张
141
- const shown = slots.latest();
142
- assert.equal(shown?.token, newer.token, '夹具自检:latest() 给出的是最新那张');
143
- assert.equal(shown?.token !== usedByPhone.token, true, '夹具自检:两张不同');
144
- // 事故形状:会话是用 A 建的,却按 latest()(B)的 PSK 派生密钥。
145
- const book = new ConversationBook();
146
- const picked = slots.resolveFor(usedByPhone.token);
147
- assert.ok(picked !== null, '按手机实际用的那张码必须取到 PSK');
148
- const correct = book.open({ id: 'c_pair', psk: picked.psk, generation: 1, now: clock.now() });
149
- assert.ok(shown !== null, '夹具自检:展示位上有码');
150
- const mistaken = book.open({ id: 'c_wrong', psk: shown.psk, generation: 1, now: clock.now() });
151
- const record = seal(correct.kH2C, { t: 'ev.session_changed', sessions: [] });
152
- assert.equal(open(derivePskKey(newer.psk, 'h2c', 'c_wrong'), { ciphertext: record.ciphertext }), null, '取错码反而解得开:说明密钥没有真正绑定到所用那张码');
153
- assert.equal(open(mistaken.kH2C, { ciphertext: record.ciphertext }), null, '取错码的结果必须是解不开(这条断言就是那次线上事故的可执行反面)');
154
- assert.equal(open(correct.kH2C, { ciphertext: record.ciphertext }) !== null, true, '夹具自检:按所用那张码取的密钥必须解得开');
155
- });
156
- test('配对码必须是 6 位数字:小程序只接受 /^\\d{6}$/,否则扫出来是"无法识别"', () => {
157
- const slots = new PairingSlots(ticking().now);
158
- for (let i = 0; i < 50; i++) {
159
- const token = slots.create(60_000).token;
160
- assert.match(token, /^\d{6}$/, `生成的配对码 ${JSON.stringify(token)} 不符合手机侧的归一化判据:这条码扫了没反应`);
161
- assert.equal(token.length, 6, '位数变了要同时动三处(中继校验、主机生成、小程序归一化),而小程序改不了');
162
- }
163
- });
164
- test('PSK 必须是 16 字节的标准 base64(24 字符):非法 base64 会让小程序卡在"正在配对…"', () => {
165
- const slots = new PairingSlots(ticking().now);
166
- for (let i = 0; i < 20; i++) {
167
- const psk = slots.create(60_000).psk;
168
- assert.equal(psk.length, 24, 'PSK 的 base64 文本必须是 24 字符(B7:16 字节 → 标准带 padding)');
169
- assert.equal(base64Text.safeParse(psk).success, true, `PSK ${psk} 不是标准 base64:小程序 _onPaired 里同步解码会抛异常且不在 try 内`);
170
- assert.equal(Buffer.from(psk, 'base64').length, 16, '解码后必须是 16 字节');
171
- }
172
- });
173
- test('两张码的 PSK 必须互不相同:PSK 每次配对轮换、单次有效(B7)', () => {
174
- const slots = new PairingSlots(ticking().now);
175
- const psks = new Set();
176
- const tokens = new Set();
177
- for (let i = 0; i < 100; i++) {
178
- const slot = slots.create(60_000);
179
- psks.add(slot.psk);
180
- tokens.add(slot.token);
181
- }
182
- assert.equal(tokens.size, 100, `${100 - tokens.size} 次生成的配对码与已有的重复:表按 token 索引,撞码会静默覆盖前一张的 PSK → 偶发的"扫了配不上"`);
183
- assert.equal(psks.size, 100, 'PSK 出现重复:两张码共享密钥,一次性作废失去意义');
184
- });
185
- test('ConversationBook 的初始簿记形状:seqHost 从 0 起、clientIds 空、代次与时刻如实记下', () => {
186
- const book = new ConversationBook();
187
- const conversation = book.open({
188
- id: 'c_shape',
189
- psk: 'AAAAAAAAAAAAAAAAAAAAAA==',
190
- generation: 4,
191
- now: 1_700_000_999_000,
192
- });
193
- assert.equal(conversation.id, 'c_shape', '会话 id 必须逐字保留(F3:加工过一次就路由不回去)');
194
- assert.equal(conversation.seqHost, 0, '本端出站序号从 0 开始,第一帧发出去是 1');
195
- assert.equal(conversation.clientIds.size, 0, '新配对不该凭空带上客户端名单(D4 的成员校验用它)');
196
- assert.equal(conversation.generation, 4, '代次没记下来:中继重启后 closeBefore 找不出该清哪些会话');
197
- assert.equal(conversation.createdAt, 1_700_000_999_000, 'createdAt 必须是传入时刻而不是 Date.now():假时钟下要可核对');
198
- });
199
- test('过期判据的边界:expiresAt 恰好等于此刻时仍算有效(与 resolveFor 的严格小于一致)', () => {
200
- const clock = ticking();
201
- const slots = new PairingSlots(clock.now);
202
- const slot = slots.create(10_000);
203
- clock.advance(10_000); // 正好到点
204
- assert.equal(slot.expiresAt, clock.now(), '夹具自检:此刻正好是过期时刻');
205
- assert.equal(slots.resolveFor(slot.token) !== null, true, '边界判据必须与 latest()/prune() 一致:一处用 <= 一处用 < 会让"能显示但取不到密钥"——又是静默丢 peer');
206
- assert.equal(slots.latest() !== null, true, 'latest() 与 resolveFor 在这一刻说法不一致:状态里还展示一张码,配对却取不到 PSK');
207
- clock.advance(1);
208
- assert.equal(slots.resolveFor(slot.token), null, '超过一刻就必须判过期');
209
- assert.equal(slots.latest(), null, '超过一刻展示位也要清空:status.json 里的码年龄判据靠它');
210
- });
211
- //# sourceMappingURL=ids-and-keys.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ids-and-keys.test.js","sourceRoot":"","sources":["../../tests/ids-and-keys.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAiB,MAAM,qBAAqB,CAAA;AAEnF,yCAAyC;AACzC,SAAS,OAAO,CAAC,KAAK,GAAG,iBAAiB;IACxC,IAAI,EAAE,GAAG,KAAK,CAAA;IACd,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;QACb,OAAO,EAAE,CAAC,EAAU,EAAE,EAAE;YACtB,EAAE,IAAI,EAAE,CAAA;QACV,CAAC;KACF,CAAA;AACH,CAAC;AAED,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC,CAAA;IAE9G,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,uCAAuC,CAAC,CAAA;IACnF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAA;IAC/D,MAAM,CAAC,KAAK,CACV,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAC1E,IAAI,EACJ,uCAAuC,CACxC,CAAA;IACD,MAAM,CAAC,SAAS,CACd,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,EACtB,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,EACpD,iCAAiC,CAClC,CAAA;IACD,MAAM,CAAC,SAAS,CACd,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,EACtB,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,EACpD,oBAAoB,CACrB,CAAA;IAED,2CAA2C;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IAC5E,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,YAAY,CAAC,0BAA0B,EAAE,KAAK,EAAE,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,EAC1G,IAAI,EACJ,2CAA2C,CAC5C,CAAA;IACD,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,KAAK,IAAI,EACjG,IAAI,EACJ,0CAA0C,CAC3C,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAA;IAC/D,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAA;IACtD,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAA;IACtD,MAAM,CAAC,KAAK,CACV,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,EAC9C,IAAI,EACJ,uCAAuC,CACxC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACpF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACpF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAEpF,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;IACnC,MAAM,CAAC,SAAS,CACd,OAAO,CAAC,IAAI,EAAE,EACd,CAAC,SAAS,EAAE,SAAS,CAAC,EACtB,QAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,0BAA0B,CAC1D,CAAA;IACD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,8BAA8B,CAAC,CAAA;IAC1D,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC,CAAA;IAE5D,wBAAwB;IACxB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,2BAA2B,CAAC,CAAA;IACtE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,eAAe,CAAC,CAAA;IAC1D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAA;AACnD,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACtD,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,mCAAmC,CAAC,CAAA;IAC9E,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAA;IAC3C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAC1B,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAA;IAEhC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAA;IACpF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAA;IAChE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,2CAA2C,CAAC,CAAA;IACpF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;IACvD,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAChG,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,0BAA0B,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACjG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,gBAAgB,IAAI,CAAC,IAAI,mBAAmB,CAAC,CAAA;IACxE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAA;IACxD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,4BAA4B,CAAC,CAAA;IAC9E,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,4CAA4C,CAAC,CAAA;AAC7F,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACrD,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAe,EAAE,CAAA;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QAClC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC,CAAA;IAE5C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,KAAK,CAAC,IAAI,kCAAkC,CAAC,CAAA;IACrF,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EACrC,MAAM,CAAC,KAAK,EACZ,iCAAiC,CAClC,CAAA;IACD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,OAAyC,CAAA;IAC1E,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,EAC9B,IAAI,EACJ,iBAAiB,MAAM,CAAC,KAAK,+BAA+B,CAC7D,CAAA;IACD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,0BAA0B,CAAC,CAAA;IACvF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;AAC3E,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;IACvD,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACjC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAA;IAE3E,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACrB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,kCAAkC,CAAC,CAAA;IACpF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,wCAAwC,CAAC,CAAA;IACzF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAA;IAC5D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,yBAAyB,CAAC,CAAA;AAC3E,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACpE,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA,CAAC,WAAW;IAC5C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACpB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;IAEjE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAA;IACjF,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,EACvC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,EACrB,mCAAmC,CACpC,CAAA;IACD,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EACvC,KAAK,EACL,gDAAgD,CACjD,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;IACzC,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACjC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAClB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAClC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA,CAAC,+BAA+B;IAElD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;IAC7B,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,OAAO,WAAW,CAAC,CAAA;IACnD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,CAAA;IAChE,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EACrC,IAAI,EACJ,gCAAgC,CACjC,CAAA;IACD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,kBAAkB,CAAC,CAAA;AACpD,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;IACtE,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA,CAAC,eAAe;IACzD,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACpB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA,CAAC,WAAW;IAE/C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAA;IAC5B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAA;IACjE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,KAAK,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;IAEnE,2CAA2C;IAC3C,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClD,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,oBAAoB,CAAC,CAAA;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC7F,MAAM,CAAC,EAAE,CAAC,KAAK,KAAK,IAAI,EAAE,aAAa,CAAC,CAAA;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAE9F,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,oBAAoB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;IAC5E,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,EAClF,IAAI,EACJ,2BAA2B,CAC5B,CAAA;IACD,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,EACtD,IAAI,EACJ,kCAAkC,CACnC,CAAA;IACD,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,KAAK,IAAI,EAC9D,IAAI,EACJ,sBAAsB,CACvB,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;IACvD,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;QACvF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,sCAAsC,CAAC,CAAA;IACvE,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACnE,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAA;QACpC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,kDAAkD,CAAC,CAAA;QAChF,MAAM,CAAC,KAAK,CACV,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EACjC,IAAI,EACJ,OAAO,GAAG,+CAA+C,CAC1D,CAAA;QACD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,cAAc,CAAC,CAAA;IACrE,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,qCAAqC,EAAE,GAAG,EAAE;IAC/C,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAClB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC;IACD,MAAM,CAAC,KAAK,CACV,MAAM,CAAC,IAAI,EACX,GAAG,EACH,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,yDAAyD,CAC9E,CAAA;IACD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,4BAA4B,CAAC,CAAA;AAC5D,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,MAAM,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACnC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC;QAC7B,EAAE,EAAE,SAAS;QACb,GAAG,EAAE,0BAA0B;QAC/B,UAAU,EAAE,CAAC;QACb,GAAG,EAAE,iBAAiB;KACvB,CAAC,CAAA;IACF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,8BAA8B,CAAC,CAAA;IACxE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAA;IAC/D,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,4BAA4B,CAAC,CAAA;IAC1E,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,EAAE,oCAAoC,CAAC,CAAA;IAC9E,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,iBAAiB,EAAE,0CAA0C,CAAC,CAAA;AACrG,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IACvB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACjC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA,CAAC,OAAO;IAC7B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAA;IAC3D,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EACrC,IAAI,EACJ,oEAAoE,CACrE,CAAA;IACD,MAAM,CAAC,KAAK,CACV,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,EACvB,IAAI,EACJ,sDAAsD,CACvD,CAAA;IACD,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAChB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;IAC9D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,mCAAmC,CAAC,CAAA;AACzE,CAAC,CAAC,CAAA"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,62 +0,0 @@
1
- /**
2
- * pairing-text.test — `/drc pair` 的文本输出。
3
- *
4
- * 这条测试守的是「配对输出必须自足」:不用去别处猜,也仍然能完成配对。
5
- * 默认值那一半由 `config.test.ts` 守着(`qrImage` 默认 **开**、`qrOpen`/`qrAnsi` 默认关)。
6
- *
7
- * 2026-10-02:真机取证发现 DSH 命令卡按 `line-height:1.6` 渲染等宽输出,会把半块二维码
8
- * 横切成条(zxing ≥1.15 行距即失败),于是把默认产物从文本码翻转成图片路径;
9
- * 文本码降级为 `qrImage:false` 时的退路。下面两条断言把这个结论锁住。
10
- */
11
- import { test } from 'node:test';
12
- import assert from 'node:assert/strict';
13
- import { pairingPairText, PAIR_UNAVAILABLE_TEXT } from '../src/shell/pairing-text.js';
14
- const QR = 'dshr:/p?v=1&s=wss%3A%2F%2Frelay.example.invalid&n=demo-host&psk=ZmFrZS1wc2srZmFrZQ%2BZmFrZQ%3D%3D&t=424242';
15
- const TERMINAL = '█▀▀▀▀▀█ ...\n▀ ▀ ▀ ▀▀';
16
- /** 已按会话工作区解析好的落点(`shell/status.ts` 的 pairingImagePath 负责拼,本文件只验印法)。 */
17
- const IMAGE = '/Users/demo/project/.dsh/pairing-qr.png';
18
- function view(overrides = {}) {
19
- return {
20
- token: '424242',
21
- qr: QR,
22
- expiresAt: 1_000_000,
23
- now: 940_000,
24
- qrImage: false,
25
- imageFile: IMAGE,
26
- terminalQr: TERMINAL,
27
- ...overrides,
28
- };
29
- }
30
- test('文本输出自带二维码、可粘贴的 URI、TTL 与一次性提示', () => {
31
- const text = pairingPairText(view());
32
- assert.match(text, /配对码 424242/, '6 位码必须在第一行:手输是扫码之外的唯一退路');
33
- assert.match(text, /60 秒内有效/, '剩余寿命要说清(服务端权威 TTL 改写后本地值就是它)');
34
- assert.match(text, /一次性/, '不写"一次性",用户会以为同一张码能配两部手机');
35
- assert.ok(text.includes(TERMINAL), '二维码本体必须在文本里');
36
- assert.ok(text.includes(QR), 'URI 原文要单独一行:比例字体或吞字形的渲染器下,粘贴是最后的退路');
37
- assert.match(text, /qrImage 设为 true/, '图片版是显式旁路,要告诉用户怎么开');
38
- });
39
- test('不出现任何"打开图片/查看器"的指示:这条命令不许把用户赶出 DSH', () => {
40
- const text = pairingPairText(view());
41
- for (const forbidden of ['open ', '查看器', 'xdg-open', '双击', '请用系统打开']) {
42
- assert.equal(text.includes(forbidden), false, `输出里出现了 "${forbidden}":配对动作被搬到了宿主外面`);
43
- }
44
- });
45
- test('开了 qrImage 时才报图片路径(报了就等于承诺写了文件),且不再塞那段扫不出来的文本码', () => {
46
- const withImage = pairingPairText(view({ qrImage: true }));
47
- assert.match(withImage, /pairing-qr\.png/, 'qrImage=true 时必须说清文件在哪');
48
- assert.ok(withImage.includes(IMAGE), `卡片上的路径必须是调用方解析好的那一个(收到的是 ${IMAGE})`);
49
- assert.match(withImage, /\.dsh\//, '落在工作区的 .dsh 下:用户拍板过"不要 home 下的 .dsh"');
50
- assert.match(withImage, /右栏/, '右栏会自动弹出二维码,主路径就得写它:只报文件路径等于让用户白跑一趟去找图');
51
- assert.equal(withImage.includes(TERMINAL), false, '图片模式下不许再吐文本二维码:DSH 命令卡的行高会把它切成条(本轮事故)');
52
- assert.ok(withImage.includes(QR), 'URI 原文仍要留着:手输/粘贴是最后退路');
53
- assert.equal(pairingPairText(view({ qrImage: false })).includes('pairing-qr.png'), false, '没写图片却报路径:用户会去找一个不存在的文件');
54
- // 图片模式开着但根本没有落点(statusFile 没配)时,也不许印出一个空路径——
55
- // 那等于让用户去找一个叫空字符串的文件。
56
- assert.doesNotMatch(pairingPairText(view({ qrImage: true, imageFile: '' })), /再扫:/);
57
- });
58
- test('中继没连上时给出可行动的下一步,而不是"失败"', () => {
59
- assert.match(PAIR_UNAVAILABLE_TEXT, /status\.json/, '排错入口必须被点名:GUI 宿主里没别的地方可看');
60
- assert.match(PAIR_UNAVAILABLE_TEXT, /relayProblem/, '要指到具体字段,否则"没连上"三个字什么也解决不了');
61
- });
62
- //# sourceMappingURL=pairing-text.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pairing-text.test.js","sourceRoot":"","sources":["../../tests/pairing-text.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,165 +0,0 @@
1
- /**
2
- * pairing-window.test — "让屏幕上永远有一张能用的码"这条策略。
3
- *
4
- * 需求与每条判据的事故来源写在 `src/core/pairing-window.ts` 文件头,取证
5
- * `docs/legacy-spec/host-plugin-runtime.md` §6.2。旧实现把这段挂在状态快照的
6
- * 3 秒循环里,而 §7.4 第 12 条明确记着**这三条判据一条都没被测过**——
7
- * 重写之后它必须是纯逻辑 + 有单测,否则同一段代码下一轮重写又会被改坏。
8
- *
9
- * 这里刻意只注入四样东西:时钟、在线状态、代次、发布动作。
10
- * 真 socket 与中继的簿记由 `transport/relay.ts` 自己的测试守。
11
- */
12
- import { test } from 'node:test';
13
- import assert from 'node:assert/strict';
14
- import { PairingWindow, staleReason } from '../src/core/pairing-window.js';
15
- import { FakeClock } from '../src/core/clock.js';
16
- function harness(pairSec = 60, overrides = {}) {
17
- const clock = new FakeClock();
18
- const published = [];
19
- const logs = [];
20
- const state = { online: overrides.online ?? true, generation: 1, failPublish: false };
21
- const window = new PairingWindow({
22
- clock,
23
- relayOnline: () => state.online,
24
- generation: () => state.generation,
25
- publish: (ttlMs) => {
26
- if (state.failPublish)
27
- return null;
28
- published.push(ttlMs);
29
- const createdAt = clock.now();
30
- return { token: `T${published.length}`, createdAt, expiresAt: createdAt + ttlMs };
31
- },
32
- log: (message) => logs.push(message),
33
- }, pairSec);
34
- return {
35
- clock,
36
- window,
37
- published,
38
- logs,
39
- setOnline: (online) => {
40
- state.online = online;
41
- },
42
- setGeneration: (generation) => {
43
- state.generation = generation;
44
- },
45
- failPublish: (fail = true) => {
46
- state.failPublish = fail;
47
- },
48
- /** 模拟 status 的 3 秒节拍跑若干秒,返回这几轮里发布的张数。 */
49
- tickSeconds: (seconds) => {
50
- const before = published.length;
51
- for (let at = 0; at < seconds; at += 3) {
52
- void clock.advance(3_000);
53
- window.tick();
54
- }
55
- return published.length - before;
56
- },
57
- };
58
- }
59
- test('没有活动码就发一张,并把当前代次记进去(判据一:防"永远没有码")', () => {
60
- const h = harness(60);
61
- assert.equal(h.window.tick(), true, '第一次 tick 就该发一张');
62
- assert.deepEqual(h.published, [60_000], 'TTL 用请求值(pairOnStartSec × 1000)');
63
- const active = h.window.active;
64
- assert.equal(active.token, 'T1');
65
- assert.equal(active.generation, 1, '代次必须记进去:中继重启后要靠它判死');
66
- assert.equal(h.window.tick(), false, '仍然有效时不该重复发:每 3 秒发一张会把中继的 pending 表打满');
67
- });
68
- test('中继不在线时一张都不发(发布闸门:raw() 对非 OPEN 的 socket 是静默丢弃)', () => {
69
- const h = harness(60, { online: false });
70
- assert.equal(h.window.tick(), false, '断连时也发了:本地记了一张码、中继那边根本没见过它');
71
- assert.equal(h.window.active, null, '发不出去还记进 active:status.json 会展示一张永远配不上的码');
72
- h.setOnline(true);
73
- assert.equal(h.window.tick(), true, '恢复在线之后必须补发:否则闸门变成"永远没有码"');
74
- });
75
- test('发布失败要留痕且不原地打转,下一轮仍会重试', () => {
76
- const h = harness(60);
77
- h.failPublish();
78
- assert.equal(h.window.tick(), false);
79
- assert.ok(h.logs.some((line) => line === 'pairing publish failed'), `失败没有痕迹(日志:${JSON.stringify(h.logs)})`);
80
- assert.equal(h.window.active, null);
81
- h.failPublish(false);
82
- assert.equal(h.window.tick(), true, '一次失败之后窗口就再也不发了');
83
- });
84
- test('中继重启(代次变化)后那张码立刻判死并重发(判据二:防"挂着一张死码")', () => {
85
- const h = harness(60);
86
- h.window.tick();
87
- assert.equal(h.published.length, 1);
88
- // 中继重启:pending-pair 表是内存的,我们这张码的 TTL 还没到但已经没人认得了。
89
- h.setGeneration(2);
90
- assert.equal(staleReason({ active: h.window.active, now: h.clock.now(), generation: 2, ttlMs: 60_000 }), 'relay-generation-changed');
91
- assert.equal(h.window.tick(), true, '代次变了却没重发:手机扫到的是一张中继完全不认识的码');
92
- assert.equal(h.window.active.generation, 2, '新码要记新代次');
93
- assert.equal(h.window.tick(), false, '换过一张之后不该再发');
94
- });
95
- test('剩余寿命不足半程才重发,仍在半程内不动(判据三:防"用户扫到一个刚刚过期的码")', () => {
96
- const h = harness(60);
97
- h.window.tick();
98
- void h.clock.advance(20_000); // 60s 的码走了 20s:还剩 40s > 半程 30s
99
- assert.equal(staleReason({ active: h.window.active, now: h.clock.now(), generation: 1, ttlMs: 60_000 }), null);
100
- assert.equal(h.window.tick(), false, '没到半程就换码:手机上刚刷出来的二维码又变了,人永远扫不上');
101
- void h.clock.advance(35_000); // 累计 55s:剩余 5s < 半程
102
- assert.equal(staleReason({ active: h.window.active, now: h.clock.now(), generation: 1, ttlMs: 60_000 }), 'past-half-life');
103
- assert.equal(h.window.tick(), true, '过了半程不换码:等用户去扫时它已经过期了');
104
- void h.clock.advance(60_000); // 彻底过期
105
- h.window.tick();
106
- assert.equal(staleReason({ active: null, now: h.clock.now(), generation: 1, ttlMs: 60_000 }), 'no-active-pair');
107
- // 判据一与判据三的动作相同(都要换新),但**原因必须分得开**:
108
- // 一张走到"已过期"的码说明半程刷新没做成(多半是发布一直失败),排查方向完全不同。
109
- const dead = {
110
- token: 'T1',
111
- createdAt: h.clock.now() - 120_000,
112
- expiresAt: h.clock.now() - 60_000,
113
- generation: 1,
114
- };
115
- assert.equal(staleReason({ active: dead, now: h.clock.now(), generation: 1, ttlMs: 60_000 }), 'expired', '过期那张被报成半程:日志里"半程刷新为什么没接住"就无从查起');
116
- });
117
- test('码被用掉就当场换一张新的,不必等半程(新实现加的第四条判据)', () => {
118
- const h = harness(60);
119
- h.window.tick();
120
- const first = h.window.active;
121
- void h.clock.advance(1_000); // 远未到半程
122
- h.window.markConsumed(first.token);
123
- assert.equal(h.window.active, null, '用掉了还挂着:status.json 会把一次性码反复展示给外部脚本');
124
- assert.equal(h.window.tick(), true, '消费之后不换新的:第二部手机与下一轮取证只能等半个 TTL');
125
- assert.notEqual(h.window.active.token, first.token, '必须是一张新码(一次性码不可复用)');
126
- // 只认领自己那张:别的 token 被消费不该把这张搞没。
127
- const mine = h.window.active.token;
128
- h.window.markConsumed('T999');
129
- assert.equal(h.window.active.token, mine, '陌生 token 也清了 active:多码并存时会互相踩');
130
- });
131
- test('服务端权威 TTL 覆盖请求值,并把已挂出那张的过期时间按**发起时刻**重算', () => {
132
- const h = harness(60);
133
- h.window.tick();
134
- const createdAt = h.window.active.createdAt;
135
- h.window.applyServerTtl('T1', 90_000); // 中继说这张还能活 90s
136
- assert.equal(h.window.active.expiresAt, createdAt + 90_000, '基线必须是发起时刻:用 pair-ready 到达时刻会让本地比中继早失效');
137
- // 之后每张都按服务端值发(请求值只在 pair-ready 落地前用)。
138
- h.window.markConsumed('T1');
139
- h.window.tick();
140
- assert.deepEqual(h.published, [60_000, 90_000], `第二张用的还是请求值:${JSON.stringify(h.published)}`);
141
- });
142
- test('手工 /drc pair 发的那张要被窗口认领,否则同一台主机会挂两张有效码', () => {
143
- const h = harness(60);
144
- h.window.adopt({ token: 'MANUAL', createdAt: h.clock.now(), expiresAt: h.clock.now() + 60_000 });
145
- assert.equal(h.window.tick(), false, '已经有一张有效码还再发一张:多码并存时手机取错那张就全线解不开');
146
- assert.equal(h.window.active.token, 'MANUAL');
147
- void h.clock.advance(55_000);
148
- assert.equal(h.window.tick(), true, '手工那张过半程之后窗口必须接手换新的');
149
- });
150
- test('pairOnStartSec=0 是关:一次都不发(这条默认关的配置绝不能默认生效)', () => {
151
- const h = harness(0);
152
- assert.equal(h.window.tick(), false);
153
- h.setOnline(true);
154
- assert.equal(h.tickSeconds(60), 0, `关着却发了 ${h.published.length} 张:带 PSK 的活码被写进 status.json`);
155
- });
156
- test('按 3 秒节拍跑 5 分钟:只在半程处换码,不会每 tick 发一张', () => {
157
- const h = harness(60);
158
- const fresh = harness(60);
159
- assert.equal(fresh.window.tick(), true);
160
- const count = fresh.tickSeconds(300); // 5 分钟 = 100 个 tick
161
- // 60s 的码在半程(30s)换一次 ⇒ 5 分钟里 60/30 → 约 10 张,绝不是一百张。
162
- assert.ok(count > 0 && count <= 12, `300 秒里发了 ${count} 张:要么没刷新,要么每 tick 发一张(中继配额会被打爆)`);
163
- assert.equal(h.published.length, 0, '夹具自检:harness() 本身不该发码');
164
- });
165
- //# sourceMappingURL=pairing-window.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pairing-window.test.js","sourceRoot":"","sources":["../../tests/pairing-window.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};