@marktrue/dsh-mobile-remote 0.0.0-stage → 3.2.1-alpha.1

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 (348) hide show
  1. package/CHANGELOG.md +796 -0
  2. package/CONTRIBUTING.md +59 -0
  3. package/FAQ.md +111 -0
  4. package/LICENSE +21 -0
  5. package/README.md +255 -2
  6. package/cordis.patch.yml +14 -0
  7. package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +122 -0
  8. package/docs/01-PRD.md +148 -0
  9. package/docs/02-architecture.md +249 -0
  10. package/docs/03-api.md +816 -0
  11. package/docs/04-security.md +96 -0
  12. package/docs/05-test-cases.md +326 -0
  13. package/docs/06-install-run.md +308 -0
  14. package/docs/07-user-manual.md +65 -0
  15. package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +86 -0
  16. package/docs/09-compatibility.md +195 -0
  17. package/docs/10-release.md +927 -0
  18. package/docs/screenshots/01-home.png +0 -0
  19. package/docs/screenshots/02-notifications.png +0 -0
  20. package/docs/screenshots/03-sessions.png +0 -0
  21. package/docs/screenshots/04-chat.png +0 -0
  22. package/docs/screenshots/05-settings.png +0 -0
  23. package/docs/screenshots/06-new-session.png +0 -0
  24. package/docs/screenshots/pgy-appstore.jpg +0 -0
  25. package/docs/screenshots/pgy-mobile-app.jpg +0 -0
  26. package/docs/screenshots/pgy-pc-client.jpg +0 -0
  27. package/lib/account-usage.js +145 -0
  28. package/lib/app-alignment.js +112 -0
  29. package/lib/app-carrier-store.js +1972 -0
  30. package/lib/app-carrier.js +602 -0
  31. package/lib/client.js +706 -0
  32. package/lib/desktop-form.js +738 -0
  33. package/lib/endpoint-registry.js +162 -0
  34. package/lib/git-read-bundled-provider.js +255 -0
  35. package/lib/git-read-service.js +322 -0
  36. package/lib/index.js +9292 -0
  37. package/lib/plugin-content-digest.js +80 -0
  38. package/lib/plugin-self-update.js +758 -0
  39. package/lib/plugin-update-controller.js +95 -0
  40. package/lib/plugin-update-transaction.js +322 -0
  41. package/lib/plugin-update.js +309 -0
  42. package/lib/runtime-dependency-snapshot.js +345 -0
  43. package/lib/session-title-cache.js +123 -0
  44. package/lib/session-title-refresh.js +261 -0
  45. package/lib/update-fetch.js +24 -0
  46. package/lib/update-lock-arbiter.js +158 -0
  47. package/lib/update-supervisor.js +312 -0
  48. package/node_modules/ansi-regex/index.d.ts +37 -0
  49. package/node_modules/ansi-regex/index.js +10 -0
  50. package/node_modules/ansi-regex/license +9 -0
  51. package/node_modules/ansi-regex/package.json +55 -0
  52. package/node_modules/ansi-regex/readme.md +78 -0
  53. package/node_modules/ansi-styles/index.d.ts +345 -0
  54. package/node_modules/ansi-styles/index.js +163 -0
  55. package/node_modules/ansi-styles/license +9 -0
  56. package/node_modules/ansi-styles/package.json +56 -0
  57. package/node_modules/ansi-styles/readme.md +152 -0
  58. package/node_modules/camelcase/index.d.ts +63 -0
  59. package/node_modules/camelcase/index.js +76 -0
  60. package/node_modules/camelcase/license +9 -0
  61. package/node_modules/camelcase/package.json +43 -0
  62. package/node_modules/camelcase/readme.md +99 -0
  63. package/node_modules/cliui/CHANGELOG.md +76 -0
  64. package/node_modules/cliui/LICENSE.txt +14 -0
  65. package/node_modules/cliui/README.md +115 -0
  66. package/node_modules/cliui/index.js +354 -0
  67. package/node_modules/cliui/package.json +65 -0
  68. package/node_modules/color-convert/CHANGELOG.md +54 -0
  69. package/node_modules/color-convert/LICENSE +21 -0
  70. package/node_modules/color-convert/README.md +68 -0
  71. package/node_modules/color-convert/conversions.js +839 -0
  72. package/node_modules/color-convert/index.js +81 -0
  73. package/node_modules/color-convert/package.json +48 -0
  74. package/node_modules/color-convert/route.js +97 -0
  75. package/node_modules/color-name/LICENSE +8 -0
  76. package/node_modules/color-name/README.md +11 -0
  77. package/node_modules/color-name/index.js +152 -0
  78. package/node_modules/color-name/package.json +28 -0
  79. package/node_modules/decamelize/index.js +13 -0
  80. package/node_modules/decamelize/license +21 -0
  81. package/node_modules/decamelize/package.json +38 -0
  82. package/node_modules/decamelize/readme.md +48 -0
  83. package/node_modules/dijkstrajs/.travis.yml +4 -0
  84. package/node_modules/dijkstrajs/CONTRIBUTING.md +8 -0
  85. package/node_modules/dijkstrajs/LICENSE.md +19 -0
  86. package/node_modules/dijkstrajs/README.md +18 -0
  87. package/node_modules/dijkstrajs/dijkstra.js +165 -0
  88. package/node_modules/dijkstrajs/package.json +60 -0
  89. package/node_modules/dijkstrajs/test/dijkstra.test.js +96 -0
  90. package/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
  91. package/node_modules/emoji-regex/README.md +73 -0
  92. package/node_modules/emoji-regex/es2015/index.js +6 -0
  93. package/node_modules/emoji-regex/es2015/text.js +6 -0
  94. package/node_modules/emoji-regex/index.d.ts +23 -0
  95. package/node_modules/emoji-regex/index.js +6 -0
  96. package/node_modules/emoji-regex/package.json +50 -0
  97. package/node_modules/emoji-regex/text.js +6 -0
  98. package/node_modules/find-up/index.d.ts +137 -0
  99. package/node_modules/find-up/index.js +89 -0
  100. package/node_modules/find-up/license +9 -0
  101. package/node_modules/find-up/package.json +53 -0
  102. package/node_modules/find-up/readme.md +156 -0
  103. package/node_modules/get-caller-file/LICENSE.md +6 -0
  104. package/node_modules/get-caller-file/README.md +41 -0
  105. package/node_modules/get-caller-file/index.d.ts +2 -0
  106. package/node_modules/get-caller-file/index.js +22 -0
  107. package/node_modules/get-caller-file/index.js.map +1 -0
  108. package/node_modules/get-caller-file/package.json +42 -0
  109. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  110. package/node_modules/is-fullwidth-code-point/index.js +50 -0
  111. package/node_modules/is-fullwidth-code-point/license +9 -0
  112. package/node_modules/is-fullwidth-code-point/package.json +42 -0
  113. package/node_modules/is-fullwidth-code-point/readme.md +39 -0
  114. package/node_modules/locate-path/index.d.ts +83 -0
  115. package/node_modules/locate-path/index.js +65 -0
  116. package/node_modules/locate-path/license +9 -0
  117. package/node_modules/locate-path/package.json +45 -0
  118. package/node_modules/locate-path/readme.md +122 -0
  119. package/node_modules/p-limit/index.d.ts +38 -0
  120. package/node_modules/p-limit/index.js +57 -0
  121. package/node_modules/p-limit/license +9 -0
  122. package/node_modules/p-limit/package.json +52 -0
  123. package/node_modules/p-limit/readme.md +101 -0
  124. package/node_modules/p-locate/index.d.ts +64 -0
  125. package/node_modules/p-locate/index.js +52 -0
  126. package/node_modules/p-locate/license +9 -0
  127. package/node_modules/p-locate/package.json +53 -0
  128. package/node_modules/p-locate/readme.md +90 -0
  129. package/node_modules/p-try/index.d.ts +39 -0
  130. package/node_modules/p-try/index.js +9 -0
  131. package/node_modules/p-try/license +9 -0
  132. package/node_modules/p-try/package.json +42 -0
  133. package/node_modules/p-try/readme.md +58 -0
  134. package/node_modules/path-exists/index.d.ts +28 -0
  135. package/node_modules/path-exists/index.js +23 -0
  136. package/node_modules/path-exists/license +9 -0
  137. package/node_modules/path-exists/package.json +39 -0
  138. package/node_modules/path-exists/readme.md +52 -0
  139. package/node_modules/pngjs/.eslintignore +2 -0
  140. package/node_modules/pngjs/.eslintrc.json +19 -0
  141. package/node_modules/pngjs/.prettierignore +3 -0
  142. package/node_modules/pngjs/LICENSE +20 -0
  143. package/node_modules/pngjs/README.md +429 -0
  144. package/node_modules/pngjs/browser.js +16096 -0
  145. package/node_modules/pngjs/coverage/lcov-report/base.css +354 -0
  146. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +881 -0
  147. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +554 -0
  148. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +77 -0
  149. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +647 -0
  150. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +176 -0
  151. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +200 -0
  152. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  153. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +593 -0
  154. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +152 -0
  155. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +143 -0
  156. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +611 -0
  157. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +359 -0
  158. package/node_modules/pngjs/coverage/lcov-report/index.html +426 -0
  159. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +365 -0
  160. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +230 -0
  161. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +248 -0
  162. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +467 -0
  163. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +128 -0
  164. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +575 -0
  165. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +404 -0
  166. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +950 -0
  167. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +116 -0
  168. package/node_modules/pngjs/coverage/lcov-report/png.js.html +662 -0
  169. package/node_modules/pngjs/coverage/lcov-report/prettify.css +101 -0
  170. package/node_modules/pngjs/coverage/lcov-report/prettify.js +1007 -0
  171. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  172. package/node_modules/pngjs/coverage/lcov-report/sorter.js +169 -0
  173. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +584 -0
  174. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +215 -0
  175. package/node_modules/pngjs/coverage/lcov.info +2177 -0
  176. package/node_modules/pngjs/lib/bitmapper.js +267 -0
  177. package/node_modules/pngjs/lib/bitpacker.js +158 -0
  178. package/node_modules/pngjs/lib/chunkstream.js +189 -0
  179. package/node_modules/pngjs/lib/constants.js +32 -0
  180. package/node_modules/pngjs/lib/crc.js +40 -0
  181. package/node_modules/pngjs/lib/filter-pack.js +171 -0
  182. package/node_modules/pngjs/lib/filter-parse-async.js +24 -0
  183. package/node_modules/pngjs/lib/filter-parse-sync.js +21 -0
  184. package/node_modules/pngjs/lib/filter-parse.js +177 -0
  185. package/node_modules/pngjs/lib/format-normaliser.js +93 -0
  186. package/node_modules/pngjs/lib/interlace.js +95 -0
  187. package/node_modules/pngjs/lib/packer-async.js +50 -0
  188. package/node_modules/pngjs/lib/packer-sync.js +56 -0
  189. package/node_modules/pngjs/lib/packer.js +129 -0
  190. package/node_modules/pngjs/lib/paeth-predictor.js +16 -0
  191. package/node_modules/pngjs/lib/parser-async.js +165 -0
  192. package/node_modules/pngjs/lib/parser-sync.js +108 -0
  193. package/node_modules/pngjs/lib/parser.js +290 -0
  194. package/node_modules/pngjs/lib/png-sync.js +12 -0
  195. package/node_modules/pngjs/lib/png.js +194 -0
  196. package/node_modules/pngjs/lib/sync-inflate.js +168 -0
  197. package/node_modules/pngjs/lib/sync-reader.js +45 -0
  198. package/node_modules/pngjs/package.json +73 -0
  199. package/node_modules/qrcode/README.md +770 -0
  200. package/node_modules/qrcode/bin/qrcode +159 -0
  201. package/node_modules/qrcode/helper/to-sjis-browser.js +2 -0
  202. package/node_modules/qrcode/helper/to-sjis.js +105 -0
  203. package/node_modules/qrcode/lib/browser.js +76 -0
  204. package/node_modules/qrcode/lib/can-promise.js +7 -0
  205. package/node_modules/qrcode/lib/core/alignment-pattern.js +83 -0
  206. package/node_modules/qrcode/lib/core/alphanumeric-data.js +59 -0
  207. package/node_modules/qrcode/lib/core/bit-buffer.js +37 -0
  208. package/node_modules/qrcode/lib/core/bit-matrix.js +65 -0
  209. package/node_modules/qrcode/lib/core/byte-data.js +30 -0
  210. package/node_modules/qrcode/lib/core/error-correction-code.js +135 -0
  211. package/node_modules/qrcode/lib/core/error-correction-level.js +50 -0
  212. package/node_modules/qrcode/lib/core/finder-pattern.js +22 -0
  213. package/node_modules/qrcode/lib/core/format-info.js +29 -0
  214. package/node_modules/qrcode/lib/core/galois-field.js +69 -0
  215. package/node_modules/qrcode/lib/core/kanji-data.js +54 -0
  216. package/node_modules/qrcode/lib/core/mask-pattern.js +234 -0
  217. package/node_modules/qrcode/lib/core/mode.js +167 -0
  218. package/node_modules/qrcode/lib/core/numeric-data.js +43 -0
  219. package/node_modules/qrcode/lib/core/polynomial.js +62 -0
  220. package/node_modules/qrcode/lib/core/qrcode.js +495 -0
  221. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +56 -0
  222. package/node_modules/qrcode/lib/core/regex.js +31 -0
  223. package/node_modules/qrcode/lib/core/segments.js +330 -0
  224. package/node_modules/qrcode/lib/core/utils.js +63 -0
  225. package/node_modules/qrcode/lib/core/version-check.js +9 -0
  226. package/node_modules/qrcode/lib/core/version.js +163 -0
  227. package/node_modules/qrcode/lib/index.js +12 -0
  228. package/node_modules/qrcode/lib/renderer/canvas.js +63 -0
  229. package/node_modules/qrcode/lib/renderer/png.js +78 -0
  230. package/node_modules/qrcode/lib/renderer/svg-tag.js +81 -0
  231. package/node_modules/qrcode/lib/renderer/svg.js +19 -0
  232. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +85 -0
  233. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +49 -0
  234. package/node_modules/qrcode/lib/renderer/terminal.js +9 -0
  235. package/node_modules/qrcode/lib/renderer/utf8.js +71 -0
  236. package/node_modules/qrcode/lib/renderer/utils.js +99 -0
  237. package/node_modules/qrcode/lib/server.js +138 -0
  238. package/node_modules/qrcode/license +10 -0
  239. package/node_modules/qrcode/package.json +77 -0
  240. package/node_modules/require-directory/.jshintrc +67 -0
  241. package/node_modules/require-directory/.npmignore +1 -0
  242. package/node_modules/require-directory/.travis.yml +3 -0
  243. package/node_modules/require-directory/LICENSE +22 -0
  244. package/node_modules/require-directory/README.markdown +184 -0
  245. package/node_modules/require-directory/index.js +86 -0
  246. package/node_modules/require-directory/package.json +40 -0
  247. package/node_modules/require-main-filename/CHANGELOG.md +26 -0
  248. package/node_modules/require-main-filename/LICENSE.txt +14 -0
  249. package/node_modules/require-main-filename/README.md +26 -0
  250. package/node_modules/require-main-filename/index.js +18 -0
  251. package/node_modules/require-main-filename/package.json +35 -0
  252. package/node_modules/set-blocking/CHANGELOG.md +26 -0
  253. package/node_modules/set-blocking/LICENSE.txt +14 -0
  254. package/node_modules/set-blocking/README.md +31 -0
  255. package/node_modules/set-blocking/index.js +7 -0
  256. package/node_modules/set-blocking/package.json +42 -0
  257. package/node_modules/string-width/index.d.ts +29 -0
  258. package/node_modules/string-width/index.js +47 -0
  259. package/node_modules/string-width/license +9 -0
  260. package/node_modules/string-width/package.json +56 -0
  261. package/node_modules/string-width/readme.md +50 -0
  262. package/node_modules/strip-ansi/index.d.ts +17 -0
  263. package/node_modules/strip-ansi/index.js +4 -0
  264. package/node_modules/strip-ansi/license +9 -0
  265. package/node_modules/strip-ansi/package.json +54 -0
  266. package/node_modules/strip-ansi/readme.md +46 -0
  267. package/node_modules/which-module/LICENSE +13 -0
  268. package/node_modules/which-module/README.md +58 -0
  269. package/node_modules/which-module/index.js +9 -0
  270. package/node_modules/which-module/package.json +41 -0
  271. package/node_modules/wrap-ansi/index.js +186 -0
  272. package/node_modules/wrap-ansi/license +9 -0
  273. package/node_modules/wrap-ansi/package.json +61 -0
  274. package/node_modules/wrap-ansi/readme.md +97 -0
  275. package/node_modules/y18n/CHANGELOG.md +35 -0
  276. package/node_modules/y18n/LICENSE +13 -0
  277. package/node_modules/y18n/README.md +109 -0
  278. package/node_modules/y18n/index.js +188 -0
  279. package/node_modules/y18n/package.json +39 -0
  280. package/node_modules/yargs/CHANGELOG.md +420 -0
  281. package/node_modules/yargs/LICENSE +21 -0
  282. package/node_modules/yargs/README.md +140 -0
  283. package/node_modules/yargs/build/lib/apply-extends.d.ts +2 -0
  284. package/node_modules/yargs/build/lib/apply-extends.js +65 -0
  285. package/node_modules/yargs/build/lib/argsert.d.ts +2 -0
  286. package/node_modules/yargs/build/lib/argsert.js +65 -0
  287. package/node_modules/yargs/build/lib/command.d.ts +64 -0
  288. package/node_modules/yargs/build/lib/command.js +416 -0
  289. package/node_modules/yargs/build/lib/common-types.d.ts +36 -0
  290. package/node_modules/yargs/build/lib/common-types.js +25 -0
  291. package/node_modules/yargs/build/lib/completion-templates.d.ts +2 -0
  292. package/node_modules/yargs/build/lib/completion-templates.js +50 -0
  293. package/node_modules/yargs/build/lib/completion.d.ts +21 -0
  294. package/node_modules/yargs/build/lib/completion.js +135 -0
  295. package/node_modules/yargs/build/lib/is-promise.d.ts +1 -0
  296. package/node_modules/yargs/build/lib/is-promise.js +9 -0
  297. package/node_modules/yargs/build/lib/levenshtein.d.ts +1 -0
  298. package/node_modules/yargs/build/lib/levenshtein.js +58 -0
  299. package/node_modules/yargs/build/lib/middleware.d.ts +10 -0
  300. package/node_modules/yargs/build/lib/middleware.js +57 -0
  301. package/node_modules/yargs/build/lib/obj-filter.d.ts +1 -0
  302. package/node_modules/yargs/build/lib/obj-filter.js +14 -0
  303. package/node_modules/yargs/build/lib/parse-command.d.ts +11 -0
  304. package/node_modules/yargs/build/lib/parse-command.js +36 -0
  305. package/node_modules/yargs/build/lib/process-argv.d.ts +2 -0
  306. package/node_modules/yargs/build/lib/process-argv.js +31 -0
  307. package/node_modules/yargs/build/lib/usage.d.ts +49 -0
  308. package/node_modules/yargs/build/lib/usage.js +540 -0
  309. package/node_modules/yargs/build/lib/validation.d.ts +34 -0
  310. package/node_modules/yargs/build/lib/validation.js +330 -0
  311. package/node_modules/yargs/build/lib/yargs.d.ts +274 -0
  312. package/node_modules/yargs/build/lib/yargs.js +1190 -0
  313. package/node_modules/yargs/build/lib/yerror.d.ts +4 -0
  314. package/node_modules/yargs/build/lib/yerror.js +11 -0
  315. package/node_modules/yargs/index.js +40 -0
  316. package/node_modules/yargs/locales/be.json +46 -0
  317. package/node_modules/yargs/locales/de.json +46 -0
  318. package/node_modules/yargs/locales/en.json +51 -0
  319. package/node_modules/yargs/locales/es.json +46 -0
  320. package/node_modules/yargs/locales/fi.json +49 -0
  321. package/node_modules/yargs/locales/fr.json +53 -0
  322. package/node_modules/yargs/locales/hi.json +49 -0
  323. package/node_modules/yargs/locales/hu.json +46 -0
  324. package/node_modules/yargs/locales/id.json +50 -0
  325. package/node_modules/yargs/locales/it.json +46 -0
  326. package/node_modules/yargs/locales/ja.json +51 -0
  327. package/node_modules/yargs/locales/ko.json +49 -0
  328. package/node_modules/yargs/locales/nb.json +44 -0
  329. package/node_modules/yargs/locales/nl.json +49 -0
  330. package/node_modules/yargs/locales/nn.json +44 -0
  331. package/node_modules/yargs/locales/pirate.json +13 -0
  332. package/node_modules/yargs/locales/pl.json +49 -0
  333. package/node_modules/yargs/locales/pt.json +45 -0
  334. package/node_modules/yargs/locales/pt_BR.json +48 -0
  335. package/node_modules/yargs/locales/ru.json +46 -0
  336. package/node_modules/yargs/locales/th.json +46 -0
  337. package/node_modules/yargs/locales/tr.json +48 -0
  338. package/node_modules/yargs/locales/zh_CN.json +48 -0
  339. package/node_modules/yargs/locales/zh_TW.json +47 -0
  340. package/node_modules/yargs/package.json +92 -0
  341. package/node_modules/yargs/yargs.js +14 -0
  342. package/node_modules/yargs-parser/CHANGELOG.md +601 -0
  343. package/node_modules/yargs-parser/LICENSE.txt +14 -0
  344. package/node_modules/yargs-parser/README.md +449 -0
  345. package/node_modules/yargs-parser/index.js +1032 -0
  346. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +40 -0
  347. package/node_modules/yargs-parser/package.json +46 -0
  348. package/package.json +77 -4
package/lib/client.js ADDED
@@ -0,0 +1,706 @@
1
+ // DSH Mobile Remote — 桌面 GUI 客户端模块
2
+ // 在 dsh 设置页注册"连接移动端设备"页:显示供手机 App 扫码连接的二维码。
3
+ //
4
+ // 打包形态:window.__ModuleLoader__.load({ id, factory })(dsh-client-modules 约定)。
5
+ // 手写 React.createElement(无 JSX/构建链),依赖由 dsh 客户端运行时提供。
6
+ // 数据来源:GET /m/api/qr-config(仅 loopback 可访问,返回地址+口令+下载码数据)。
7
+ //
8
+ // #35 连接页两个选项卡(设计稿 §5.7):
9
+ // - 默认「已安装 App」:现有连接二维码(`DSHREMOTE|地址|口令`)+ 地址/口令信息;
10
+ // - 「未安装 App」:下载二维码,内容 = 宿主下发的 `appDownloadUrl`(**不含口令**);
11
+ // 未就绪时显示状态文案,而不是生成指向错误地址的二维码。
12
+ // 两种二维码不可混用;浏览器端只消费 qr-config 数据,不自行拼地址、不回显/复制口令到下载码。
13
+
14
+ window.__ModuleLoader__.load({
15
+ id: "@marktrue/dsh-mobile-remote",
16
+ factory: (require) => {
17
+ var module = { exports: {} };
18
+ var exports = module.exports;
19
+
20
+ var react = require("react");
21
+ var jsxRuntime = require("react/jsx-runtime");
22
+
23
+ var useState = react.useState;
24
+ var useEffect = react.useEffect;
25
+
26
+ /** 所需服务:slots(设置页 slot 注册)。 */
27
+ exports.inject = ["slots"];
28
+
29
+ var QR_CONFIG = "/m/api/qr-config";
30
+ var QR_IMAGE = "/m/qr.png";
31
+
32
+ var rowStyle = {
33
+ display: "flex",
34
+ alignItems: "center",
35
+ gap: "10px",
36
+ padding: "10px 0",
37
+ borderBottom: "1px solid var(--dsw-alias-divider-strong, rgba(128,128,128,.18))",
38
+ fontSize: "13.5px",
39
+ };
40
+ var btnStyle = {
41
+ marginLeft: "auto",
42
+ flex: "none",
43
+ padding: "4px 12px",
44
+ borderRadius: "8px",
45
+ border: "1px solid var(--dsw-alias-border-strong, rgba(128,128,128,.35))",
46
+ background: "none",
47
+ cursor: "pointer",
48
+ font: "inherit",
49
+ fontSize: "12px",
50
+ color: "inherit",
51
+ };
52
+
53
+ function toast(text) {
54
+ var el = document.getElementById("dsh-mr-toast");
55
+ if (!el) {
56
+ el = document.createElement("div");
57
+ el.id = "dsh-mr-toast";
58
+ el.style.cssText =
59
+ "position:fixed;left:50%;bottom:48px;transform:translateX(-50%);" +
60
+ "background:rgba(31,35,41,.92);color:#fff;border-radius:999px;" +
61
+ "padding:8px 16px;font-size:13px;z-index:9999;opacity:0;" +
62
+ "transition:opacity .2s;pointer-events:none;";
63
+ document.body.appendChild(el);
64
+ }
65
+ el.textContent = text;
66
+ el.style.opacity = "1";
67
+ clearTimeout(el._h);
68
+ el._h = setTimeout(function () {
69
+ el.style.opacity = "0";
70
+ }, 1600);
71
+ }
72
+
73
+ function fallbackCopy(text) {
74
+ // 非安全上下文(http 局域网 IP)下 Clipboard API 不可用,用 textarea + execCommand 降级
75
+ var ta = document.createElement("textarea");
76
+ ta.value = text;
77
+ ta.style.position = "fixed";
78
+ ta.style.opacity = "0";
79
+ document.body.appendChild(ta);
80
+ ta.select();
81
+ try {
82
+ document.execCommand("copy");
83
+ } catch (_) {
84
+ /* 忽略 */
85
+ }
86
+ ta.remove();
87
+ }
88
+
89
+ function copyText(text) {
90
+ var done = function () {
91
+ toast("已复制");
92
+ };
93
+ if (navigator.clipboard && window.isSecureContext) {
94
+ navigator.clipboard.writeText(text).then(
95
+ done,
96
+ function () {
97
+ fallbackCopy(text);
98
+ done();
99
+ }
100
+ );
101
+ } else {
102
+ fallbackCopy(text);
103
+ done();
104
+ }
105
+ }
106
+
107
+ /** 复制访问口令:60 秒后自动清空剪贴板(口令是唯一凭据,防其他应用读取;尽力而为)。 */
108
+ function copyToken(token) {
109
+ copyText(token);
110
+ setTimeout(function () {
111
+ try {
112
+ if (navigator.clipboard && window.isSecureContext) {
113
+ navigator.clipboard.readText().then(function (t) {
114
+ if (t === token) navigator.clipboard.writeText("");
115
+ });
116
+ }
117
+ } catch (_) {
118
+ /* 非安全上下文等场景尽力而为 */
119
+ }
120
+ }, 60000);
121
+ }
122
+
123
+ /**
124
+ * 主机名是否为"手机不可用"地址:回环(127/8、localhost、::1、IPv4-mapped)或通配
125
+ * (0.0.0.0、::)。与 lib/desktop-form.js 的判定保持同一语义(浏览器端不共享模块)。
126
+ */
127
+ function unusablePhoneHost(hostname) {
128
+ var host = String(hostname || "").toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
129
+ if (host === "localhost" || host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
130
+ if (host === "0.0.0.0" || host === "::" || host === "0:0:0:0:0:0:0:0") return true;
131
+ if (host.indexOf("::ffff:") === 0) {
132
+ // IPv4-mapped:点分形式递归;URL 规范化后的十六进制形式(::ffff:7f00:1)解析最后 32 位。
133
+ var mapped = host.slice(7);
134
+ if (mapped.indexOf(".") !== -1) return unusablePhoneHost(mapped);
135
+ var groups = mapped.split(":");
136
+ if (groups.length === 2 && /^[0-9a-f]{1,4}$/.test(groups[0]) && /^[0-9a-f]{1,4}$/.test(groups[1])) {
137
+ var address = ((parseInt(groups[0], 16) << 16) | parseInt(groups[1], 16)) >>> 0;
138
+ return ((address >>> 24) & 0xff) === 127;
139
+ }
140
+ return false;
141
+ }
142
+ if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return false;
143
+ return Number(host.split(".")[0]) === 127;
144
+ }
145
+
146
+ /**
147
+ * 该地址能否放进手机二维码:绝对 http(s) 且主机不是回环/通配;旧宿主下发的不带 scheme
148
+ * 的裸 authority 只要不是回环/通配仍接受(兼容旧响应);空/不可解析一律不选。
149
+ */
150
+ function usablePhoneUrl(value) {
151
+ var text = String(value == null ? "" : value).trim();
152
+ if (text === "") return false;
153
+ try {
154
+ var url = new URL(text);
155
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
156
+ return !unusablePhoneHost(url.hostname);
157
+ } catch (e) {
158
+ // 兼容旧响应的裸 authority(如 192.168.1.5:3080):不以回环/通配开头即可。
159
+ return !/^(?:127\.|localhost(?::|$)|\[?::1\]?|0\.0\.0\.0(?::|$))/i.test(text);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 构造二维码载荷:`DSHREMOTE|<电脑地址(含挂载路径)>|<访问口令>`。
165
+ *
166
+ * 纯函数(不触碰 DOM/React),导出供回归测试直接调用。
167
+ *
168
+ * #36 follow-up(issue note 1509 §6):**没有手机可达候选就返回 null**——
169
+ * 删除旧实现的 `http://127.0.0.1:3080` 默认回退;服务端 connection.state 非 ready
170
+ * 时也不构码(不因空列表/环回候选造出指向本机回环的二维码)。
171
+ *
172
+ * 为什么需要 withMount 判断(PR #32 / 蔡严庆,2026-10-04):
173
+ * qr-config 响应的 urls **已自带挂载路径**——服务端 lib/index.js 的两条分支都拼了
174
+ * `${basePath}`(LAN 桥 `http://ip:3082/m`、回环 `http://ip:3080/m`)。旧实现在此处
175
+ * 再拼一次 mount,二维码里就成了 `/m/m`;App 端 `_pathOf()` 把整段路径原样当作请求
176
+ * 前缀,于是扫码连接一律 404(两种模式都中招,不只 LAN 桥)。
177
+ * 故:仅当目标 url 自身不含路径时才补 mount。
178
+ */
179
+ function buildQrPayload(data) {
180
+ var urls = Array.isArray(data && data.urls) ? data.urls : [];
181
+ var mount = String((data && data.basePath) || "/m").replace(/\/+$/, "");
182
+ var connection = data && data.connection;
183
+ if (connection && typeof connection === "object" && typeof connection.state === "string" && connection.state !== "ready") {
184
+ return null; // 宿主明确说不可用/启动中:不构码(旧响应无 connection 字段时按 urls 兼容)
185
+ }
186
+ var candidates = urls.filter(usablePhoneUrl);
187
+ // 新旧响应都容忍:connection.origins 是宿主端点视图的权威 JSON,但 qr-config 的 urls
188
+ // 才是带挂载路径的扫码地址;urls 为空时不再回退到任何猜测地址。
189
+ if (candidates.length === 0 && connection && Array.isArray(connection.origins)) {
190
+ candidates = connection.origins.filter(usablePhoneUrl);
191
+ }
192
+ if (candidates.length === 0) return null;
193
+ var qrTarget = candidates[0];
194
+ var withMount = function (t) {
195
+ try {
196
+ var p = new URL(t).pathname.replace(/\/+$/, "");
197
+ return p ? t : t + mount;
198
+ } catch (e) {
199
+ return t + mount;
200
+ }
201
+ };
202
+ return "DSHREMOTE|" + withMount(qrTarget) + "|" + String((data && data.token) ?? "");
203
+ }
204
+ exports.buildQrPayload = buildQrPayload;
205
+
206
+ /**
207
+ * 「未安装 App」下载码内容:**直接使用宿主下发的绝对地址**(qr-config.appDownloadUrl)。
208
+ *
209
+ * 设计稿 §5.6/§5.7:地址投影由宿主完成,浏览器端不自行拼地址;不是合法 http(s)
210
+ * 绝对地址一律视为未就绪(返回 null),绝不猜一个地址出来。下载码**不含口令**——
211
+ * 它与连接码是两种二维码,不可混用。
212
+ */
213
+ function buildDownloadQrContent(data) {
214
+ var url = typeof (data && data.appDownloadUrl) === "string" ? data.appDownloadUrl.trim() : "";
215
+ if (!/^https?:\/\/[^\s]+$/i.test(url)) return null;
216
+ return url;
217
+ }
218
+ exports.buildDownloadQrContent = buildDownloadQrContent;
219
+
220
+ /** 连接信息自动重取上限(与「有界重试、可取消」契约一致:1s × ≤10 次)。 */
221
+ var CONNECTION_POLL_LIMIT = 10;
222
+
223
+ /**
224
+ * 是否应继续自动重取连接信息(纯函数,供组件与测试共用):
225
+ * 仅 `connection.state==="starting"` 且次数未耗尽;ready/unavailable/无 connection 都不重取。
226
+ * 次数耗尽后由 UI 显示"自动重试已停止,请手动刷新"(NEVER 无限轮询)。
227
+ */
228
+ function shouldAutoRetry(connection, attempts) {
229
+ if (!connection || typeof connection !== "object") return false;
230
+ if (connection.state !== "starting") return false;
231
+ return Number(attempts) < CONNECTION_POLL_LIMIT;
232
+ }
233
+ exports.shouldAutoRetry = shouldAutoRetry;
234
+ exports.CONNECTION_POLL_LIMIT = CONNECTION_POLL_LIMIT;
235
+
236
+ /**
237
+ * 连接页两个选项卡的渲染模型(纯函数;测试可直接断言,不需要 DOM/React):
238
+ * - defaultTab = "installed":默认「已安装 App」(连接码含口令,仍是扫码即带口令);
239
+ * - connection.ready=false:payload/qrSrc 均为 null,前端显示 reason/hint 与刷新入口,
240
+ * **绝不**在空候选时自造回环二维码(#36 note 1509 §6);
241
+ * - download.ready=true:下载码 payload 就是 appDownloadUrl(不含口令);
242
+ * - download.ready=false:payload/qrSrc 均为 null,前端显示状态文案而非坏二维码。
243
+ */
244
+ function buildSectionModel(data) {
245
+ var urls = Array.isArray(data && data.urls) ? data.urls : [];
246
+ var mount = String((data && data.basePath) || "/m").replace(/\/+$/, "");
247
+ var token = String((data && data.token) ?? "");
248
+ var artifact = (data && data.appArtifact) || {};
249
+ var hostConnection = (data && data.connection) || {};
250
+ var hostDownload = (data && data.download) || {};
251
+ var connectionPayload = buildQrPayload(data);
252
+ var connectionReady = connectionPayload !== null;
253
+ // N5:payload=null 时 state 必须与 ready=false 一致;host 声称 ready 但地址不可用
254
+ // (例如半升级/冲突响应)降级为 unavailable,避免"ready=false 却走 ready 文案"。
255
+ var connectionState = connectionReady
256
+ ? "ready"
257
+ : (typeof hostConnection.state === "string" && hostConnection.state === "starting"
258
+ ? "starting"
259
+ : "unavailable");
260
+ var connectionReason = connectionReady
261
+ ? ""
262
+ : (typeof hostConnection.reason === "string" && hostConnection.reason !== ""
263
+ ? hostConnection.reason
264
+ : (urls.length === 0 ? "no-phone-endpoint" : "address-projection-unavailable"));
265
+ var connectionHint = connectionReady
266
+ ? ""
267
+ : (typeof hostConnection.hint === "string" && hostConnection.hint !== ""
268
+ ? hostConnection.hint
269
+ : "电脑端没有手机可达地址:请确认电脑与手机在同一局域网,且桌面形态已自动开启 LAN 桥;如仍不可用,可在电脑端显式配置 lanBridge.enabled/port 或 publicOrigin。");
270
+ var downloadUrl = buildDownloadQrContent(data);
271
+ // W5/Q3:若宿主显式给出 download 视图且不是 ready,则即使 appDownloadUrl 非空也不显示
272
+ // 下载码(防自相矛盾/半升级响应);旧响应没有 download 字段时按合法 appDownloadUrl 兜底。
273
+ if (downloadUrl && typeof hostDownload.state === "string" && hostDownload.state !== "" && hostDownload.state !== "ready") {
274
+ downloadUrl = null;
275
+ }
276
+ var downloadState = typeof hostDownload.state === "string" && hostDownload.state !== ""
277
+ ? hostDownload.state
278
+ : (typeof artifact.state === "string" && artifact.state !== "" ? artifact.state : "missing");
279
+ var hint = typeof hostDownload.hint === "string" && hostDownload.hint !== ""
280
+ ? hostDownload.hint
281
+ : (typeof artifact.hint === "string" ? artifact.hint : "");
282
+ var downloadReason = typeof hostDownload.reason === "string" && hostDownload.reason !== ""
283
+ ? hostDownload.reason
284
+ : (typeof artifact.reason === "string" && artifact.reason !== "" ? artifact.reason : null);
285
+ var downloadMessage = hint !== ""
286
+ ? "暂时无法生成下载二维码:" + hint
287
+ : downloadState === "mismatch"
288
+ ? "暂时无法生成下载二维码:安装包与发布清单不一致,请在电脑端重新安装同一次发布的完整插件包。"
289
+ : "暂时无法生成下载二维码:App 安装包尚未就绪,请稍后重试或在电脑诊断页检查 APK 分发状态。";
290
+ return {
291
+ defaultTab: "installed",
292
+ tabs: [
293
+ { id: "installed", label: "已安装 App" },
294
+ { id: "download", label: "未安装 App" },
295
+ ],
296
+ connection: {
297
+ ready: connectionReady,
298
+ state: connectionState,
299
+ source: typeof hostConnection.source === "string" ? hostConnection.source : null,
300
+ reason: connectionReason,
301
+ hint: connectionHint,
302
+ payload: connectionPayload,
303
+ qrSrc: connectionReady ? mount + "/qr.png?text=" + encodeURIComponent(connectionPayload) : null,
304
+ urls: urls,
305
+ token: token,
306
+ message: connectionReady
307
+ ? ""
308
+ : (connectionState === "starting"
309
+ ? "LAN 桥正在启动,连接页会自动重试;也可以点「刷新连接信息」立即重试。"
310
+ : "暂时无法生成连接二维码:" + connectionHint),
311
+ },
312
+ download: downloadUrl
313
+ ? {
314
+ ready: true,
315
+ state: downloadState,
316
+ url: downloadUrl,
317
+ payload: downloadUrl,
318
+ qrSrc: mount + "/qr.png?text=" + encodeURIComponent(downloadUrl),
319
+ message: "扫码下载 App 安装包;安装完成后切到「已安装 App」重新扫码连接。",
320
+ }
321
+ : {
322
+ ready: false,
323
+ state: downloadState,
324
+ reason: downloadReason,
325
+ url: null,
326
+ payload: null,
327
+ qrSrc: null,
328
+ message: downloadMessage,
329
+ },
330
+ };
331
+ }
332
+ exports.buildSectionModel = buildSectionModel;
333
+
334
+ /** 选项卡按钮(内联样式,与设置页现有观感一致)。 */
335
+ function tabButton(tab, active, onSelect) {
336
+ return jsxRuntime.jsx(
337
+ "button",
338
+ {
339
+ style: {
340
+ padding: "6px 14px",
341
+ borderRadius: "999px",
342
+ cursor: "pointer",
343
+ font: "inherit",
344
+ fontSize: "12.5px",
345
+ border: "1px solid " + (active
346
+ ? "var(--dsw-alias-brand-primary, #4d6bfe)"
347
+ : "var(--dsw-alias-border-strong, rgba(128,128,128,.35))"),
348
+ background: active ? "var(--dsw-alias-brand-primary, #4d6bfe)" : "none",
349
+ color: active ? "#fff" : "inherit",
350
+ },
351
+ onClick: function () {
352
+ onSelect(tab.id);
353
+ },
354
+ children: tab.label,
355
+ },
356
+ tab.id
357
+ );
358
+ }
359
+
360
+ /** 二维码图 + 一行说明(连接码/下载码共用布局,但载荷完全不同)。 */
361
+ function qrBlock(qrSrc, alt, caption) {
362
+ return jsxRuntime.jsx("div", {
363
+ style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "10px", padding: "14px 0" },
364
+ children: [
365
+ jsxRuntime.jsx("img", {
366
+ src: qrSrc,
367
+ alt: alt,
368
+ style: { width: 216, height: 216, borderRadius: "12px", background: "#fff", padding: 8 },
369
+ }),
370
+ jsxRuntime.jsx("div", {
371
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
372
+ children: caption,
373
+ }),
374
+ ],
375
+ });
376
+ }
377
+
378
+ /** 「已安装 App」选项卡:连接二维码 + 地址/口令信息(行为与旧版一致)。 */
379
+ function connectionPane(model, data, onRefresh, retryExhausted) {
380
+ var conn = model.connection;
381
+ var unavailable = conn.ready !== true;
382
+ var refreshRow = jsxRuntime.jsx("div", {
383
+ style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", padding: "2px 0 0" },
384
+ children: [
385
+ jsxRuntime.jsx("button", {
386
+ style: btnStyle,
387
+ onClick: function () { if (onRefresh) onRefresh(); },
388
+ children: "刷新连接信息",
389
+ }),
390
+ jsxRuntime.jsx("span", {
391
+ style: { fontSize: "11.5px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
392
+ children: "二维码为最近一次检查结果;网络/监听变化后请刷新(桥关闭后旧码需刷新撤销)",
393
+ }),
394
+ ],
395
+ });
396
+ return jsxRuntime.jsx("div", {
397
+ style: { display: "flex", flexDirection: "column", gap: "6px" },
398
+ children: [
399
+ jsxRuntime.jsx("div", {
400
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
401
+ children: "用手机 DSH Remote App 扫描下方二维码,自动填入电脑地址与访问口令,即可远程发消息、看进度、收通知、审批决策。",
402
+ }),
403
+ data.token
404
+ ? null
405
+ : jsxRuntime.jsx("div", {
406
+ style: {
407
+ padding: "10px 12px",
408
+ borderRadius: "8px",
409
+ background: "rgba(229,72,77,.12)",
410
+ border: "1px solid rgba(229,72,77,.4)",
411
+ color: "var(--dsw-alias-text-danger, #e5484d)",
412
+ fontSize: "12.5px",
413
+ lineHeight: "1.6",
414
+ },
415
+ children:
416
+ "⚠️ 访问口令未启用:同一网络内任何设备都能连接并控制 agent,建议立即在 cordis.patch.yml 配置 authToken 后重启。",
417
+ }),
418
+ // #36 follow-up:没有真实手机端点时不画二维码(不再用回环地址伪造连接码),
419
+ // 展示稳定 reason/hint 与刷新入口;启动中则由页面有界自动重试。
420
+ unavailable
421
+ ? jsxRuntime.jsx("div", {
422
+ style: {
423
+ padding: "12px 14px",
424
+ borderRadius: "8px",
425
+ background: "rgba(230,160,40,.12)",
426
+ border: "1px solid rgba(230,160,40,.4)",
427
+ fontSize: "12.5px",
428
+ lineHeight: "1.7",
429
+ },
430
+ children: [
431
+ jsxRuntime.jsx("div", {
432
+ style: { fontWeight: 600, marginBottom: "4px" },
433
+ children: conn.state === "starting"
434
+ ? (retryExhausted ? "正在准备手机连接…(自动重试已停止)" : "正在准备手机连接…(页面会自动重试)")
435
+ : "暂时没有手机可达地址",
436
+ }),
437
+ jsxRuntime.jsx("div", {
438
+ children: conn.state === "starting" && retryExhausted
439
+ ? "自动重试已停止,请点「刷新连接信息」手动重试。"
440
+ : (conn.message || conn.hint),
441
+ }),
442
+ jsxRuntime.jsx("div", { style: { opacity: 0.75, marginTop: "4px" }, children: "原因代号:" + (conn.reason || conn.state) }),
443
+ jsxRuntime.jsx("div", {
444
+ style: { marginTop: "8px" },
445
+ children: refreshRow,
446
+ }),
447
+ ],
448
+ })
449
+ : jsxRuntime.jsx("div", {
450
+ children: [
451
+ qrBlock(conn.qrSrc, "连接二维码", "打开 App → 扫码连接 → 对准本二维码"),
452
+ refreshRow,
453
+ ],
454
+ }),
455
+ jsxRuntime.jsx("div", { style: { fontSize: "13px", fontWeight: 600, margin: "8px 0 2px" }, children: "连接信息" }),
456
+ conn.urls.length === 0
457
+ ? jsxRuntime.jsx("div", {
458
+ style: { fontSize: "12.5px", color: "var(--dsw-alias-label-tertiary, #aaa)", padding: "6px 0" },
459
+ children: "暂无手机可达地址(LAN 桥未监听且 webserver 仅回环时会这样显示)。",
460
+ })
461
+ : null,
462
+ conn.urls.map(function (u) {
463
+ return jsxRuntime.jsx(
464
+ "div",
465
+ {
466
+ style: rowStyle,
467
+ children: [
468
+ jsxRuntime.jsx("span", { style: { wordBreak: "break-all", fontFamily: "monospace", fontSize: "12.5px" }, children: u }),
469
+ jsxRuntime.jsx("button", { style: btnStyle, onClick: function () { copyText(u); }, children: "复制" }),
470
+ ],
471
+ },
472
+ u
473
+ );
474
+ }),
475
+ jsxRuntime.jsx("div", {
476
+ style: rowStyle,
477
+ children: [
478
+ jsxRuntime.jsx("span", { children: "访问口令" }),
479
+ jsxRuntime.jsx("span", {
480
+ style: { fontFamily: "monospace", fontSize: "12px", opacity: 0.75 },
481
+ children: model.connection.token
482
+ ? model.connection.token.slice(0, 6) + "…" + model.connection.token.slice(-4)
483
+ : "未启用(局域网/内网环境)",
484
+ }),
485
+ model.connection.token
486
+ ? jsxRuntime.jsx("button", {
487
+ style: btnStyle,
488
+ onClick: function () { copyToken(model.connection.token); },
489
+ children: "复制",
490
+ })
491
+ : null,
492
+ ],
493
+ }),
494
+ jsxRuntime.jsx("div", {
495
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)", marginTop: "6px" },
496
+ children: "二维码包含访问口令,仅在本机显示;请勿截屏转发。App 与手机浏览器(/m 网页)共用同一连接。",
497
+ }),
498
+ ],
499
+ });
500
+ }
501
+
502
+ /** 「未安装 App」选项卡:下载二维码(内容 = appDownloadUrl,不含口令)或状态文案。 */
503
+ function downloadPane(model, onRefresh) {
504
+ if (!model.download.ready) {
505
+ return jsxRuntime.jsx("div", {
506
+ style: { display: "flex", flexDirection: "column", gap: "8px", padding: "10px 0 16px" },
507
+ children: [
508
+ jsxRuntime.jsx("div", {
509
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
510
+ children: "还没有安装手机 App?先在手机浏览器下载安装包,安装完成后再扫码连接。",
511
+ }),
512
+ jsxRuntime.jsx("div", {
513
+ style: {
514
+ padding: "10px 12px",
515
+ borderRadius: "8px",
516
+ background: "rgba(230,160,40,.12)",
517
+ border: "1px solid rgba(230,160,40,.4)",
518
+ fontSize: "12.5px",
519
+ lineHeight: "1.6",
520
+ },
521
+ children: model.download.message,
522
+ }),
523
+ jsxRuntime.jsx("div", {
524
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
525
+ children: "安装包/手机端点就绪后点「刷新连接信息」即可重新检查(无需重开页面)。",
526
+ }),
527
+ jsxRuntime.jsx("button", {
528
+ style: Object.assign({}, btnStyle, { marginLeft: 0 }),
529
+ onClick: function () { if (onRefresh) onRefresh(); },
530
+ children: "刷新连接信息",
531
+ }),
532
+ ],
533
+ });
534
+ }
535
+ return jsxRuntime.jsx("div", {
536
+ style: { display: "flex", flexDirection: "column", gap: "6px" },
537
+ children: [
538
+ jsxRuntime.jsx("div", {
539
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
540
+ children: "用手机相机或浏览器扫描下方二维码,下载与当前插件配对的 App 安装包。",
541
+ }),
542
+ qrBlock(model.download.qrSrc, "App 下载二维码", "扫码下载 → 安装 App → 回到「已安装 App」扫码连接"),
543
+ jsxRuntime.jsx("div", {
544
+ style: rowStyle,
545
+ children: [
546
+ jsxRuntime.jsx("span", {
547
+ style: { wordBreak: "break-all", fontFamily: "monospace", fontSize: "12.5px" },
548
+ children: model.download.url,
549
+ }),
550
+ jsxRuntime.jsx("button", {
551
+ style: btnStyle,
552
+ onClick: function () { copyText(model.download.url); },
553
+ children: "复制",
554
+ }),
555
+ ],
556
+ }),
557
+ jsxRuntime.jsx("div", {
558
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)", marginTop: "6px" },
559
+ children: "下载二维码只含安装包地址,不含访问口令;连接二维码请切回「已安装 App」查看。",
560
+ }),
561
+ // W3-a/T18:下载 tab **ready** 态同样提供刷新入口与"最近一次检查"说明;
562
+ // 与连接 tab 共用同一个安全 refresh handler(重新拉取 qr-config,不自行拼地址)。
563
+ jsxRuntime.jsx("div", {
564
+ style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", padding: "2px 0 0" },
565
+ children: [
566
+ jsxRuntime.jsx("button", {
567
+ style: Object.assign({}, btnStyle, { marginLeft: 0 }),
568
+ onClick: function () { if (onRefresh) onRefresh(); },
569
+ children: "刷新连接信息",
570
+ }),
571
+ jsxRuntime.jsx("span", {
572
+ style: { fontSize: "11.5px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
573
+ children: "下载码为最近一次检查结果;网络/监听变化后请刷新(桥关闭后旧码需刷新撤销)",
574
+ }),
575
+ ],
576
+ }),
577
+ ],
578
+ });
579
+ }
580
+
581
+ /**
582
+ * 设置页 section:连接移动端设备(两个选项卡:已安装 App / 未安装 App)。
583
+ * 挂载后从 /m/api/qr-config 拉取地址、口令与下载码数据。
584
+ *
585
+ * #36 follow-up(note 1509 §6):桥启动是异步的——
586
+ * - connection.state==="starting" 时每秒重取一次,最多 10 次(≤10s)后停止并保留手动刷新;
587
+ * - 新请求会取消旧请求结果(alive/代次),组件卸载时停止计时器,不留后台轮询;
588
+ * - 成功不依赖重新打开页面:刷新拿到 ready 后立即换码。
589
+ */
590
+ function MobileDeviceSection() {
591
+ var state = useState({ status: "loading", urls: [], token: "" });
592
+ var data = state[0];
593
+ var setData = state[1];
594
+ var tabState = useState("installed");
595
+ var activeTab = tabState[0];
596
+ var setActiveTab = tabState[1];
597
+ var refreshState = useState(0);
598
+ var refreshTick = refreshState[0];
599
+ var setRefreshTick = refreshState[1];
600
+ var requestGeneration = 0;
601
+
602
+ useEffect(function () {
603
+ var alive = true;
604
+ var timer = null;
605
+ var attempts = 0;
606
+ var generation = ++requestGeneration;
607
+ var stillCurrent = function () {
608
+ return alive && generation === requestGeneration;
609
+ };
610
+ var load = function () {
611
+ fetch(QR_CONFIG, { headers: { accept: "application/json" } })
612
+ .then(function (res) {
613
+ if (!res.ok) throw new Error("HTTP " + res.status);
614
+ return res.json();
615
+ })
616
+ .then(function (body) {
617
+ if (!stillCurrent()) return;
618
+ var urls = Array.isArray(body.urls) ? body.urls : [];
619
+ // v3.0.0 review:path 从 qr-config 读取(不再硬编码 /m——插件挂载路径可配置)
620
+ // #35:下载码数据同样只从 qr-config 取,不改写/不自行拼地址
621
+ // #36 follow-up:connection/download 是端点可用性的权威视图(旧响应可能没有)。
622
+ var retry = shouldAutoRetry(body.connection, attempts);
623
+ setData({
624
+ status: "ready",
625
+ urls: urls,
626
+ token: String(body.token ?? ""),
627
+ basePath: String(body.path || "/m"),
628
+ appDownloadUrl: typeof body.appDownloadUrl === "string" ? body.appDownloadUrl : null,
629
+ appArtifact: body.appArtifact && typeof body.appArtifact === "object" ? body.appArtifact : null,
630
+ connection: body.connection && typeof body.connection === "object" ? body.connection : null,
631
+ download: body.download && typeof body.download === "object" ? body.download : null,
632
+ localUrl: typeof body.localUrl === "string" ? body.localUrl : "",
633
+ retryExhausted: Boolean(body.connection && body.connection.state === "starting" && !retry),
634
+ });
635
+ // 桥启动中:每秒重取,最多 CONNECTION_POLL_LIMIT 次后停止(避免无限后台轮询;
636
+ // 之后 UI 明确提示"自动重试已停止,请手动刷新")。
637
+ if (retry) {
638
+ attempts += 1;
639
+ timer = setTimeout(load, 1000);
640
+ }
641
+ })
642
+ .catch(function (err) {
643
+ if (!stillCurrent()) return;
644
+ setData({
645
+ status: "error",
646
+ urls: [],
647
+ token: "",
648
+ message: String(err?.message ?? err),
649
+ });
650
+ });
651
+ };
652
+ load();
653
+ return function () {
654
+ alive = false;
655
+ if (timer) clearTimeout(timer);
656
+ };
657
+ }, [refreshTick]);
658
+ var refresh = function () {
659
+ setRefreshTick(function (n) { return n + 1; });
660
+ };
661
+
662
+ if (data.status === "loading") {
663
+ return jsxRuntime.jsx("div", { style: { padding: "20px 0", color: "var(--dsw-alias-label-secondary, #888)" }, children: "加载中…" });
664
+ }
665
+ if (data.status === "error") {
666
+ return jsxRuntime.jsx("div", {
667
+ style: { padding: "20px 0", color: "var(--dsw-alias-text-danger, #e5484d)" },
668
+ children: "无法获取连接信息:" + data.message + "(确认 dsh-mobile-remote 插件已启用,且路径为 /m)",
669
+ });
670
+ }
671
+
672
+ var model = buildSectionModel(data);
673
+ var activeIsDownload = activeTab === "download";
674
+ return jsxRuntime.jsx("div", {
675
+ style: { display: "flex", flexDirection: "column", gap: "6px", padding: "4px 0 16px" },
676
+ children: [
677
+ jsxRuntime.jsx("div", {
678
+ style: { display: "flex", gap: "8px", padding: "2px 0 10px" },
679
+ children: model.tabs.map(function (tab) {
680
+ return tabButton(tab, activeTab === tab.id, setActiveTab);
681
+ }),
682
+ }),
683
+ activeIsDownload ? downloadPane(model, refresh) : connectionPane(model, data, refresh, data.retryExhausted === true),
684
+ ],
685
+ });
686
+ }
687
+
688
+ /** 注册设置页 section(id: mobile-device)。 */
689
+ function apply(ctx) {
690
+ ctx.slots.inject("settings.section", function () {
691
+ return ctx.slots.register(
692
+ {
693
+ name: "settings.section",
694
+ id: "mobile-device",
695
+ order: 90,
696
+ label: "连接移动端设备",
697
+ },
698
+ MobileDeviceSection
699
+ );
700
+ });
701
+ }
702
+ exports.apply = apply;
703
+
704
+ return module.exports;
705
+ },
706
+ });