@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,69 @@
1
+ /**
2
+ * LAN bind toggle: the managed cordis patch block that pins the web
3
+ * server's bind. On = a block binding 0.0.0.0; off = the same block binding
4
+ * 127.0.0.1. The block takes effect on the next `dsh web` start (the live
5
+ * patch watcher cannot rebind a running listener, and on this harness line
6
+ * the user patch layer cannot evaluate webStartup-dependent expressions
7
+ * reliably - static values are the only dependable form), so the plugin
8
+ * re-asserts the block at every boot and the settings card reports the
9
+ * running bind honestly.
10
+ *
11
+ * Same-id patch rows REPLACE the row config wholesale, so the block carries
12
+ * the official web-app webserver row's full config (bind + compression)
13
+ * with the bind values materialized. The CLI's --host 0.0.0.0 guard stays
14
+ * intact: deliberate LAN exposure happens through this configuration layer
15
+ * only. Until the user flips the toggle once, the plugin never touches the
16
+ * patch file.
17
+ *
18
+ * The block discipline (markers, atomic write, strip-and-rewrite) follows
19
+ * the dsh-LAN reference implementation (MIT).
20
+ */
21
+ export declare const LAN_BIND_BLOCK_BEGIN = "# --- remote-web-ui lan-bind block (managed - do not edit) ---";
22
+ export declare const LAN_BIND_BLOCK_END = "# --- end remote-web-ui lan-bind block ---";
23
+ /** The two bind hosts the managed block pins. */
24
+ export type LanBindHost = '0.0.0.0' | '127.0.0.1';
25
+ /**
26
+ * The absolute path of the profile patch file this toggle manages. The value
27
+ * is config-controlled (and the DSH_PROFILE env fallback bypasses schema
28
+ * validation entirely), so the path is guarded twice: the profile must be a
29
+ * single safe path segment, and the resolved file must stay under the
30
+ * profiles directory.
31
+ */
32
+ export declare function profilePatchFile(profile: string, home?: string): string;
33
+ /** Remove the managed block (and its markers) from patch content. */
34
+ export declare function stripManagedBlock(content: string): string;
35
+ /**
36
+ * Render the managed block for one bind state. The values are static: the
37
+ * user patch layer has no reliable lazy service evaluation, and the plugin
38
+ * re-asserts the block at every boot so CLI flags (--port, --host) win by
39
+ * rewriting it before the next start.
40
+ */
41
+ export declare function managedBlock(host: LanBindHost, port: number): string;
42
+ /** The bind state a managed block pins (undefined when there is no block). */
43
+ export interface ManagedBindState {
44
+ host: LanBindHost | (string & {});
45
+ port: number | undefined;
46
+ }
47
+ /**
48
+ * Parse the block's pinned bind out of patch content. A block whose values
49
+ * were hand-edited reports the literals as-is so the card can surface them
50
+ * instead of silently claiming one of the two known states.
51
+ */
52
+ export declare function managedBindOf(content: string): ManagedBindState | undefined;
53
+ /** Full file-level state for the settings card and the boot re-assert. */
54
+ export declare function lanBindState(profile: string, home?: string): {
55
+ blockPresent: boolean;
56
+ host?: string;
57
+ port?: number;
58
+ };
59
+ /**
60
+ * Write (or rewrite) the managed block with the given bind. The rest of the
61
+ * patch file is preserved; the block is rewritten atomically (unique temp
62
+ * file + rename, so concurrent writers can never rename a half-written
63
+ * file) with the original file's permissions preserved. A hand-truncated
64
+ * unterminated block (BEGIN marker without END, which stripManagedBlock
65
+ * cannot match) is truncated at its BEGIN marker first so the rewrite can
66
+ * never stack a second webserver row onto the orphan.
67
+ */
68
+ export declare function writeLanBind(host: LanBindHost, port: number, profile: string, home?: string): void;
69
+ //# sourceMappingURL=lan-bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lan-bind.d.ts","sourceRoot":"","sources":["../../src/lan-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,eAAO,MAAM,oBAAoB,mEAAmE,CAAA;AACpG,eAAO,MAAM,kBAAkB,+CAA+C,CAAA;AAE9E,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUlF;AAYD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAW3E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAI/H;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,IAAI,CAW7G"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * LAN bind toggle: the managed cordis patch block that pins the web
3
+ * server's bind. On = a block binding 0.0.0.0; off = the same block binding
4
+ * 127.0.0.1. The block takes effect on the next `dsh web` start (the live
5
+ * patch watcher cannot rebind a running listener, and on this harness line
6
+ * the user patch layer cannot evaluate webStartup-dependent expressions
7
+ * reliably - static values are the only dependable form), so the plugin
8
+ * re-asserts the block at every boot and the settings card reports the
9
+ * running bind honestly.
10
+ *
11
+ * Same-id patch rows REPLACE the row config wholesale, so the block carries
12
+ * the official web-app webserver row's full config (bind + compression)
13
+ * with the bind values materialized. The CLI's --host 0.0.0.0 guard stays
14
+ * intact: deliberate LAN exposure happens through this configuration layer
15
+ * only. Until the user flips the toggle once, the plugin never touches the
16
+ * patch file.
17
+ *
18
+ * The block discipline (markers, atomic write, strip-and-rewrite) follows
19
+ * the dsh-LAN reference implementation (MIT).
20
+ */
21
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
22
+ import { dirname, join, resolve, sep } from 'node:path';
23
+ import { dshHome } from "./dsh-home.js";
24
+ /** Profile names safe to interpolate into the patch path: one path segment, no traversal. */
25
+ const PROFILE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
26
+ export const LAN_BIND_BLOCK_BEGIN = '# --- remote-web-ui lan-bind block (managed - do not edit) ---';
27
+ export const LAN_BIND_BLOCK_END = '# --- end remote-web-ui lan-bind block ---';
28
+ /**
29
+ * The absolute path of the profile patch file this toggle manages. The value
30
+ * is config-controlled (and the DSH_PROFILE env fallback bypasses schema
31
+ * validation entirely), so the path is guarded twice: the profile must be a
32
+ * single safe path segment, and the resolved file must stay under the
33
+ * profiles directory.
34
+ */
35
+ export function profilePatchFile(profile, home = dshHome()) {
36
+ if (!PROFILE_PATTERN.test(profile)) {
37
+ throw new Error(`remote-web-ui: unsafe lan-bind profile ${JSON.stringify(profile)}`);
38
+ }
39
+ const file = resolve(join(home, 'profiles', profile, 'cordis.patch.yml'));
40
+ const root = resolve(join(home, 'profiles')) + sep;
41
+ if (!file.startsWith(root)) {
42
+ throw new Error(`remote-web-ui: lan-bind profile ${JSON.stringify(profile)} escapes the profiles directory`);
43
+ }
44
+ return file;
45
+ }
46
+ function readPatchContent(file) {
47
+ if (!existsSync(file))
48
+ return '';
49
+ return readFileSync(file, 'utf8');
50
+ }
51
+ /** Escape one literal string for embedding into a RegExp pattern. */
52
+ function escapeRegex(text) {
53
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
54
+ }
55
+ /** Remove the managed block (and its markers) from patch content. */
56
+ export function stripManagedBlock(content) {
57
+ const begin = escapeRegex(LAN_BIND_BLOCK_BEGIN);
58
+ const end = escapeRegex(LAN_BIND_BLOCK_END);
59
+ const pattern = new RegExp(`\\r?\\n?${begin}[\\s\\S]*?${end}\\r?\\n?`, 'g');
60
+ return content.replace(pattern, '\n');
61
+ }
62
+ /**
63
+ * Render the managed block for one bind state. The values are static: the
64
+ * user patch layer has no reliable lazy service evaluation, and the plugin
65
+ * re-asserts the block at every boot so CLI flags (--port, --host) win by
66
+ * rewriting it before the next start.
67
+ */
68
+ export function managedBlock(host, port) {
69
+ return [
70
+ LAN_BIND_BLOCK_BEGIN,
71
+ '- id: webserver',
72
+ " name: '@deepseek-ai/dsh-host-webserver'",
73
+ ' config:',
74
+ ` host: '${host}'`,
75
+ ` port: ${String(port)}`,
76
+ ' compression: gzip',
77
+ ' compressionLevel: 1',
78
+ ' compressionThresholdBytes: 1024',
79
+ LAN_BIND_BLOCK_END,
80
+ '',
81
+ ].join('\n');
82
+ }
83
+ /**
84
+ * Parse the block's pinned bind out of patch content. A block whose values
85
+ * were hand-edited reports the literals as-is so the card can surface them
86
+ * instead of silently claiming one of the two known states.
87
+ */
88
+ export function managedBindOf(content) {
89
+ const begin = content.indexOf(LAN_BIND_BLOCK_BEGIN);
90
+ if (begin === -1)
91
+ return undefined;
92
+ const end = content.indexOf(LAN_BIND_BLOCK_END, begin);
93
+ const block = end === -1 ? content.slice(begin) : content.slice(begin, end);
94
+ const hostMatch = /host:\s*'([^']+)'/.exec(block);
95
+ const portMatch = /port:\s*(\d+)/.exec(block);
96
+ return {
97
+ host: hostMatch?.[1] ?? '',
98
+ port: portMatch !== null ? Number(portMatch[1]) : undefined,
99
+ };
100
+ }
101
+ /** Full file-level state for the settings card and the boot re-assert. */
102
+ export function lanBindState(profile, home = dshHome()) {
103
+ const state = managedBindOf(readPatchContent(profilePatchFile(profile, home)));
104
+ if (state === undefined)
105
+ return { blockPresent: false };
106
+ return { blockPresent: true, host: state.host, port: state.port };
107
+ }
108
+ /**
109
+ * Write (or rewrite) the managed block with the given bind. The rest of the
110
+ * patch file is preserved; the block is rewritten atomically (unique temp
111
+ * file + rename, so concurrent writers can never rename a half-written
112
+ * file) with the original file's permissions preserved. A hand-truncated
113
+ * unterminated block (BEGIN marker without END, which stripManagedBlock
114
+ * cannot match) is truncated at its BEGIN marker first so the rewrite can
115
+ * never stack a second webserver row onto the orphan.
116
+ */
117
+ export function writeLanBind(host, port, profile, home = dshHome()) {
118
+ const file = profilePatchFile(profile, home);
119
+ const stripped = stripManagedBlock(readPatchContent(file));
120
+ const orphanBegin = stripped.indexOf(LAN_BIND_BLOCK_BEGIN);
121
+ const base = (orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd();
122
+ const content = `${base}\n\n${managedBlock(host, port)}`;
123
+ const mode = existsSync(file) ? statSync(file).mode & 0o777 : 0o600;
124
+ mkdirSync(dirname(file), { recursive: true });
125
+ const temp = `${file}.remote-web-ui-tmp-${process.pid.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
126
+ writeFileSync(temp, content, { mode });
127
+ renameSync(temp, file);
128
+ }
@@ -8,14 +8,19 @@
8
8
  */
9
9
  import type { IncomingMessage, ServerResponse } from 'node:http';
10
10
  import type { Duplex } from 'node:stream';
11
+ import type { InnerAuth } from './inner-auth.ts';
11
12
  /**
12
13
  * Pipe one HTTP request to loopback and stream the response back.
13
14
  * @param req - the already-gated outer request.
14
15
  * @param res - the outer response.
15
16
  * @param port - local webServer port.
16
17
  * @param upstreamPath - path + query on 127.0.0.1 (must start with `/`).
18
+ * @param auth - when given, the process's inner browser-auth credential is
19
+ * attached so the connection plugin's /api route (fence + browser auth,
20
+ * authority-bound cookie, no loopback exemption on this cohort) accepts
21
+ * the re-issued request; a 401 answer invalidates the cached credential.
17
22
  */
18
- export declare function proxyLoopbackHttp(req: IncomingMessage, res: ServerResponse, port: number, upstreamPath: string): void;
23
+ export declare function proxyLoopbackHttp(req: IncomingMessage, res: ServerResponse, port: number, upstreamPath: string, auth?: InnerAuth): void;
19
24
  /**
20
25
  * Rebuild a WebSocket handshake as loopback-shaped and pipe both directions.
21
26
  * @param req - the already-gated upgrade request.
@@ -23,6 +28,8 @@ export declare function proxyLoopbackHttp(req: IncomingMessage, res: ServerRespo
23
28
  * @param head - bytes already read past the handshake.
24
29
  * @param port - local webServer port.
25
30
  * @param upstreamPath - path + query on 127.0.0.1.
31
+ * @param cookie - the inner browser-auth credential for the handshake, when
32
+ * the upstream route enforces it (the gateway event-stream mux does).
26
33
  */
27
- export declare function proxyLoopbackUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, port: number, upstreamPath: string): void;
34
+ export declare function proxyLoopbackUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, port: number, upstreamPath: string, cookie?: string): void;
28
35
  //# sourceMappingURL=loopback-proxy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopback-proxy.d.ts","sourceRoot":"","sources":["../../src/loopback-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAA;AAGrF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAqBzC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,IAAI,CAmCN;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,IAAI,CA6BN"}
1
+ {"version":3,"file":"loopback-proxy.d.ts","sourceRoot":"","sources":["../../src/loopback-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAA;AAGrF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAoBhD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,SAAS,GACf,IAAI,CAIN;AAmDD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAoCN"}
@@ -31,8 +31,17 @@ const HTTP_FORWARD_RESPONSE_HEADERS = [
31
31
  * @param res - the outer response.
32
32
  * @param port - local webServer port.
33
33
  * @param upstreamPath - path + query on 127.0.0.1 (must start with `/`).
34
+ * @param auth - when given, the process's inner browser-auth credential is
35
+ * attached so the connection plugin's /api route (fence + browser auth,
36
+ * authority-bound cookie, no loopback exemption on this cohort) accepts
37
+ * the re-issued request; a 401 answer invalidates the cached credential.
34
38
  */
35
- export function proxyLoopbackHttp(req, res, port, upstreamPath) {
39
+ export function proxyLoopbackHttp(req, res, port, upstreamPath, auth) {
40
+ void Promise.resolve(auth?.ready())
41
+ .catch(() => undefined)
42
+ .then((cookie) => { pipeLoopbackHttp(req, res, port, upstreamPath, auth, typeof cookie === 'string' ? cookie : undefined); });
43
+ }
44
+ function pipeLoopbackHttp(req, res, port, upstreamPath, auth, cookie) {
36
45
  const headers = {
37
46
  host: `127.0.0.1:${String(port)}`,
38
47
  'sec-fetch-site': 'same-origin',
@@ -46,6 +55,8 @@ export function proxyLoopbackHttp(req, res, port, upstreamPath) {
46
55
  const accept = req.headers.accept;
47
56
  if (typeof accept === 'string')
48
57
  headers.accept = accept;
58
+ if (cookie !== undefined)
59
+ headers.cookie = cookie;
49
60
  const upstream = httpRequest({
50
61
  host: '127.0.0.1',
51
62
  port,
@@ -53,6 +64,11 @@ export function proxyLoopbackHttp(req, res, port, upstreamPath) {
53
64
  method: req.method,
54
65
  headers,
55
66
  }, (upstreamRes) => {
67
+ // The cached credential went stale (secret rotation, credential store
68
+ // reset): drop it so the next request re-redeems. The in-flight response
69
+ // still pipes through untouched.
70
+ if (upstreamRes.statusCode === 401 && cookie !== undefined)
71
+ auth?.invalidate();
56
72
  const out = {};
57
73
  for (const name of HTTP_FORWARD_RESPONSE_HEADERS) {
58
74
  const value = upstreamRes.headers[name];
@@ -78,14 +94,18 @@ export function proxyLoopbackHttp(req, res, port, upstreamPath) {
78
94
  * @param head - bytes already read past the handshake.
79
95
  * @param port - local webServer port.
80
96
  * @param upstreamPath - path + query on 127.0.0.1.
97
+ * @param cookie - the inner browser-auth credential for the handshake, when
98
+ * the upstream route enforces it (the gateway event-stream mux does).
81
99
  */
82
- export function proxyLoopbackUpgrade(req, socket, head, port, upstreamPath) {
100
+ export function proxyLoopbackUpgrade(req, socket, head, port, upstreamPath, cookie) {
83
101
  const lines = [
84
102
  `GET ${upstreamPath} HTTP/1.1`,
85
103
  `Host: 127.0.0.1:${String(port)}`,
86
104
  'Upgrade: websocket',
87
105
  'Connection: Upgrade',
88
106
  ];
107
+ if (cookie !== undefined)
108
+ lines.push(`Cookie: ${cookie}`);
89
109
  for (const name of WS_FORWARD_HEADERS) {
90
110
  const value = req.headers[name];
91
111
  if (value === undefined)
@@ -94,6 +114,8 @@ export function proxyLoopbackUpgrade(req, socket, head, port, upstreamPath) {
94
114
  }
95
115
  const handshake = `${lines.join('\r\n')}\r\n\r\n`;
96
116
  const upstream = connect(port, '127.0.0.1');
117
+ socket.setKeepAlive(true, 20_000);
118
+ upstream.setKeepAlive(true, 20_000);
97
119
  const tearDown = () => {
98
120
  upstream.destroy();
99
121
  socket.destroy();
@@ -7,8 +7,12 @@
7
7
  * Security invariants:
8
8
  * - One active token at a time; `issue()` replaces it, so a refreshed QR
9
9
  * immediately invalidates the previous link.
10
- * - A token is consumed by the first successful `accept()` — reuse is
11
- * refused with `'used'`.
10
+ * - A token stays re-usable until it expires or is replaced: the first
11
+ * successful `accept()` marks it consumed, but the same link may pair
12
+ * again within the window (each re-accept mints a fresh device session).
13
+ * Mobile flows routinely split across cookie contexts (camera preview to
14
+ * in-app browser to the system browser), and the later context must be
15
+ * able to complete its own pairing from the same link.
12
16
  * - Tokens expire; `accept()` on an expired token is refused like an
13
17
  * unknown one (no oracle for validity).
14
18
  * - `stop()` revokes every device session and clears the token, so paired
@@ -44,7 +48,15 @@ export interface TokenRecord {
44
48
  /** LAN IP literal the QR link was built from (optional; default first). */
45
49
  address?: string;
46
50
  }
47
- /** Default idle-expiry window: 7 days without heartbeat or a gated request. */
51
+ /**
52
+ * Default idle-expiry window: 30 days without a heartbeat or a gated
53
+ * request. The reopen service worker refreshes lastSeenAt on every
54
+ * navigation it serves, so the window only runs out through genuine
55
+ * disuse; 30 days matches the browser-credential lifetimes the surrounding
56
+ * flow was built around, while the effective device lifetime stays shorter
57
+ * than the 365-day cookie because of this sweep. Override per deployment
58
+ * through the idleExpireMs config.
59
+ */
48
60
  export declare const DEFAULT_IDLE_EXPIRE_MS: number;
49
61
  /** One paired device session, keyed by the device id stored in its cookie. */
50
62
  export interface DeviceSession {
@@ -1 +1 @@
1
- {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
@@ -7,8 +7,12 @@
7
7
  * Security invariants:
8
8
  * - One active token at a time; `issue()` replaces it, so a refreshed QR
9
9
  * immediately invalidates the previous link.
10
- * - A token is consumed by the first successful `accept()` — reuse is
11
- * refused with `'used'`.
10
+ * - A token stays re-usable until it expires or is replaced: the first
11
+ * successful `accept()` marks it consumed, but the same link may pair
12
+ * again within the window (each re-accept mints a fresh device session).
13
+ * Mobile flows routinely split across cookie contexts (camera preview to
14
+ * in-app browser to the system browser), and the later context must be
15
+ * able to complete its own pairing from the same link.
12
16
  * - Tokens expire; `accept()` on an expired token is refused like an
13
17
  * unknown one (no oracle for validity).
14
18
  * - `stop()` revokes every device session and clears the token, so paired
@@ -19,8 +23,16 @@
19
23
  import { randomBytes } from 'node:crypto';
20
24
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
21
25
  import { dirname } from 'node:path';
22
- /** Default idle-expiry window: 7 days without heartbeat or a gated request. */
23
- export const DEFAULT_IDLE_EXPIRE_MS = 7 * 24 * 60 * 60 * 1000;
26
+ /**
27
+ * Default idle-expiry window: 30 days without a heartbeat or a gated
28
+ * request. The reopen service worker refreshes lastSeenAt on every
29
+ * navigation it serves, so the window only runs out through genuine
30
+ * disuse; 30 days matches the browser-credential lifetimes the surrounding
31
+ * flow was built around, while the effective device lifetime stays shorter
32
+ * than the 365-day cookie because of this sweep. Override per deployment
33
+ * through the idleExpireMs config.
34
+ */
35
+ export const DEFAULT_IDLE_EXPIRE_MS = 30 * 24 * 60 * 60 * 1000;
24
36
  /** Cap on the persisted/displayed User-Agent string. */
25
37
  const MAX_USER_AGENT_CHARS = 180;
26
38
  /** Thrown by issue() for an address outside the sampled LAN literals. */
@@ -243,8 +255,10 @@ export class PairingService {
243
255
  */
244
256
  accept(token, userAgent) {
245
257
  const record = this.tokens.get(token);
246
- if (record === undefined || record.consumed || this.stopped || this.clock.now() > record.expiresAt) {
247
- return { ok: false, code: record?.consumed === true ? 'used' : 'invalid' };
258
+ // A consumed token stays a valid bearer credential until expiry or
259
+ // replacement (see the module doc): re-accept mints a fresh device.
260
+ if (record === undefined || this.stopped || this.clock.now() > record.expiresAt) {
261
+ return { ok: false, code: 'invalid' };
248
262
  }
249
263
  record.consumed = true;
250
264
  const deviceId = this.clock.randomToken();
@@ -1,29 +1,31 @@
1
1
  /**
2
2
  * The remote desktop data channel: `/remote` is this plugin's own prefix, so
3
- * the paired-device cookie is the access control (exactly like `/m/api`).
4
- * After that gate, every fenced same-origin path the browser rewrote here is
5
- * re-issued to 127.0.0.1 as a loopback-shaped request so sibling plugin
6
- * fences (and the connection plugin's `/api`) accept it — no `--trusted-host`
7
- * and no per-plugin pairing consult.
3
+ * the paired-device cookie is the access control. After that gate, every
4
+ * fenced same-origin path the browser rewrote here is re-issued to 127.0.0.1
5
+ * as a loopback-shaped request so sibling plugin fences (and the connection
6
+ * plugin's `/api`) accept it — no `--trusted-host` and no per-plugin pairing
7
+ * consult.
8
8
  *
9
9
  * Security model:
10
10
  * - While `requirePairingForLan` is on (default), every request must carry a
11
11
  * live paired-device cookie, enforced before any bytes are forwarded and
12
12
  * before any host call. With the policy off, the cookie gate is skipped
13
- * (the loopback-only denials below still apply).
14
- * - The SDK's loopback-only privileged methods (native dialogs, the settings
15
- * plane, credentials — the `PRIVILEGED_METHODS` set of client-connection)
16
- * are denied here. The set is pinned by tests/remote-contract.spec.ts.
17
- * - `/api/pair/*`, `/api/update/*`, `/api/plugin-manager/*`,
18
- * `/api/dsh-desktop-launcher/*` and `/api/dsh-web-ui-settings/*` stay physically local.
13
+ * (the local-only denials below still apply).
14
+ * - A paired remote desktop is a full-control credential: the browser half
15
+ * flips the official UI into host mode (the transport ownsHost hook), so
16
+ * the configuration plane (settings, credentials, presets, deliverables)
17
+ * rides this channel like every other call. The four control planes in
18
+ * LOCAL_ONLY_PREFIXES stay physically local.
19
19
  * - Everything else is HTTP- or WebSocket-proxied to the local port with
20
20
  * Host rewritten, Origin and cookies dropped, and a synthetic same-origin
21
21
  * browser marker added after authentication. Plugin loopback fences then
22
22
  * pass. The pairing cookie never leaves this process.
23
23
  */
24
+ import type { IncomingMessage } from 'node:http';
24
25
  import type { WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver';
25
26
  import type { PairingService } from './pairing.ts';
26
- export { DESKTOP_LAUNCHER_PATH, LOOPBACK_ONLY_METHODS, PLUGIN_MANAGER_PATH, REMOTE_API_PATHS, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, } from './remote-methods.ts';
27
+ import type { InnerAuth } from './inner-auth.ts';
28
+ export { DESKTOP_LAUNCHER_PATH, LOCAL_ONLY_PREFIXES, PLUGIN_MANAGER_PATH, REMOTE_API_PATHS, REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, localOnlyDenial, } from './remote-methods.ts';
27
29
  export { REMOTE_API_PREFIX } from './remote-methods.ts';
28
30
  /** Route-family dependencies. */
29
31
  export interface RemoteApiDeps {
@@ -38,6 +40,13 @@ export interface RemoteApiDeps {
38
40
  * edit takes effect without a restart. Defaults to true.
39
41
  */
40
42
  requirePairingForLan?: boolean | (() => boolean);
43
+ /**
44
+ * The process's inner browser-auth credential attached to re-issued
45
+ * requests (the connection plugin's /api route enforces that cookie and
46
+ * the pairing gate above already ran). Undefined keeps the previous
47
+ * cookie-less behavior — the inner route then answers 401 on this cohort.
48
+ */
49
+ auth?: InnerAuth;
41
50
  }
42
51
  /**
43
52
  * Map `/remote/...` to the inner path, or undefined when the outer path is
@@ -45,10 +54,18 @@ export interface RemoteApiDeps {
45
54
  */
46
55
  export declare function innerPathOf(pathname: string): string | undefined;
47
56
  /**
48
- * Whether a paired inner path must stay physically local.
57
+ * Whether a paired inner path must stay physically local (delegates to the
58
+ * shared LOCAL_ONLY_PREFIXES table).
49
59
  * @returns a denial message, or undefined when the path may be proxied.
50
60
  */
51
61
  export declare function loopbackOnlyDenial(innerPath: string): string | undefined;
62
+ /**
63
+ * Resolve a live device credential for a gated HTTP request: the pairing
64
+ * cookie first, then the cookieless header the boot patch attaches. Unknown
65
+ * or revoked ids are a no-op - a stale id never re-arms a device.
66
+ * @returns the touched device id, or undefined when neither credential is live.
67
+ */
68
+ export declare function pairedDeviceIdOf(req: IncomingMessage, service: PairingService): string | undefined;
52
69
  /**
53
70
  * Build the remote desktop channel HTTP routes.
54
71
  * @param deps - pairing service + local port + live pairing policy.
@@ -1 +1 @@
1
- {"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAalD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;CACjD;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsBxE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA2CnE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,EAAE,CA2BjF"}
1
+ {"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAUhD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;IAChD;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA0CnE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,EAAE,CA2CjF"}