@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.10

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 (238) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +77 -468
  3. package/README.zh.md +83 -113
  4. package/lib/client.js +993 -203
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1136 -1466
  7. package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
  8. package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
  9. package/lib/types/client/FooterRemoteEntry.js +1 -1
  10. package/lib/types/client/RemoteEntry.d.ts +7 -4
  11. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  12. package/lib/types/client/RemoteEntry.js +7 -10
  13. package/lib/types/client/RemotePanel.d.ts +2 -2
  14. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  15. package/lib/types/client/RemotePanel.js +2 -2
  16. package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
  17. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  18. package/lib/types/client/RemoteSettingsCard.js +56 -3
  19. package/lib/types/client/deep-link.d.ts +10 -13
  20. package/lib/types/client/deep-link.d.ts.map +1 -1
  21. package/lib/types/client/deep-link.js +14 -74
  22. package/lib/types/client/index.d.ts +2 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +79 -39
  25. package/lib/types/client/locales.d.ts +32 -12
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +42 -22
  28. package/lib/types/client/mobile-adapt.d.ts +49 -0
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -0
  30. package/lib/types/client/mobile-adapt.js +1002 -0
  31. package/lib/types/client/pair-api.d.ts +24 -6
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +9 -14
  34. package/lib/types/client/remote-channel.d.ts +3 -0
  35. package/lib/types/client/remote-channel.d.ts.map +1 -1
  36. package/lib/types/client/remote-channel.js +35 -3
  37. package/lib/types/client/settings-form.d.ts +35 -43
  38. package/lib/types/client/settings-form.d.ts.map +1 -1
  39. package/lib/types/client/settings-form.js +60 -53
  40. package/lib/types/firewall.d.ts +75 -0
  41. package/lib/types/firewall.d.ts.map +1 -0
  42. package/lib/types/firewall.js +184 -0
  43. package/lib/types/gate.d.ts +11 -5
  44. package/lib/types/gate.d.ts.map +1 -1
  45. package/lib/types/gate.js +11 -5
  46. package/lib/types/index.d.ts +39 -10
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +206 -58
  49. package/lib/types/inner-auth.d.ts +32 -0
  50. package/lib/types/inner-auth.d.ts.map +1 -0
  51. package/lib/types/inner-auth.js +72 -0
  52. package/lib/types/lan-bind-plan.d.ts +55 -0
  53. package/lib/types/lan-bind-plan.d.ts.map +1 -0
  54. package/lib/types/lan-bind-plan.js +60 -0
  55. package/lib/types/lan-bind.d.ts +69 -0
  56. package/lib/types/lan-bind.d.ts.map +1 -0
  57. package/lib/types/lan-bind.js +128 -0
  58. package/lib/types/loopback-proxy.d.ts +9 -2
  59. package/lib/types/loopback-proxy.d.ts.map +1 -1
  60. package/lib/types/loopback-proxy.js +24 -2
  61. package/lib/types/pairing.d.ts +15 -3
  62. package/lib/types/pairing.d.ts.map +1 -1
  63. package/lib/types/pairing.js +20 -6
  64. package/lib/types/remote-api.d.ts +30 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +55 -42
  67. package/lib/types/remote-channel-boot.d.ts +21 -2
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +65 -3
  70. package/lib/types/remote-channel-rules.d.ts +8 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +9 -2
  73. package/lib/types/remote-methods.d.ts +45 -11
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +56 -27
  76. package/lib/types/remote-presence-pet.d.ts +51 -0
  77. package/lib/types/remote-presence-pet.d.ts.map +1 -0
  78. package/lib/types/remote-presence-pet.js +101 -0
  79. package/lib/types/routes.d.ts +76 -4
  80. package/lib/types/routes.d.ts.map +1 -1
  81. package/lib/types/routes.js +336 -19
  82. package/lib/types/tunnel.d.ts.map +1 -1
  83. package/lib/types/tunnel.js +7 -1
  84. package/package.json +20 -18
  85. package/src/client/FooterRemoteEntry.tsx +9 -10
  86. package/src/client/RemoteEntry.tsx +13 -14
  87. package/src/client/RemotePanel.tsx +5 -16
  88. package/src/client/RemoteSettingsCard.tsx +72 -13
  89. package/src/client/deep-link.ts +14 -75
  90. package/src/client/index.ts +82 -39
  91. package/src/client/locales.ts +42 -22
  92. package/src/client/mobile-adapt.ts +954 -0
  93. package/src/client/pair-api.ts +26 -15
  94. package/src/client/remote-channel.ts +31 -3
  95. package/src/client/settings-form.ts +78 -92
  96. package/src/dsh-home.ts +1 -1
  97. package/src/firewall.ts +230 -0
  98. package/src/gate.ts +11 -5
  99. package/src/index.ts +235 -65
  100. package/src/inner-auth.ts +85 -0
  101. package/src/lan-bind-plan.ts +81 -0
  102. package/src/lan-bind.ts +145 -0
  103. package/src/loopback-proxy.ts +34 -0
  104. package/src/pairing.persist.test.ts +1 -1
  105. package/src/pairing.ts +20 -6
  106. package/src/remote-api.ts +67 -45
  107. package/src/remote-channel-boot.ts +70 -3
  108. package/src/remote-channel-rules.ts +17 -2
  109. package/src/remote-methods.ts +58 -27
  110. package/src/remote-presence-pet.ts +129 -0
  111. package/src/routes.ts +358 -19
  112. package/src/tunnel.ts +7 -1
  113. package/assets/apple-touch-icon.png +0 -0
  114. package/assets/mobile-icon-192.png +0 -0
  115. package/assets/mobile-icon-512.png +0 -0
  116. package/assets/mobile-service-worker.js +0 -63
  117. package/lib/mobile.js +0 -17658
  118. package/lib/mobile.js.map +0 -1
  119. package/lib/types/mobile/PairRequiredView.d.ts +0 -7
  120. package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
  121. package/lib/types/mobile/PairRequiredView.js +0 -28
  122. package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
  123. package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
  124. package/lib/types/mobile/PairRequiredView.test.js +0 -24
  125. package/lib/types/mobile/api.d.ts +0 -145
  126. package/lib/types/mobile/api.d.ts.map +0 -1
  127. package/lib/types/mobile/api.js +0 -103
  128. package/lib/types/mobile/index.d.ts +0 -10
  129. package/lib/types/mobile/index.d.ts.map +0 -1
  130. package/lib/types/mobile/index.js +0 -38
  131. package/lib/types/mobile/markdown.d.ts +0 -34
  132. package/lib/types/mobile/markdown.d.ts.map +0 -1
  133. package/lib/types/mobile/markdown.js +0 -285
  134. package/lib/types/mobile/markdown.test.d.ts +0 -2
  135. package/lib/types/mobile/markdown.test.d.ts.map +0 -1
  136. package/lib/types/mobile/markdown.test.js +0 -79
  137. package/lib/types/mobile/messages.d.ts +0 -149
  138. package/lib/types/mobile/messages.d.ts.map +0 -1
  139. package/lib/types/mobile/messages.js +0 -561
  140. package/lib/types/mobile/messages.test.d.ts +0 -2
  141. package/lib/types/mobile/messages.test.d.ts.map +0 -1
  142. package/lib/types/mobile/messages.test.js +0 -356
  143. package/lib/types/mobile/mobile-styles.d.ts +0 -6
  144. package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
  145. package/lib/types/mobile/mobile-styles.js +0 -1438
  146. package/lib/types/mobile/mobile-theme.d.ts +0 -22
  147. package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
  148. package/lib/types/mobile/mobile-theme.js +0 -70
  149. package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
  150. package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
  151. package/lib/types/mobile/mobile-theme.test.js +0 -71
  152. package/lib/types/mobile/mux.d.ts +0 -128
  153. package/lib/types/mobile/mux.d.ts.map +0 -1
  154. package/lib/types/mobile/mux.js +0 -305
  155. package/lib/types/mobile/mux.test.d.ts +0 -2
  156. package/lib/types/mobile/mux.test.d.ts.map +0 -1
  157. package/lib/types/mobile/mux.test.js +0 -267
  158. package/lib/types/mobile/pairing.d.ts +0 -31
  159. package/lib/types/mobile/pairing.d.ts.map +0 -1
  160. package/lib/types/mobile/pairing.js +0 -62
  161. package/lib/types/mobile/rpc.d.ts +0 -38
  162. package/lib/types/mobile/rpc.d.ts.map +0 -1
  163. package/lib/types/mobile/rpc.js +0 -83
  164. package/lib/types/mobile/theme-toggle.d.ts +0 -12
  165. package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
  166. package/lib/types/mobile/theme-toggle.js +0 -26
  167. package/lib/types/mobile/views/App.d.ts +0 -57
  168. package/lib/types/mobile/views/App.d.ts.map +0 -1
  169. package/lib/types/mobile/views/App.js +0 -150
  170. package/lib/types/mobile/views/App.test.d.ts +0 -2
  171. package/lib/types/mobile/views/App.test.d.ts.map +0 -1
  172. package/lib/types/mobile/views/App.test.js +0 -52
  173. package/lib/types/mobile/views/ChatView.d.ts +0 -63
  174. package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
  175. package/lib/types/mobile/views/ChatView.js +0 -821
  176. package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
  177. package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
  178. package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
  179. package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
  180. package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
  181. package/lib/types/mobile/views/ChatView.test.js +0 -718
  182. package/lib/types/mobile/views/SessionListView.d.ts +0 -27
  183. package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
  184. package/lib/types/mobile/views/SessionListView.js +0 -139
  185. package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
  186. package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
  187. package/lib/types/mobile/views/SessionListView.test.js +0 -167
  188. package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
  189. package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
  190. package/lib/types/mobile/views/WorkspaceView.js +0 -93
  191. package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
  192. package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
  193. package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
  194. package/lib/types/mobile-api.d.ts +0 -65
  195. package/lib/types/mobile-api.d.ts.map +0 -1
  196. package/lib/types/mobile-api.js +0 -404
  197. package/lib/types/mobile-pending.d.ts +0 -38
  198. package/lib/types/mobile-pending.d.ts.map +0 -1
  199. package/lib/types/mobile-pending.js +0 -59
  200. package/lib/types/mobile-pwa.d.ts +0 -6
  201. package/lib/types/mobile-pwa.d.ts.map +0 -1
  202. package/lib/types/mobile-pwa.js +0 -17
  203. package/lib/types/mobile-routes.d.ts +0 -13
  204. package/lib/types/mobile-routes.d.ts.map +0 -1
  205. package/lib/types/mobile-routes.js +0 -217
  206. package/lib/types/paired-model-catalog.d.ts +0 -17
  207. package/lib/types/paired-model-catalog.d.ts.map +0 -1
  208. package/lib/types/paired-model-catalog.js +0 -311
  209. package/src/mobile/PairRequiredView.test.tsx +0 -28
  210. package/src/mobile/PairRequiredView.tsx +0 -58
  211. package/src/mobile/api.ts +0 -229
  212. package/src/mobile/index.tsx +0 -41
  213. package/src/mobile/markdown.test.ts +0 -91
  214. package/src/mobile/markdown.ts +0 -291
  215. package/src/mobile/messages.test.ts +0 -402
  216. package/src/mobile/messages.ts +0 -685
  217. package/src/mobile/mobile-styles.ts +0 -1439
  218. package/src/mobile/mobile-theme.test.ts +0 -84
  219. package/src/mobile/mobile-theme.ts +0 -76
  220. package/src/mobile/mux.test.ts +0 -314
  221. package/src/mobile/mux.ts +0 -336
  222. package/src/mobile/pairing.ts +0 -77
  223. package/src/mobile/rpc.ts +0 -90
  224. package/src/mobile/theme-toggle.tsx +0 -47
  225. package/src/mobile/views/App.test.tsx +0 -62
  226. package/src/mobile/views/App.tsx +0 -213
  227. package/src/mobile/views/ChatView.stream.test.tsx +0 -268
  228. package/src/mobile/views/ChatView.test.tsx +0 -816
  229. package/src/mobile/views/ChatView.tsx +0 -1342
  230. package/src/mobile/views/SessionListView.test.tsx +0 -207
  231. package/src/mobile/views/SessionListView.tsx +0 -244
  232. package/src/mobile/views/WorkspaceView.test.tsx +0 -227
  233. package/src/mobile/views/WorkspaceView.tsx +0 -245
  234. package/src/mobile-api.ts +0 -418
  235. package/src/mobile-pending.ts +0 -88
  236. package/src/mobile-pwa.ts +0 -19
  237. package/src/mobile-routes.ts +0 -231
  238. package/src/paired-model-catalog.ts +0 -272
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firewall.d.ts","sourceRoot":"","sources":["../../src/firewall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,yBAAyB,CAAA;AAExD,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,UAAU,CAAA;AAEhF,2EAA2E;AAC3E,eAAO,MAAM,WAAW,EAAE,aAazB,CAAA;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAClC;AA4ED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,GAAG,GAAE,aAA2B,GAC/B,eAAe,GAAG,SAAS,CAW7B;AAID,+DAA+D;AAC/D,wBAAgB,eAAe,IAAI,eAAe,GAAG,SAAS,CAM7D;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,EAAE,EAAE,OAAO,CAAA;IACX,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAA;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,eAAe,GAAG,SAAS,GACnC,eAAe,CAGjB;AAED,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAI7C,4DAA4D;AAC5D,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,eAAe,CASlF"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Host firewall management for the LAN bind toggle: while LAN access is on,
3
+ * keep one inbound allow rule for the bound port; when it turns off, remove
4
+ * the rule. Windows manages the Windows Defender Firewall rule via netsh;
5
+ * Linux uses the first available manager among firewalld / ufw / iptables.
6
+ * On every other platform (macOS included) the plugin reports the firewall
7
+ * as unmanaged: the port usually needs no rule there, and managing pf or the
8
+ * application firewall is out of scope for a distributable plugin.
9
+ *
10
+ * Ported from the dsh-LAN reference implementation (MIT), adapted to this
11
+ * package's structure and test seams.
12
+ */
13
+ import { spawnSync } from 'node:child_process';
14
+ /** Rule name shared by every backend so re-runs recreate the same rule. */
15
+ export const FIREWALL_RULE_NAME = 'remote-web-ui (auto)';
16
+ /** Production runner: spawn without a shell, capture text, 20s timeout. */
17
+ export const spawnRunner = (cmd, args) => {
18
+ const result = spawnSync(cmd, args, {
19
+ shell: false,
20
+ encoding: 'utf8',
21
+ windowsHide: process.platform === 'win32',
22
+ timeout: 20_000,
23
+ });
24
+ return {
25
+ ok: result.status === 0,
26
+ out: result.stdout ?? '',
27
+ err: result.stderr ?? '',
28
+ missing: result.error !== undefined,
29
+ };
30
+ };
31
+ function netshBackend(run) {
32
+ // `show rule name=` exits 0 even when nothing matches (the localized "no
33
+ // rules match" note), so exit-code-only probes always report the rule as
34
+ // present. The verbose listing echoes the full rule name — the one
35
+ // locale-independent field — and is matched literally instead.
36
+ const show = () => {
37
+ const result = run('netsh', ['advfirewall', 'firewall', 'show', 'rule', `name=${FIREWALL_RULE_NAME}`, 'verbose']);
38
+ return result.ok && result.out.includes(FIREWALL_RULE_NAME);
39
+ };
40
+ return {
41
+ label: 'netsh',
42
+ ruleExists: show,
43
+ addRule: (port) => run('netsh', [
44
+ 'advfirewall', 'firewall', 'add', 'rule', `name=${FIREWALL_RULE_NAME}`,
45
+ 'dir=in', 'action=allow', 'protocol=TCP', `localport=${String(port)}`, 'profile=private,domain',
46
+ ]).ok,
47
+ removeRule: () => {
48
+ if (!show())
49
+ return true;
50
+ return run('netsh', ['advfirewall', 'firewall', 'delete', 'rule', `name=${FIREWALL_RULE_NAME}`]).ok;
51
+ },
52
+ };
53
+ }
54
+ function firewalldBackend(run) {
55
+ return {
56
+ label: 'firewalld',
57
+ ruleExists: (port) => run('firewall-cmd', ['--permanent', '--query-port', `${String(port)}/tcp`]).ok,
58
+ addRule: (port) => {
59
+ const add = run('firewall-cmd', ['--permanent', '--add-port', `${String(port)}/tcp`]);
60
+ const reload = run('firewall-cmd', ['--reload']);
61
+ return add.ok && reload.ok;
62
+ },
63
+ removeRule: (port) => {
64
+ const del = run('firewall-cmd', ['--permanent', '--remove-port', `${String(port)}/tcp`]);
65
+ const reload = run('firewall-cmd', ['--reload']);
66
+ return del.ok && reload.ok;
67
+ },
68
+ };
69
+ }
70
+ function ufwBackend(run) {
71
+ return {
72
+ label: 'ufw',
73
+ ruleExists: (port) => {
74
+ const result = run('ufw', ['status']);
75
+ // Anchored so a superset port's row (1443/tcp) cannot satisfy the
76
+ // probe for a shorter one (443/tcp).
77
+ return result.ok && new RegExp(`(^|\\s)${String(port)}/tcp\\s+ALLOW`, 'i').test(result.out);
78
+ },
79
+ addRule: (port) => run('ufw', ['allow', `${String(port)}/tcp`]).ok,
80
+ removeRule: (port) => run('ufw', ['delete', 'allow', `${String(port)}/tcp`]).ok,
81
+ };
82
+ }
83
+ function iptablesBackend(run) {
84
+ const rule = (port) => ['INPUT', '-p', 'tcp', '--dport', String(port), '-j', 'ACCEPT'];
85
+ return {
86
+ label: 'iptables',
87
+ ruleExists: (port) => run('iptables', ['-C', ...rule(port)]).ok,
88
+ addRule: (port) => run('iptables', ['-A', ...rule(port)]).ok,
89
+ removeRule: (port) => run('iptables', ['-D', ...rule(port)]).ok,
90
+ };
91
+ }
92
+ function toolAvailable(run, cmd) {
93
+ try {
94
+ // A missing binary reports through the runner's missing flag (spawnSync
95
+ // sets result.error for ENOENT); any graceful answer counts as present.
96
+ return run(cmd, ['--version']).missing !== true;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ /**
103
+ * Detect the platform firewall manager. Returns undefined when the platform
104
+ * has no supported manager (macOS, unknown Linux without tools): the port
105
+ * then usually needs no rule and the UI reports "unmanaged".
106
+ */
107
+ export function detectFirewallBackend(platform, run = spawnRunner) {
108
+ if (platform === 'win32')
109
+ return netshBackend(run);
110
+ if (platform !== 'linux')
111
+ return undefined;
112
+ if (toolAvailable(run, 'firewall-cmd')) {
113
+ // firewalld only counts when its daemon answers; an installed-but-idle
114
+ // firewall-cmd would otherwise claim management it cannot exercise.
115
+ if (run('firewall-cmd', ['--state']).ok)
116
+ return firewalldBackend(run);
117
+ }
118
+ if (toolAvailable(run, 'ufw'))
119
+ return ufwBackend(run);
120
+ if (toolAvailable(run, 'iptables'))
121
+ return iptablesBackend(run);
122
+ return undefined;
123
+ }
124
+ let cachedBackend;
125
+ /** Cached detection for the running platform (per process). */
126
+ export function firewallBackend() {
127
+ const platform = process.platform;
128
+ if (cachedBackend === undefined || cachedBackend.platform !== platform) {
129
+ cachedBackend = { platform, backend: detectFirewallBackend(platform) };
130
+ }
131
+ return cachedBackend.backend;
132
+ }
133
+ /**
134
+ * Delete-and-add: recreating the rule is idempotent and locale-proof (netsh
135
+ * output is localized, so parsing the live rule's port is fragile; the Linux
136
+ * backends follow the same recreate pattern).
137
+ */
138
+ export function ensureFirewallRule(port) {
139
+ invalidateFirewallSummary();
140
+ const backend = firewallBackend();
141
+ if (backend === undefined)
142
+ return true;
143
+ backend.removeRule(port);
144
+ return backend.addRule(port);
145
+ }
146
+ export function removeFirewallRule(port) {
147
+ invalidateFirewallSummary();
148
+ const backend = firewallBackend();
149
+ if (backend === undefined)
150
+ return true;
151
+ return backend.removeRule(port);
152
+ }
153
+ /**
154
+ * Human-readable firewall state for the status endpoint. A missing backend
155
+ * means the platform has nothing to manage; detection stays at the call site
156
+ * so an explicit undefined cannot silently re-probe the real OS mid-test.
157
+ */
158
+ export function computeFirewallSummary(port, lanEnabled, backend) {
159
+ if (backend === undefined)
160
+ return { ok: true, managed: false };
161
+ return { ok: lanEnabled ? backend.ruleExists(port) : !backend.ruleExists(port), managed: true, note: backend.label };
162
+ }
163
+ /** How long a firewallSummary answer is reused before the backend is probed again. */
164
+ export const FIREWALL_SUMMARY_TTL_MS = 30_000;
165
+ let summaryCache;
166
+ /** Forget the cached summary (rule mutations call this). */
167
+ export function invalidateFirewallSummary() {
168
+ summaryCache = undefined;
169
+ }
170
+ /**
171
+ * The settings card polls this every ten seconds; the probes are blocking
172
+ * spawnSync subprocesses on managed platforms, so short-TTL memoization
173
+ * keeps one poll from freezing the host event loop per request.
174
+ */
175
+ export function firewallSummary(port, lanEnabled) {
176
+ const key = `${String(port)}|${lanEnabled ? '1' : '0'}`;
177
+ const now = Date.now();
178
+ if (summaryCache !== undefined && summaryCache.key === key && now - summaryCache.at < FIREWALL_SUMMARY_TTL_MS) {
179
+ return summaryCache.value;
180
+ }
181
+ const value = computeFirewallSummary(port, lanEnabled, firewallBackend());
182
+ summaryCache = { key, at: now, value };
183
+ return value;
184
+ }
@@ -6,9 +6,15 @@
6
6
  *
7
7
  * Policy: loopback requests (the desktop) pass without a device identity;
8
8
  * every non-loopback /api request must carry a live, non-revoked device
9
- * cookie. This makes the QR the only way into a LAN-exposed dsh web and
10
- * gives "停止" real teeth: revoked devices 403 on their next request,
11
- * including the mux/SSE stream (which then dies on reconnect).
9
+ * cookie — intended to make the QR the only way into a LAN-exposed dsh web
10
+ * and to give "停止" real teeth for direct /api callers.
11
+ *
12
+ * Cohort reality (0.1.2-alpha.2): NOTHING emits api/gate in the official
13
+ * runtime, so this listener never fires there and direct /api is governed
14
+ * solely by the harness fence + browser-auth cookie. The pairing gate with
15
+ * real teeth lives on the plugin's own /remote channel (remote-api.ts); the
16
+ * listener stays wired for cohort lines that do carry the seam, and the
17
+ * README security model states the gap plainly.
12
18
  */
13
19
  import type { IncomingMessage } from 'node:http';
14
20
  import type { PairingService } from './pairing.ts';
@@ -52,8 +58,8 @@ export declare function isLoopbackClient(request: IncomingMessage): boolean;
52
58
  export declare function makeGateListener(service: PairingService, requirePairingForLan?: boolean | (() => boolean), enabled?: boolean | (() => boolean)): (request: IncomingMessage, method: string | undefined, next: () => boolean | Promise<boolean>) => boolean | Promise<boolean>;
53
59
  /**
54
60
  * Whether a request carries a live, non-revoked paired-device cookie for
55
- * this service. Sibling host routes outside /api (aionui-panel, etc.) use
56
- * the same check via the remoteWebUiPairing service.
61
+ * this service. Sibling host routes outside /api (the right-panel
62
+ * /sidebar/* routes, etc.) use the same check via the remoteWebUiPairing service.
57
63
  * @param service - the pairing service that owns the device table.
58
64
  * @param request - the incoming HTTP request.
59
65
  * @returns true when the cookie names a live session (and lastSeenAt was refreshed).
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,oBAAoB,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,EACtD,OAAO,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,GACxC,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAc9H;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAIhG"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,oBAAoB,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,EACtD,OAAO,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,GACxC,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAc9H;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAIhG"}
package/lib/types/gate.js CHANGED
@@ -6,9 +6,15 @@
6
6
  *
7
7
  * Policy: loopback requests (the desktop) pass without a device identity;
8
8
  * every non-loopback /api request must carry a live, non-revoked device
9
- * cookie. This makes the QR the only way into a LAN-exposed dsh web and
10
- * gives "停止" real teeth: revoked devices 403 on their next request,
11
- * including the mux/SSE stream (which then dies on reconnect).
9
+ * cookie — intended to make the QR the only way into a LAN-exposed dsh web
10
+ * and to give "停止" real teeth for direct /api callers.
11
+ *
12
+ * Cohort reality (0.1.2-alpha.2): NOTHING emits api/gate in the official
13
+ * runtime, so this listener never fires there and direct /api is governed
14
+ * solely by the harness fence + browser-auth cookie. The pairing gate with
15
+ * real teeth lives on the plugin's own /remote channel (remote-api.ts); the
16
+ * listener stays wired for cohort lines that do carry the seam, and the
17
+ * README security model states the gap plainly.
12
18
  */
13
19
  import { isLoopbackAddress, isLoopbackHostname } from "./loopback.js";
14
20
  /**
@@ -96,8 +102,8 @@ export function makeGateListener(service, requirePairingForLan = true, enabled =
96
102
  }
97
103
  /**
98
104
  * Whether a request carries a live, non-revoked paired-device cookie for
99
- * this service. Sibling host routes outside /api (aionui-panel, etc.) use
100
- * the same check via the remoteWebUiPairing service.
105
+ * this service. Sibling host routes outside /api (the right-panel
106
+ * /sidebar/* routes, etc.) use the same check via the remoteWebUiPairing service.
101
107
  * @param service - the pairing service that owns the device table.
102
108
  * @param request - the incoming HTTP request.
103
109
  * @returns true when the cookie names a live session (and lastSeenAt was refreshed).
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { IncomingMessage } from 'node:http';
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
12
13
  import z from 'schemastery';
13
14
  import { type PairingConfig } from './pairing.ts';
14
15
  declare module '@deepseek-ai/cordis' {
@@ -18,6 +19,12 @@ declare module '@deepseek-ai/cordis' {
18
19
  * fires this per /api request before bridging to the API proxy on
19
20
  * deployments that carry the pairing/revocation seam; call `next()` to
20
21
  * delegate, return false (without calling it) to veto with 403.
22
+ *
23
+ * Cohort note (0.1.2-alpha.2): the official runtime ships NO emitter
24
+ * for this event, so the listener below never fires there and direct
25
+ * /api stays under the harness fence + browser auth. It is wired anyway
26
+ * so cohort lines that do carry the seam get pairing enforcement on
27
+ * direct /api without a plugin change.
21
28
  */
22
29
  'api/gate'(this: Context, request: IncomingMessage, method: string | undefined, next: () => boolean | Promise<boolean>): boolean | Promise<boolean>;
23
30
  }
@@ -31,7 +38,7 @@ export declare const inject: string[];
31
38
  * settings surface edits. Spelled here rather than imported: the browser
32
39
  * half spells the same value and must not depend on a Host package.
33
40
  */
34
- export declare const REMOTE_WEB_UI_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
41
+ export declare const REMOTE_WEB_UI_SETTINGS_NAMESPACE: SettingsNamespace;
35
42
  /** Plugin config, validated by the same-named schemastery schema. */
36
43
  export interface Config {
37
44
  /** Token lifetime in ms; the QR link dies after this. */
@@ -50,9 +57,14 @@ export interface Config {
50
57
  /**
51
58
  * When true (default), a desktop Web GUI opened at a non-loopback origin
52
59
  * rides the gated `/remote/api` channel and must carry a live paired-device
53
- * cookie — the QR is the only way into remote desktop, and stop() cuts
54
- * paired devices off. Set false to keep the desktop on plain `/api`
55
- * (only useful when that origin is already trusted for `/api`).
60
+ * cookie — the QR is the only way into remote desktop, and stop()/revoke()
61
+ * cut the /remote channel and the pairing cookie off immediately. Scope
62
+ * note for this cohort: direct /api is governed by the harness fence +
63
+ * browser-auth cookie (the api/gate seam has no emitter on 0.1.2-alpha.2),
64
+ * so a harness browser credential a device has already redeemed is not
65
+ * invalidated by stop() — see the README security model. Set false to keep
66
+ * the desktop on plain `/api` (only useful when that origin is already
67
+ * trusted for `/api`).
56
68
  */
57
69
  requirePairingForLan?: boolean;
58
70
  /**
@@ -80,12 +92,26 @@ export interface Config {
80
92
  */
81
93
  autoTunnel?: boolean;
82
94
  /**
83
- * Mobile composer behavior: when true (default), a plain Enter in the
84
- * phone chat textarea sends the prompt and Shift+Enter inserts a newline.
85
- * When false, plain Enter inserts a newline and only the send button
86
- * sends (Shift+Enter keeps inserting a newline).
95
+ * LAN bind toggle. When the user flips it (true or false) the plugin
96
+ * writes the managed webserver block into the profile patch — true pins
97
+ * the bind default to 0.0.0.0 (an explicit --host flag still wins), false
98
+ * pins it back to 127.0.0.1 — and maintains the matching host firewall
99
+ * rule (Windows netsh; Linux firewalld/ufw/iptables; other platforms
100
+ * report the firewall as unmanaged). The block takes effect when the
101
+ * process next applies the profile — the live patch watcher recomposes on
102
+ * some profile shapes, otherwise on the next start — and the settings
103
+ * card reports the divergence (pendingRestart) instead of guessing.
104
+ * While the toggle has never been set (undefined), the plugin does not
105
+ * touch the patch file at all.
87
106
  */
88
- mobileEnterToSend?: boolean;
107
+ lanBind?: boolean;
108
+ /**
109
+ * The profile whose cordis.patch.yml the LAN bind toggle manages.
110
+ * Defaults to the DSH_PROFILE environment variable, then "web". Must be
111
+ * a single safe path segment (the DSH_PROFILE env fallback bypasses this
112
+ * schema, so the path builder asserts containment independently).
113
+ */
114
+ profile?: string;
89
115
  /** Master switch for the plugin (browser half + host pairing surfaces). */
90
116
  enabled?: boolean;
91
117
  }
@@ -95,9 +121,12 @@ export declare const Config: z<Config>;
95
121
  * which legitimately resolves to `undefined` when unset (the schema keeps it
96
122
  * optional, so `Required` alone would over-narrow it to `string`).
97
123
  */
98
- type ResolvedConfig = Required<Omit<Config, 'publicBaseUrl' | 'devicesFile'>> & {
124
+ type ResolvedConfig = Required<Omit<Config, 'publicBaseUrl' | 'devicesFile' | 'lanBind' | 'profile'>> & {
99
125
  publicBaseUrl: string | undefined;
100
126
  devicesFile: string;
127
+ /** undefined until the user flips the toggle once; undefined never writes the patch. */
128
+ lanBind: boolean | undefined;
129
+ profile: string;
101
130
  };
102
131
  /**
103
132
  * The single mapping from resolved plugin config to the pairing service
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAI3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA6BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAAkD,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAY3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,GAAG;IAC9E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAiBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAwVtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,CAAC,MAAM,aAAa,CAAA;AAO3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA8BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;;;;;;;WAWG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA+C,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,EAAsB,iBAAiB,CAAA;AAEpF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;;;;OAWG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAa3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC,GAAG;IACtG,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAkBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAgetD"}