@marktrue/dsh-mobile-remote 3.2.4-alpha.3-app → 3.2.4-alpha.3

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 (352) hide show
  1. package/CHANGELOG.md +1439 -0
  2. package/CONTRIBUTING.md +59 -0
  3. package/FAQ.md +111 -0
  4. package/README.md +254 -6
  5. package/cordis.patch.yml +14 -0
  6. package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +125 -0
  7. package/docs/01-PRD.md +148 -0
  8. package/docs/02-architecture.md +249 -0
  9. package/docs/03-api.md +1154 -0
  10. package/docs/04-security.md +100 -0
  11. package/docs/05-test-cases.md +405 -0
  12. package/docs/06-install-run.md +372 -0
  13. package/docs/07-user-manual.md +70 -0
  14. package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +86 -0
  15. package/docs/09-compatibility.md +227 -0
  16. package/docs/10-release.md +1067 -0
  17. package/docs/screenshots/01-home.png +0 -0
  18. package/docs/screenshots/02-notifications.png +0 -0
  19. package/docs/screenshots/03-sessions.png +0 -0
  20. package/docs/screenshots/04-chat.png +0 -0
  21. package/docs/screenshots/05-settings.png +0 -0
  22. package/docs/screenshots/06-new-session.png +0 -0
  23. package/docs/screenshots/pgy-appstore.jpg +0 -0
  24. package/docs/screenshots/pgy-mobile-app.jpg +0 -0
  25. package/docs/screenshots/pgy-pc-client.jpg +0 -0
  26. package/lib/account-usage.js +145 -0
  27. package/lib/app-alignment.js +112 -0
  28. package/lib/app-carrier-store.js +1972 -0
  29. package/lib/app-carrier.js +609 -0
  30. package/lib/client.js +1174 -0
  31. package/lib/desktop-form.js +738 -0
  32. package/lib/endpoint-registry.js +167 -0
  33. package/lib/git-read-bundled-provider.js +450 -0
  34. package/lib/git-read-service.js +601 -0
  35. package/lib/index.js +9622 -0
  36. package/lib/npm-update-source.js +226 -0
  37. package/lib/plugin-content-digest.js +80 -0
  38. package/lib/plugin-self-update.js +997 -0
  39. package/lib/plugin-update-controller.js +274 -0
  40. package/lib/plugin-update-log.js +164 -0
  41. package/lib/plugin-update-transaction.js +322 -0
  42. package/lib/plugin-update.js +309 -0
  43. package/lib/runtime-dependency-snapshot.js +345 -0
  44. package/lib/session-title-cache.js +123 -0
  45. package/lib/session-title-refresh.js +261 -0
  46. package/lib/tool-process.js +20 -0
  47. package/lib/update-fetch.js +24 -0
  48. package/lib/update-lock-arbiter.js +170 -0
  49. package/lib/update-supervisor.js +346 -0
  50. package/node_modules/ansi-regex/index.d.ts +37 -0
  51. package/node_modules/ansi-regex/index.js +10 -0
  52. package/node_modules/ansi-regex/license +9 -0
  53. package/node_modules/ansi-regex/package.json +55 -0
  54. package/node_modules/ansi-regex/readme.md +78 -0
  55. package/node_modules/ansi-styles/index.d.ts +345 -0
  56. package/node_modules/ansi-styles/index.js +163 -0
  57. package/node_modules/ansi-styles/license +9 -0
  58. package/node_modules/ansi-styles/package.json +56 -0
  59. package/node_modules/ansi-styles/readme.md +152 -0
  60. package/node_modules/camelcase/index.d.ts +63 -0
  61. package/node_modules/camelcase/index.js +76 -0
  62. package/node_modules/camelcase/license +9 -0
  63. package/node_modules/camelcase/package.json +43 -0
  64. package/node_modules/camelcase/readme.md +99 -0
  65. package/node_modules/cliui/CHANGELOG.md +76 -0
  66. package/node_modules/cliui/LICENSE.txt +14 -0
  67. package/node_modules/cliui/README.md +115 -0
  68. package/node_modules/cliui/index.js +354 -0
  69. package/node_modules/cliui/package.json +65 -0
  70. package/node_modules/color-convert/CHANGELOG.md +54 -0
  71. package/node_modules/color-convert/LICENSE +21 -0
  72. package/node_modules/color-convert/README.md +68 -0
  73. package/node_modules/color-convert/conversions.js +839 -0
  74. package/node_modules/color-convert/index.js +81 -0
  75. package/node_modules/color-convert/package.json +48 -0
  76. package/node_modules/color-convert/route.js +97 -0
  77. package/node_modules/color-name/LICENSE +8 -0
  78. package/node_modules/color-name/README.md +11 -0
  79. package/node_modules/color-name/index.js +152 -0
  80. package/node_modules/color-name/package.json +28 -0
  81. package/node_modules/decamelize/index.js +13 -0
  82. package/node_modules/decamelize/license +21 -0
  83. package/node_modules/decamelize/package.json +38 -0
  84. package/node_modules/decamelize/readme.md +48 -0
  85. package/node_modules/dijkstrajs/.travis.yml +4 -0
  86. package/node_modules/dijkstrajs/CONTRIBUTING.md +8 -0
  87. package/node_modules/dijkstrajs/LICENSE.md +19 -0
  88. package/node_modules/dijkstrajs/README.md +18 -0
  89. package/node_modules/dijkstrajs/dijkstra.js +165 -0
  90. package/node_modules/dijkstrajs/package.json +60 -0
  91. package/node_modules/dijkstrajs/test/dijkstra.test.js +96 -0
  92. package/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
  93. package/node_modules/emoji-regex/README.md +73 -0
  94. package/node_modules/emoji-regex/es2015/index.js +6 -0
  95. package/node_modules/emoji-regex/es2015/text.js +6 -0
  96. package/node_modules/emoji-regex/index.d.ts +23 -0
  97. package/node_modules/emoji-regex/index.js +6 -0
  98. package/node_modules/emoji-regex/package.json +50 -0
  99. package/node_modules/emoji-regex/text.js +6 -0
  100. package/node_modules/find-up/index.d.ts +137 -0
  101. package/node_modules/find-up/index.js +89 -0
  102. package/node_modules/find-up/license +9 -0
  103. package/node_modules/find-up/package.json +53 -0
  104. package/node_modules/find-up/readme.md +156 -0
  105. package/node_modules/get-caller-file/LICENSE.md +6 -0
  106. package/node_modules/get-caller-file/README.md +41 -0
  107. package/node_modules/get-caller-file/index.d.ts +2 -0
  108. package/node_modules/get-caller-file/index.js +22 -0
  109. package/node_modules/get-caller-file/index.js.map +1 -0
  110. package/node_modules/get-caller-file/package.json +42 -0
  111. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  112. package/node_modules/is-fullwidth-code-point/index.js +50 -0
  113. package/node_modules/is-fullwidth-code-point/license +9 -0
  114. package/node_modules/is-fullwidth-code-point/package.json +42 -0
  115. package/node_modules/is-fullwidth-code-point/readme.md +39 -0
  116. package/node_modules/locate-path/index.d.ts +83 -0
  117. package/node_modules/locate-path/index.js +65 -0
  118. package/node_modules/locate-path/license +9 -0
  119. package/node_modules/locate-path/package.json +45 -0
  120. package/node_modules/locate-path/readme.md +122 -0
  121. package/node_modules/p-limit/index.d.ts +38 -0
  122. package/node_modules/p-limit/index.js +57 -0
  123. package/node_modules/p-limit/license +9 -0
  124. package/node_modules/p-limit/package.json +52 -0
  125. package/node_modules/p-limit/readme.md +101 -0
  126. package/node_modules/p-locate/index.d.ts +64 -0
  127. package/node_modules/p-locate/index.js +52 -0
  128. package/node_modules/p-locate/license +9 -0
  129. package/node_modules/p-locate/package.json +53 -0
  130. package/node_modules/p-locate/readme.md +90 -0
  131. package/node_modules/p-try/index.d.ts +39 -0
  132. package/node_modules/p-try/index.js +9 -0
  133. package/node_modules/p-try/license +9 -0
  134. package/node_modules/p-try/package.json +42 -0
  135. package/node_modules/p-try/readme.md +58 -0
  136. package/node_modules/path-exists/index.d.ts +28 -0
  137. package/node_modules/path-exists/index.js +23 -0
  138. package/node_modules/path-exists/license +9 -0
  139. package/node_modules/path-exists/package.json +39 -0
  140. package/node_modules/path-exists/readme.md +52 -0
  141. package/node_modules/pngjs/.eslintignore +2 -0
  142. package/node_modules/pngjs/.eslintrc.json +19 -0
  143. package/node_modules/pngjs/.prettierignore +3 -0
  144. package/node_modules/pngjs/LICENSE +20 -0
  145. package/node_modules/pngjs/README.md +429 -0
  146. package/node_modules/pngjs/browser.js +16096 -0
  147. package/node_modules/pngjs/coverage/lcov-report/base.css +354 -0
  148. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +881 -0
  149. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +554 -0
  150. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +77 -0
  151. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +647 -0
  152. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +176 -0
  153. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +200 -0
  154. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  155. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +593 -0
  156. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +152 -0
  157. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +143 -0
  158. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +611 -0
  159. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +359 -0
  160. package/node_modules/pngjs/coverage/lcov-report/index.html +426 -0
  161. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +365 -0
  162. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +230 -0
  163. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +248 -0
  164. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +467 -0
  165. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +128 -0
  166. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +575 -0
  167. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +404 -0
  168. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +950 -0
  169. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +116 -0
  170. package/node_modules/pngjs/coverage/lcov-report/png.js.html +662 -0
  171. package/node_modules/pngjs/coverage/lcov-report/prettify.css +101 -0
  172. package/node_modules/pngjs/coverage/lcov-report/prettify.js +1007 -0
  173. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  174. package/node_modules/pngjs/coverage/lcov-report/sorter.js +169 -0
  175. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +584 -0
  176. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +215 -0
  177. package/node_modules/pngjs/coverage/lcov.info +2177 -0
  178. package/node_modules/pngjs/lib/bitmapper.js +267 -0
  179. package/node_modules/pngjs/lib/bitpacker.js +158 -0
  180. package/node_modules/pngjs/lib/chunkstream.js +189 -0
  181. package/node_modules/pngjs/lib/constants.js +32 -0
  182. package/node_modules/pngjs/lib/crc.js +40 -0
  183. package/node_modules/pngjs/lib/filter-pack.js +171 -0
  184. package/node_modules/pngjs/lib/filter-parse-async.js +24 -0
  185. package/node_modules/pngjs/lib/filter-parse-sync.js +21 -0
  186. package/node_modules/pngjs/lib/filter-parse.js +177 -0
  187. package/node_modules/pngjs/lib/format-normaliser.js +93 -0
  188. package/node_modules/pngjs/lib/interlace.js +95 -0
  189. package/node_modules/pngjs/lib/packer-async.js +50 -0
  190. package/node_modules/pngjs/lib/packer-sync.js +56 -0
  191. package/node_modules/pngjs/lib/packer.js +129 -0
  192. package/node_modules/pngjs/lib/paeth-predictor.js +16 -0
  193. package/node_modules/pngjs/lib/parser-async.js +165 -0
  194. package/node_modules/pngjs/lib/parser-sync.js +108 -0
  195. package/node_modules/pngjs/lib/parser.js +290 -0
  196. package/node_modules/pngjs/lib/png-sync.js +12 -0
  197. package/node_modules/pngjs/lib/png.js +194 -0
  198. package/node_modules/pngjs/lib/sync-inflate.js +168 -0
  199. package/node_modules/pngjs/lib/sync-reader.js +45 -0
  200. package/node_modules/pngjs/package.json +73 -0
  201. package/node_modules/qrcode/README.md +770 -0
  202. package/node_modules/qrcode/bin/qrcode +159 -0
  203. package/node_modules/qrcode/helper/to-sjis-browser.js +2 -0
  204. package/node_modules/qrcode/helper/to-sjis.js +105 -0
  205. package/node_modules/qrcode/lib/browser.js +76 -0
  206. package/node_modules/qrcode/lib/can-promise.js +7 -0
  207. package/node_modules/qrcode/lib/core/alignment-pattern.js +83 -0
  208. package/node_modules/qrcode/lib/core/alphanumeric-data.js +59 -0
  209. package/node_modules/qrcode/lib/core/bit-buffer.js +37 -0
  210. package/node_modules/qrcode/lib/core/bit-matrix.js +65 -0
  211. package/node_modules/qrcode/lib/core/byte-data.js +30 -0
  212. package/node_modules/qrcode/lib/core/error-correction-code.js +135 -0
  213. package/node_modules/qrcode/lib/core/error-correction-level.js +50 -0
  214. package/node_modules/qrcode/lib/core/finder-pattern.js +22 -0
  215. package/node_modules/qrcode/lib/core/format-info.js +29 -0
  216. package/node_modules/qrcode/lib/core/galois-field.js +69 -0
  217. package/node_modules/qrcode/lib/core/kanji-data.js +54 -0
  218. package/node_modules/qrcode/lib/core/mask-pattern.js +234 -0
  219. package/node_modules/qrcode/lib/core/mode.js +167 -0
  220. package/node_modules/qrcode/lib/core/numeric-data.js +43 -0
  221. package/node_modules/qrcode/lib/core/polynomial.js +62 -0
  222. package/node_modules/qrcode/lib/core/qrcode.js +495 -0
  223. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +56 -0
  224. package/node_modules/qrcode/lib/core/regex.js +31 -0
  225. package/node_modules/qrcode/lib/core/segments.js +330 -0
  226. package/node_modules/qrcode/lib/core/utils.js +63 -0
  227. package/node_modules/qrcode/lib/core/version-check.js +9 -0
  228. package/node_modules/qrcode/lib/core/version.js +163 -0
  229. package/node_modules/qrcode/lib/index.js +12 -0
  230. package/node_modules/qrcode/lib/renderer/canvas.js +63 -0
  231. package/node_modules/qrcode/lib/renderer/png.js +78 -0
  232. package/node_modules/qrcode/lib/renderer/svg-tag.js +81 -0
  233. package/node_modules/qrcode/lib/renderer/svg.js +19 -0
  234. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +85 -0
  235. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +49 -0
  236. package/node_modules/qrcode/lib/renderer/terminal.js +9 -0
  237. package/node_modules/qrcode/lib/renderer/utf8.js +71 -0
  238. package/node_modules/qrcode/lib/renderer/utils.js +99 -0
  239. package/node_modules/qrcode/lib/server.js +138 -0
  240. package/node_modules/qrcode/license +10 -0
  241. package/node_modules/qrcode/package.json +77 -0
  242. package/node_modules/require-directory/.jshintrc +67 -0
  243. package/node_modules/require-directory/.npmignore +1 -0
  244. package/node_modules/require-directory/.travis.yml +3 -0
  245. package/node_modules/require-directory/LICENSE +22 -0
  246. package/node_modules/require-directory/README.markdown +184 -0
  247. package/node_modules/require-directory/index.js +86 -0
  248. package/node_modules/require-directory/package.json +40 -0
  249. package/node_modules/require-main-filename/CHANGELOG.md +26 -0
  250. package/node_modules/require-main-filename/LICENSE.txt +14 -0
  251. package/node_modules/require-main-filename/README.md +26 -0
  252. package/node_modules/require-main-filename/index.js +18 -0
  253. package/node_modules/require-main-filename/package.json +35 -0
  254. package/node_modules/set-blocking/CHANGELOG.md +26 -0
  255. package/node_modules/set-blocking/LICENSE.txt +14 -0
  256. package/node_modules/set-blocking/README.md +31 -0
  257. package/node_modules/set-blocking/index.js +7 -0
  258. package/node_modules/set-blocking/package.json +42 -0
  259. package/node_modules/string-width/index.d.ts +29 -0
  260. package/node_modules/string-width/index.js +47 -0
  261. package/node_modules/string-width/license +9 -0
  262. package/node_modules/string-width/package.json +56 -0
  263. package/node_modules/string-width/readme.md +50 -0
  264. package/node_modules/strip-ansi/index.d.ts +17 -0
  265. package/node_modules/strip-ansi/index.js +4 -0
  266. package/node_modules/strip-ansi/license +9 -0
  267. package/node_modules/strip-ansi/package.json +54 -0
  268. package/node_modules/strip-ansi/readme.md +46 -0
  269. package/node_modules/which-module/LICENSE +13 -0
  270. package/node_modules/which-module/README.md +58 -0
  271. package/node_modules/which-module/index.js +9 -0
  272. package/node_modules/which-module/package.json +41 -0
  273. package/node_modules/wrap-ansi/index.js +186 -0
  274. package/node_modules/wrap-ansi/license +9 -0
  275. package/node_modules/wrap-ansi/package.json +61 -0
  276. package/node_modules/wrap-ansi/readme.md +97 -0
  277. package/node_modules/y18n/CHANGELOG.md +35 -0
  278. package/node_modules/y18n/LICENSE +13 -0
  279. package/node_modules/y18n/README.md +109 -0
  280. package/node_modules/y18n/index.js +188 -0
  281. package/node_modules/y18n/package.json +39 -0
  282. package/node_modules/yargs/CHANGELOG.md +420 -0
  283. package/node_modules/yargs/LICENSE +21 -0
  284. package/node_modules/yargs/README.md +140 -0
  285. package/node_modules/yargs/build/lib/apply-extends.d.ts +2 -0
  286. package/node_modules/yargs/build/lib/apply-extends.js +65 -0
  287. package/node_modules/yargs/build/lib/argsert.d.ts +2 -0
  288. package/node_modules/yargs/build/lib/argsert.js +65 -0
  289. package/node_modules/yargs/build/lib/command.d.ts +64 -0
  290. package/node_modules/yargs/build/lib/command.js +416 -0
  291. package/node_modules/yargs/build/lib/common-types.d.ts +36 -0
  292. package/node_modules/yargs/build/lib/common-types.js +25 -0
  293. package/node_modules/yargs/build/lib/completion-templates.d.ts +2 -0
  294. package/node_modules/yargs/build/lib/completion-templates.js +50 -0
  295. package/node_modules/yargs/build/lib/completion.d.ts +21 -0
  296. package/node_modules/yargs/build/lib/completion.js +135 -0
  297. package/node_modules/yargs/build/lib/is-promise.d.ts +1 -0
  298. package/node_modules/yargs/build/lib/is-promise.js +9 -0
  299. package/node_modules/yargs/build/lib/levenshtein.d.ts +1 -0
  300. package/node_modules/yargs/build/lib/levenshtein.js +58 -0
  301. package/node_modules/yargs/build/lib/middleware.d.ts +10 -0
  302. package/node_modules/yargs/build/lib/middleware.js +57 -0
  303. package/node_modules/yargs/build/lib/obj-filter.d.ts +1 -0
  304. package/node_modules/yargs/build/lib/obj-filter.js +14 -0
  305. package/node_modules/yargs/build/lib/parse-command.d.ts +11 -0
  306. package/node_modules/yargs/build/lib/parse-command.js +36 -0
  307. package/node_modules/yargs/build/lib/process-argv.d.ts +2 -0
  308. package/node_modules/yargs/build/lib/process-argv.js +31 -0
  309. package/node_modules/yargs/build/lib/usage.d.ts +49 -0
  310. package/node_modules/yargs/build/lib/usage.js +540 -0
  311. package/node_modules/yargs/build/lib/validation.d.ts +34 -0
  312. package/node_modules/yargs/build/lib/validation.js +330 -0
  313. package/node_modules/yargs/build/lib/yargs.d.ts +274 -0
  314. package/node_modules/yargs/build/lib/yargs.js +1190 -0
  315. package/node_modules/yargs/build/lib/yerror.d.ts +4 -0
  316. package/node_modules/yargs/build/lib/yerror.js +11 -0
  317. package/node_modules/yargs/index.js +40 -0
  318. package/node_modules/yargs/locales/be.json +46 -0
  319. package/node_modules/yargs/locales/de.json +46 -0
  320. package/node_modules/yargs/locales/en.json +51 -0
  321. package/node_modules/yargs/locales/es.json +46 -0
  322. package/node_modules/yargs/locales/fi.json +49 -0
  323. package/node_modules/yargs/locales/fr.json +53 -0
  324. package/node_modules/yargs/locales/hi.json +49 -0
  325. package/node_modules/yargs/locales/hu.json +46 -0
  326. package/node_modules/yargs/locales/id.json +50 -0
  327. package/node_modules/yargs/locales/it.json +46 -0
  328. package/node_modules/yargs/locales/ja.json +51 -0
  329. package/node_modules/yargs/locales/ko.json +49 -0
  330. package/node_modules/yargs/locales/nb.json +44 -0
  331. package/node_modules/yargs/locales/nl.json +49 -0
  332. package/node_modules/yargs/locales/nn.json +44 -0
  333. package/node_modules/yargs/locales/pirate.json +13 -0
  334. package/node_modules/yargs/locales/pl.json +49 -0
  335. package/node_modules/yargs/locales/pt.json +45 -0
  336. package/node_modules/yargs/locales/pt_BR.json +48 -0
  337. package/node_modules/yargs/locales/ru.json +46 -0
  338. package/node_modules/yargs/locales/th.json +46 -0
  339. package/node_modules/yargs/locales/tr.json +48 -0
  340. package/node_modules/yargs/locales/zh_CN.json +48 -0
  341. package/node_modules/yargs/locales/zh_TW.json +47 -0
  342. package/node_modules/yargs/package.json +92 -0
  343. package/node_modules/yargs/yargs.js +14 -0
  344. package/node_modules/yargs-parser/CHANGELOG.md +601 -0
  345. package/node_modules/yargs-parser/LICENSE.txt +14 -0
  346. package/node_modules/yargs-parser/README.md +449 -0
  347. package/node_modules/yargs-parser/index.js +1032 -0
  348. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +40 -0
  349. package/node_modules/yargs-parser/package.json +46 -0
  350. package/package.json +65 -12
  351. package/DSH-Remote-v3.2.4.apk +0 -4
  352. package/manifest.json +0 -21
package/lib/client.js ADDED
@@ -0,0 +1,1174 @@
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
+ /** Normalize the host's authenticated NPM check response for the settings panel. */
335
+ function buildPluginUpdateModel(status) {
336
+ var value = status && typeof status === "object" ? status : {};
337
+ var candidate = value.candidate && typeof value.candidate === "object" ? value.candidate : null;
338
+ var plugin = candidate && candidate.plugin && typeof candidate.plugin === "object" ? candidate.plugin : {};
339
+ var carrier = candidate && candidate.carrier && typeof candidate.carrier === "object" ? candidate.carrier : {};
340
+ var state = String(value.state || "unknown");
341
+ var transaction = value.transaction && typeof value.transaction === "object" ? value.transaction : null;
342
+ var appVersion = String(carrier.appVersion || transaction?.targetAppVersion || "");
343
+ return {
344
+ state: state,
345
+ reason: String(value.reason || ""),
346
+ candidate: candidate,
347
+ hasUpdate: state === "update-available" && Boolean(plugin.version),
348
+ canSelfUpdate: value.canSelfUpdate === true,
349
+ pluginVersion: String(plugin.version || transaction?.targetVersion || ""),
350
+ appVersion: appVersion,
351
+ needsPreparation: state === "update-available" && !appVersion,
352
+ transaction: value.transaction && typeof value.transaction === "object" ? value.transaction : null,
353
+ guidance: value.guidance && typeof value.guidance === "object" ? value.guidance : null,
354
+ checkLog: pluginUpdateCheckLogLines(value),
355
+ };
356
+ }
357
+ exports.buildPluginUpdateModel = buildPluginUpdateModel;
358
+
359
+ /** 检查日志投影:只保留有界字符串行,桌面与手机显示同一份脱敏诊断。 */
360
+ function pluginUpdateCheckLogLines(value) {
361
+ var source = value && typeof value === "object" && Array.isArray(value.checkLog) ? value.checkLog : [];
362
+ return source
363
+ .map(function (line) { return String(line == null ? "" : line).trim(); })
364
+ .filter(function (line) { return line !== ""; })
365
+ .slice(-40);
366
+ }
367
+ exports.pluginUpdateCheckLogLines = pluginUpdateCheckLogLines;
368
+
369
+ /** pnpm/宿主 stage → 面向用户的阶段名(与手机端一致)。 */
370
+ function pluginUpdateStageText(stage) {
371
+ var value = String(stage || "").toLowerCase();
372
+ var known = {
373
+ staging: "准备更新", prepare: "准备更新", download: "下载插件与载体", verify: "校验下载产物",
374
+ extract: "校验插件内容", snapshot: "生成安装快照", switch: "写入 profile 依赖",
375
+ install: "安装依赖(pnpm install)", restart: "重启宿主", "restart-pending": "等待宿主重启",
376
+ supervisor: "等待健康确认", worker: "电脑端工人进程处理中", finished: "更新完成", failed: "更新失败",
377
+ };
378
+ return known[value] || String(stage || "");
379
+ }
380
+ exports.pluginUpdateStageText = pluginUpdateStageText;
381
+
382
+ var PLUGIN_UPDATE_STEPS = ["检查候选", "下载校验", "安装依赖", "重启宿主", "健康确认", "完成"];
383
+
384
+ /** 事务 + 最新日志事件 → 六步进度(桌面与手机端同一套语义)。 */
385
+ function pluginUpdateProgress(model, lastEvent) {
386
+ var transaction = model && model.transaction && typeof model.transaction === "object" ? model.transaction : null;
387
+ var phase = String(transaction && transaction.phase || "");
388
+ var result = transaction && transaction.result && typeof transaction.result === "object" ? transaction.result : null;
389
+ var rolledBack = Boolean(transaction && transaction.rolledBack === true);
390
+ var failed = rolledBack || phase === "rolled-back" || phase === "failed" || (phase === "settled" && result && result.ok === false);
391
+ var terminal = ["settled", "failed", "rolled-back"].includes(phase);
392
+ if (failed) return { step: 4, text: "更新失败/已回滚", terminal: true, failed: true };
393
+ if (phase === "settled") return { step: 5, text: "更新完成", terminal: true, failed: false };
394
+ if (phase === "rolling-back") return { step: 4, text: "健康确认失败,正在回滚", terminal: false, failed: true };
395
+ var stage = String(lastEvent && lastEvent.stage || "").toLowerCase();
396
+ var byStage = {
397
+ staging: 0, prepare: 0, download: 1, verify: 1, extract: 1, snapshot: 1,
398
+ switch: 2, install: 2, worker: 2, restart: 3, "restart-pending": 3,
399
+ supervisor: 4, finished: 5, failed: 4,
400
+ };
401
+ if (byStage[stage] !== undefined) return { step: byStage[stage], text: pluginUpdateStageText(stage) || "处理中", terminal: false, failed: false };
402
+ var byPhase = { staging: 0, installing: 2, "restart-pending": 3 };
403
+ if (byPhase[phase] !== undefined) return { step: byPhase[phase], text: pluginUpdateProgressPhaseText(phase), terminal: false, failed: false };
404
+ return { step: 0, text: "准备中", terminal: false, failed: false };
405
+ }
406
+ function pluginUpdateProgressPhaseText(phase) {
407
+ if (phase === "staging") return "准备更新";
408
+ if (phase === "installing") return "安装依赖(pnpm install)";
409
+ if (phase === "restart-pending") return "等待宿主重启";
410
+ return pluginUpdateStageText(phase) || "处理中";
411
+ }
412
+ exports.pluginUpdateProgress = pluginUpdateProgress;
413
+ exports.pluginUpdateProgressSteps = PLUGIN_UPDATE_STEPS;
414
+
415
+ /** 安装进度事件 → 一行文本(与手机端一致)。 */
416
+ function pluginUpdateEventLine(event) {
417
+ var stage = String(event && event.stage || "update");
418
+ var stream = String(event && event.stream || "status");
419
+ var text = String(event && event.text || "");
420
+ return stream === "status" ? "[" + stage + "] " + text : "[" + stage + "][" + stream + "] " + text;
421
+ }
422
+
423
+ /** 失败原因的稳定码 → 可读文案(未知码原样显示,绝不隐藏)。 */
424
+ function updateFailureReasonText(reason) {
425
+ var value = String(reason || "");
426
+ var known = {
427
+ "npm-carrier-plugin-identity-mismatch": "载体清单与 NPM 包身份不一致(版本/大小校验失败)",
428
+ "npm-carrier-manifest-unreadable": "载体清单不可读",
429
+ "npm-carrier-extract-failed": "载体解包失败",
430
+ "npm-plugin-tarball-sha256-mismatch": "插件 tarball 字节哈希与载体清单不一致",
431
+ "npm-plugin-content-sha256-mismatch": "插件内容标识与载体清单不一致",
432
+ "npm-packument-content-sha256-mismatch": "NPM 包内容与载体清单不一致",
433
+ "npm-packument-app-version-mismatch": "NPM 包 App build 与载体清单不一致",
434
+ "npm-pack-failed": "宿主机 npm pack 下载插件/载体失败",
435
+ "npm-pack-integrity-mismatch": "npm pack 产物完整性不符(registry 返回异常)",
436
+ "npm-pack-metadata-mismatch": "npm pack 返回的包版本/完整性不符",
437
+ "npm-pack-tarball-missing": "npm pack 产物缺失",
438
+ "install-failed": "pnpm 安装失败",
439
+ "health-check-failed": "新插件健康检查失败,已回滚",
440
+ "rollback-failed": "回滚失败,需要人工处理",
441
+ "restart-unsupported": "宿主形态不支持自动重启",
442
+ };
443
+ return known[value] ? known[value] + "(" + value + ")" : value;
444
+ }
445
+ exports.updateFailureReasonText = updateFailureReasonText;
446
+
447
+ /** 合并检查详情与安装详情:一个“插件更新详情”列表,桌面/手机同源。 */
448
+ function pluginUpdateDetailLines(model, events) {
449
+ var lines = pluginUpdateCheckLogLines(model).slice();
450
+ var install = (Array.isArray(events) ? events : []).map(pluginUpdateEventLine).filter(Boolean);
451
+ if (install.length) {
452
+ lines.push("# 安装详情(宿主 pnpm 输出,已脱敏)");
453
+ lines = lines.concat(install);
454
+ }
455
+ var transaction = model && model.transaction && typeof model.transaction === "object" ? model.transaction : null;
456
+ var result = transaction && transaction.result && typeof transaction.result === "object" ? transaction.result : null;
457
+ if (result && result.reason) {
458
+ lines.push("# 安装结果:" + String(transaction.phase || "failed") + " · 原因:" + updateFailureReasonText(result.reason));
459
+ }
460
+ return lines;
461
+ }
462
+ exports.pluginUpdateDetailLines = pluginUpdateDetailLines;
463
+
464
+ /** 检查不可用的可读原因:区分「npm 客户端缺失」与「registry 不可用」。 */
465
+ function updateCheckReasonText(reason) {
466
+ if (reason === "npm-client-unavailable") return "找不到 npm 客户端(请确认 DSH 进程的 PATH 包含 npm)";
467
+ if (reason === "npm-registry-unavailable") return "无法访问 npm registry(请检查主机 npm 配置与网络)";
468
+ if (reason === "npm-channel-version-missing") return "npm 上没有本机预发布通道对应的 dist-tag(展开检查日志查看查询步骤)";
469
+ if (reason === "npm-plugin-metadata-unavailable") return "读取插件版本元数据失败(展开检查日志查看查询步骤)";
470
+ if (reason === "npm-carrier-metadata-unavailable") return "读取配对载体元数据失败(展开检查日志查看查询步骤)";
471
+ return reason || "registry-unavailable";
472
+ }
473
+ exports.updateCheckReasonText = updateCheckReasonText;
474
+
475
+ function apiRoute(basePath, route) {
476
+ var mount = String(basePath || "/m").replace(/\/+$/, "");
477
+ if (!mount || mount === "/") mount = "/m";
478
+ return mount + "/api" + route;
479
+ }
480
+
481
+ /** 选项卡按钮(内联样式,与设置页现有观感一致)。 */
482
+ function tabButton(tab, active, onSelect) {
483
+ return jsxRuntime.jsx(
484
+ "button",
485
+ {
486
+ style: {
487
+ padding: "6px 14px",
488
+ borderRadius: "999px",
489
+ cursor: "pointer",
490
+ font: "inherit",
491
+ fontSize: "12.5px",
492
+ border: "1px solid " + (active
493
+ ? "var(--dsw-alias-brand-primary, #4d6bfe)"
494
+ : "var(--dsw-alias-border-strong, rgba(128,128,128,.35))"),
495
+ background: active ? "var(--dsw-alias-brand-primary, #4d6bfe)" : "none",
496
+ color: active ? "#fff" : "inherit",
497
+ },
498
+ onClick: function () {
499
+ onSelect(tab.id);
500
+ },
501
+ children: tab.label,
502
+ },
503
+ tab.id
504
+ );
505
+ }
506
+
507
+ /** 二维码图 + 一行说明(连接码/下载码共用布局,但载荷完全不同)。 */
508
+ function qrBlock(qrSrc, alt, caption) {
509
+ return jsxRuntime.jsx("div", {
510
+ style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "10px", padding: "14px 0" },
511
+ children: [
512
+ jsxRuntime.jsx("img", {
513
+ src: qrSrc,
514
+ alt: alt,
515
+ style: { width: 216, height: 216, borderRadius: "12px", background: "#fff", padding: 8 },
516
+ }),
517
+ jsxRuntime.jsx("div", {
518
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
519
+ children: caption,
520
+ }),
521
+ ],
522
+ });
523
+ }
524
+
525
+ /** 「已安装 App」选项卡:连接二维码 + 地址/口令信息(行为与旧版一致)。 */
526
+ function connectionPane(model, data, onRefresh, retryExhausted) {
527
+ var conn = model.connection;
528
+ var unavailable = conn.ready !== true;
529
+ var refreshRow = jsxRuntime.jsx("div", {
530
+ style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", padding: "2px 0 0" },
531
+ children: [
532
+ jsxRuntime.jsx("button", {
533
+ style: btnStyle,
534
+ onClick: function () { if (onRefresh) onRefresh(); },
535
+ children: "刷新连接信息",
536
+ }),
537
+ jsxRuntime.jsx("span", {
538
+ style: { fontSize: "11.5px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
539
+ children: "二维码为最近一次检查结果;网络/监听变化后请刷新(桥关闭后旧码需刷新撤销)",
540
+ }),
541
+ ],
542
+ });
543
+ return jsxRuntime.jsx("div", {
544
+ style: { display: "flex", flexDirection: "column", gap: "6px" },
545
+ children: [
546
+ jsxRuntime.jsx("div", {
547
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
548
+ children: "用手机 DSH Remote App 扫描下方二维码,自动填入电脑地址与访问口令,即可远程发消息、看进度、收通知、审批决策。",
549
+ }),
550
+ data.token
551
+ ? null
552
+ : jsxRuntime.jsx("div", {
553
+ style: {
554
+ padding: "10px 12px",
555
+ borderRadius: "8px",
556
+ background: "rgba(229,72,77,.12)",
557
+ border: "1px solid rgba(229,72,77,.4)",
558
+ color: "var(--dsw-alias-text-danger, #e5484d)",
559
+ fontSize: "12.5px",
560
+ lineHeight: "1.6",
561
+ },
562
+ children:
563
+ "⚠️ 访问口令未启用:同一网络内任何设备都能连接并控制 agent,建议立即在 cordis.patch.yml 配置 authToken 后重启。",
564
+ }),
565
+ // #36 follow-up:没有真实手机端点时不画二维码(不再用回环地址伪造连接码),
566
+ // 展示稳定 reason/hint 与刷新入口;启动中则由页面有界自动重试。
567
+ unavailable
568
+ ? jsxRuntime.jsx("div", {
569
+ style: {
570
+ padding: "12px 14px",
571
+ borderRadius: "8px",
572
+ background: "rgba(230,160,40,.12)",
573
+ border: "1px solid rgba(230,160,40,.4)",
574
+ fontSize: "12.5px",
575
+ lineHeight: "1.7",
576
+ },
577
+ children: [
578
+ jsxRuntime.jsx("div", {
579
+ style: { fontWeight: 600, marginBottom: "4px" },
580
+ children: conn.state === "starting"
581
+ ? (retryExhausted ? "正在准备手机连接…(自动重试已停止)" : "正在准备手机连接…(页面会自动重试)")
582
+ : "暂时没有手机可达地址",
583
+ }),
584
+ jsxRuntime.jsx("div", {
585
+ children: conn.state === "starting" && retryExhausted
586
+ ? "自动重试已停止,请点「刷新连接信息」手动重试。"
587
+ : (conn.message || conn.hint),
588
+ }),
589
+ jsxRuntime.jsx("div", { style: { opacity: 0.75, marginTop: "4px" }, children: "原因代号:" + (conn.reason || conn.state) }),
590
+ jsxRuntime.jsx("div", {
591
+ style: { marginTop: "8px" },
592
+ children: refreshRow,
593
+ }),
594
+ ],
595
+ })
596
+ : jsxRuntime.jsx("div", {
597
+ children: [
598
+ qrBlock(conn.qrSrc, "连接二维码", "打开 App → 扫码连接 → 对准本二维码"),
599
+ refreshRow,
600
+ ],
601
+ }),
602
+ jsxRuntime.jsx("div", { style: { fontSize: "13px", fontWeight: 600, margin: "8px 0 2px" }, children: "连接信息" }),
603
+ conn.urls.length === 0
604
+ ? jsxRuntime.jsx("div", {
605
+ style: { fontSize: "12.5px", color: "var(--dsw-alias-label-tertiary, #aaa)", padding: "6px 0" },
606
+ children: "暂无手机可达地址(LAN 桥未监听且 webserver 仅回环时会这样显示)。",
607
+ })
608
+ : null,
609
+ conn.urls.map(function (u) {
610
+ return jsxRuntime.jsx(
611
+ "div",
612
+ {
613
+ style: rowStyle,
614
+ children: [
615
+ jsxRuntime.jsx("span", { style: { wordBreak: "break-all", fontFamily: "monospace", fontSize: "12.5px" }, children: u }),
616
+ jsxRuntime.jsx("button", { style: btnStyle, onClick: function () { copyText(u); }, children: "复制" }),
617
+ ],
618
+ },
619
+ u
620
+ );
621
+ }),
622
+ jsxRuntime.jsx("div", {
623
+ style: rowStyle,
624
+ children: [
625
+ jsxRuntime.jsx("span", { children: "访问口令" }),
626
+ jsxRuntime.jsx("span", {
627
+ style: { fontFamily: "monospace", fontSize: "12px", opacity: 0.75 },
628
+ children: model.connection.token
629
+ ? model.connection.token.slice(0, 6) + "…" + model.connection.token.slice(-4)
630
+ : "未启用(局域网/内网环境)",
631
+ }),
632
+ model.connection.token
633
+ ? jsxRuntime.jsx("button", {
634
+ style: btnStyle,
635
+ onClick: function () { copyToken(model.connection.token); },
636
+ children: "复制",
637
+ })
638
+ : null,
639
+ ],
640
+ }),
641
+ jsxRuntime.jsx("div", {
642
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)", marginTop: "6px" },
643
+ children: "二维码包含访问口令,仅在本机显示;请勿截屏转发。App 与手机浏览器(/m 网页)共用同一连接。",
644
+ }),
645
+ ],
646
+ });
647
+ }
648
+
649
+ /** 「未安装 App」选项卡:下载二维码(内容 = appDownloadUrl,不含口令)或状态文案。 */
650
+ function downloadPane(model, onRefresh) {
651
+ if (!model.download.ready) {
652
+ return jsxRuntime.jsx("div", {
653
+ style: { display: "flex", flexDirection: "column", gap: "8px", padding: "10px 0 16px" },
654
+ children: [
655
+ jsxRuntime.jsx("div", {
656
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
657
+ children: "还没有安装手机 App?先在手机浏览器下载安装包,安装完成后再扫码连接。",
658
+ }),
659
+ jsxRuntime.jsx("div", {
660
+ style: {
661
+ padding: "10px 12px",
662
+ borderRadius: "8px",
663
+ background: "rgba(230,160,40,.12)",
664
+ border: "1px solid rgba(230,160,40,.4)",
665
+ fontSize: "12.5px",
666
+ lineHeight: "1.6",
667
+ },
668
+ children: model.download.message,
669
+ }),
670
+ jsxRuntime.jsx("div", {
671
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
672
+ children: "安装包/手机端点就绪后点「刷新连接信息」即可重新检查(无需重开页面)。",
673
+ }),
674
+ jsxRuntime.jsx("button", {
675
+ style: Object.assign({}, btnStyle, { marginLeft: 0 }),
676
+ onClick: function () { if (onRefresh) onRefresh(); },
677
+ children: "刷新连接信息",
678
+ }),
679
+ ],
680
+ });
681
+ }
682
+ return jsxRuntime.jsx("div", {
683
+ style: { display: "flex", flexDirection: "column", gap: "6px" },
684
+ children: [
685
+ jsxRuntime.jsx("div", {
686
+ style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #888)", lineHeight: "1.6" },
687
+ children: "用手机相机或浏览器扫描下方二维码,下载与当前插件配对的 App 安装包。",
688
+ }),
689
+ qrBlock(model.download.qrSrc, "App 下载二维码", "扫码下载 → 安装 App → 回到「已安装 App」扫码连接"),
690
+ jsxRuntime.jsx("div", {
691
+ style: rowStyle,
692
+ children: [
693
+ jsxRuntime.jsx("span", {
694
+ style: { wordBreak: "break-all", fontFamily: "monospace", fontSize: "12.5px" },
695
+ children: model.download.url,
696
+ }),
697
+ jsxRuntime.jsx("button", {
698
+ style: btnStyle,
699
+ onClick: function () { copyText(model.download.url); },
700
+ children: "复制",
701
+ }),
702
+ ],
703
+ }),
704
+ jsxRuntime.jsx("div", {
705
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #aaa)", marginTop: "6px" },
706
+ children: "下载二维码只含安装包地址,不含访问口令;连接二维码请切回「已安装 App」查看。",
707
+ }),
708
+ // W3-a/T18:下载 tab **ready** 态同样提供刷新入口与"最近一次检查"说明;
709
+ // 与连接 tab 共用同一个安全 refresh handler(重新拉取 qr-config,不自行拼地址)。
710
+ jsxRuntime.jsx("div", {
711
+ style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", padding: "2px 0 0" },
712
+ children: [
713
+ jsxRuntime.jsx("button", {
714
+ style: Object.assign({}, btnStyle, { marginLeft: 0 }),
715
+ onClick: function () { if (onRefresh) onRefresh(); },
716
+ children: "刷新连接信息",
717
+ }),
718
+ jsxRuntime.jsx("span", {
719
+ style: { fontSize: "11.5px", color: "var(--dsw-alias-label-tertiary, #aaa)" },
720
+ children: "下载码为最近一次检查结果;网络/监听变化后请刷新(桥关闭后旧码需刷新撤销)",
721
+ }),
722
+ ],
723
+ }),
724
+ ],
725
+ });
726
+ }
727
+
728
+ function pluginUpdatePane(model, workflow, logs, logExpanded, showAllLogs, actions) {
729
+ var candidate = workflow.candidate || model.candidate || {};
730
+ var plugin = candidate.plugin && typeof candidate.plugin === "object" ? candidate.plugin : {};
731
+ var carrier = candidate.carrier && typeof candidate.carrier === "object" ? candidate.carrier : {};
732
+ var transaction = model.transaction && typeof model.transaction === "object" ? model.transaction : {};
733
+ var pluginVersion = String(workflow.transaction && transaction.targetVersion || plugin.version || model.pluginVersion || "");
734
+ var appVersion = String(workflow.transaction && transaction.targetAppVersion || carrier.appVersion || model.appVersion || "");
735
+ var events = Array.isArray(logs.events) ? logs.events : [];
736
+ var installLines = events.map(pluginUpdateEventLine).filter(Boolean);
737
+ var visibleInstallLines = installLines;
738
+ if (!showAllLogs && installLines.length > 12) {
739
+ visibleInstallLines = installLines.slice(0, 4).concat(["… 中间 " + (installLines.length - 12) + " 行已折叠 …"], installLines.slice(-8));
740
+ }
741
+ var checkLines = pluginUpdateCheckLogLines(model);
742
+ var displayLines = checkLines.concat(installLines.length ? ["# 安装详情(宿主 pnpm 输出,已脱敏)"] : [], visibleInstallLines);
743
+ var detailLineCount = pluginUpdateDetailLines(model, events).length;
744
+ var working = ["preparing", "submitting", "running"].includes(workflow.status);
745
+ var statusText = model.state === "loading" ? "正在检查 NPM 更新…"
746
+ : model.state === "update-available" ? "发现插件更新候选"
747
+ : model.state === "up-to-date" || model.state === "local-ahead" ? "当前插件已是最新"
748
+ : model.state === "disabled" ? "NPM 自动更新已关闭"
749
+ : model.state === "unavailable" ? "检查暂不可用:" + updateCheckReasonText(model.reason)
750
+ : "等待检查";
751
+ var operationTarget = workflow.transaction && (pluginVersion || appVersion)
752
+ ? jsxRuntime.jsx("div", { style: { fontSize: "12px", lineHeight: "1.6" }, children: "操作目标:插件 v" + (pluginVersion || "未知") + (appVersion ? " · App build " + appVersion : "") })
753
+ : null;
754
+ var actionButton = model.hasUpdate && model.canSelfUpdate && !["preparing", "submitting", "running", "confirming"].includes(workflow.status)
755
+ ? jsxRuntime.jsx("button", {
756
+ style: Object.assign({}, btnStyle, { marginLeft: 0 }),
757
+ disabled: working,
758
+ onClick: actions.onBegin,
759
+ children: model.needsPreparation ? "准备更新" : "更新插件",
760
+ })
761
+ : null;
762
+ var confirmBox = workflow.status === "confirming"
763
+ ? jsxRuntime.jsx("div", {
764
+ style: { padding: "10px", borderRadius: "8px", border: "1px solid var(--dsw-alias-brand-primary, #4d6bfe)", background: "rgba(77,107,254,.08)" },
765
+ children: [
766
+ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "4px" }, children: "请确认安装目标" }),
767
+ jsxRuntime.jsx("div", { style: { fontSize: "12.5px", lineHeight: "1.6" }, children: "插件 v" + pluginVersion + (appVersion ? " · App build " + appVersion : "") }),
768
+ jsxRuntime.jsx("div", { style: { fontSize: "12px", margin: "4px 0 8px", color: "var(--dsw-alias-label-secondary, #888)" }, children: "确认后电脑端会使用主机 npm 配置安装,并重启 DSH;连接短暂中断属正常。" }),
769
+ jsxRuntime.jsx("div", { style: { display: "flex", gap: "8px" }, children: [
770
+ jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0 }), onClick: actions.onCancel, children: "取消" }),
771
+ jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0, borderColor: "var(--dsw-alias-brand-primary, #4d6bfe)" }), onClick: actions.onConfirm, children: "确认并安装" }),
772
+ ] }),
773
+ ],
774
+ })
775
+ : null;
776
+ var lastEvent = events.length ? events[events.length - 1] : null;
777
+ var progress = pluginUpdateProgress(model, lastEvent);
778
+ var liveLine = lastEvent && lastEvent.text ? String(lastEvent.text).trim() : "";
779
+ if (liveLine.length > 140) liveLine = liveLine.slice(0, 140) + "…";
780
+ var progressSteps = PLUGIN_UPDATE_STEPS.map(function (name, index) {
781
+ var done = index <= progress.step;
782
+ return jsxRuntime.jsx("span", { style: { color: done ? (progress.failed ? "var(--dsw-alias-text-danger, #e5484d)" : "var(--dsw-alias-brand-primary, #4d6bfe)") : "var(--dsw-alias-label-tertiary, #9aa0a6)", fontWeight: index === progress.step ? 700 : 400, marginRight: "6px" }, children: (done ? "● " : "○ ") + name }, "update-step-" + index);
783
+ });
784
+ var progressBox = (workflow.transaction || model.transaction || events.length)
785
+ ? jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "3px" }, children: [
786
+ jsxRuntime.jsx("div", { style: { fontSize: "12px", fontWeight: 600 }, children: "当前阶段:" + progress.text }),
787
+ jsxRuntime.jsx("div", { style: { fontSize: "10.5px", display: "flex", flexWrap: "wrap" }, children: progressSteps }),
788
+ liveLine ? jsxRuntime.jsx("div", { style: { fontSize: "11px", fontFamily: "monospace", color: "var(--dsw-alias-label-secondary, #888)", whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: "最新输出:" + liveLine }) : null,
789
+ ] })
790
+ : null;
791
+ var checkLog = Array.isArray(model.checkLog) ? model.checkLog.slice(-40) : [];
792
+ var detailsVisible = detailLineCount > 0 || workflow.transaction || events.length;
793
+ var detailsPanel = detailsVisible
794
+ ? jsxRuntime.jsx("div", { style: { marginTop: "4px", display: "flex", flexDirection: "column", gap: "8px" }, children: [
795
+ jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0, alignSelf: "flex-start" }), "aria-expanded": !!logExpanded, onClick: actions.onToggleLogs,
796
+ children: (logExpanded ? "收起" : "查看") + "插件更新详情(" + detailLineCount + " 行)" }),
797
+ logExpanded ? jsxRuntime.jsx("div", { style: { border: "1px solid var(--dsw-alias-divider-strong, rgba(128,128,128,.2))", borderRadius: "8px", overflow: "hidden" }, children: displayLines.length ? displayLines.map(function (line, index) {
798
+ var command = line.charAt(0) === "$";
799
+ var note = line.charAt(0) === "#";
800
+ var failed = /^\[(failed|rolled-back|rollback)\]/.test(line);
801
+ if (command) {
802
+ return jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "6px 10px", background: "rgba(0,0,0,.05)", fontFamily: "monospace", fontSize: "11.5px" }, children: [
803
+ jsxRuntime.jsx("span", { style: { color: "#1f9d55", lineHeight: 1 }, children: "●" }),
804
+ jsxRuntime.jsx("span", { style: { flex: 1, whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: line }),
805
+ jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0, padding: "1px 8px", fontSize: "11px" }), onClick: actions.onCopyDetails, children: "复制" }),
806
+ ] }, "update-details-cmd-" + index);
807
+ }
808
+ return jsxRuntime.jsx("div", { style: { padding: "2px 10px", fontFamily: "monospace", fontSize: "11px", lineHeight: "1.55", whiteSpace: "pre-wrap", overflowWrap: "anywhere", color: failed ? "var(--dsw-alias-text-danger, #e5484d)" : note ? "var(--dsw-alias-label-tertiary, #9aa0a6)" : "inherit" }, children: line }, "update-details-line-" + index);
809
+ }) : jsxRuntime.jsx("div", { style: { padding: "6px 10px", fontFamily: "monospace", fontSize: "11px", color: "var(--dsw-alias-label-secondary, #888)" }, children: "等待 pnpm 输出…" }) }) : null,
810
+ logExpanded && installLines.length > 12 ? jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0, alignSelf: "flex-start" }), onClick: actions.onToggleAllLogs, children: showAllLogs ? "折叠中间安装日志" : "展开全部安装日志" }) : null,
811
+ ] })
812
+ : null;
813
+ return jsxRuntime.jsx("div", {
814
+ style: { marginTop: "14px", padding: "12px", borderRadius: "10px", border: "1px solid var(--dsw-alias-divider-strong, rgba(128,128,128,.2))", display: "flex", flexDirection: "column", gap: "8px" },
815
+ children: [
816
+ jsxRuntime.jsx("div", { style: { fontSize: "14px", fontWeight: 650 }, children: "电脑端插件更新" }),
817
+ jsxRuntime.jsx("div", { style: { fontSize: "12.5px", color: "var(--dsw-alias-label-secondary, #888)" }, children: statusText }),
818
+ progressBox,
819
+ model.hasUpdate ? jsxRuntime.jsx("div", { style: { fontSize: "12.5px", lineHeight: "1.6" }, children: "插件 v" + pluginVersion + (appVersion ? " · App build " + appVersion : " · App build 将在准备后核验") }) : null,
820
+ operationTarget,
821
+ model.hasUpdate && !model.canSelfUpdate ? jsxRuntime.jsx("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #888)" }, children: model.guidance?.summary || "当前安装形态需要按电脑端指引更新。" }) : null,
822
+ workflow.status === "preparing" ? jsxRuntime.jsx("div", { style: { fontSize: "12px" }, children: "正在从主机 NPM 源准备并校验 carrier;profile 尚未修改…" }) : null,
823
+ workflow.status === "submitting" ? jsxRuntime.jsx("div", { style: { fontSize: "12px" }, children: "正在提交安装事务…" }) : null,
824
+ workflow.status === "running" ? jsxRuntime.jsx("div", { style: { fontSize: "12px" }, children: "pnpm 安装/重启进行中;输出会保留并可在重连后继续查看。" }) : null,
825
+ workflow.message ? jsxRuntime.jsx("div", { style: { fontSize: "12px", color: workflow.status === "failed" ? "var(--dsw-alias-text-danger, #e5484d)" : "inherit" }, children: workflow.message }) : null,
826
+ detailsPanel,
827
+ actionButton,
828
+ confirmBox,
829
+ jsxRuntime.jsx("button", { style: Object.assign({}, btnStyle, { marginLeft: 0, alignSelf: "flex-start" }), disabled: model.state === "loading", onClick: actions.onCheck, children: model.state === "loading" ? "检查中…" : "手动检查更新" }),
830
+ ],
831
+ });
832
+ }
833
+
834
+ /**
835
+ * 设置页 section:连接移动端设备(两个选项卡:已安装 App / 未安装 App)。
836
+ * 挂载后从 /m/api/qr-config 拉取地址、口令与下载码数据。
837
+ *
838
+ * #36 follow-up(note 1509 §6):桥启动是异步的——
839
+ * - connection.state==="starting" 时每秒重取一次,最多 10 次(≤10s)后停止并保留手动刷新;
840
+ * - 新请求会取消旧请求结果(alive/代次),组件卸载时停止计时器,不留后台轮询;
841
+ * - 成功不依赖重新打开页面:刷新拿到 ready 后立即换码。
842
+ */
843
+ function MobileDeviceSection() {
844
+ var state = useState({ status: "loading", urls: [], token: "" });
845
+ var data = state[0];
846
+ var setData = state[1];
847
+ var tabState = useState("installed");
848
+ var activeTab = tabState[0];
849
+ var setActiveTab = tabState[1];
850
+ var refreshState = useState(0);
851
+ var refreshTick = refreshState[0];
852
+ var setRefreshTick = refreshState[1];
853
+ var updateCheckState = useState({ state: "loading", reason: "not-checked", hasUpdate: false, canSelfUpdate: false, candidate: null });
854
+ var updateStatus = updateCheckState[0];
855
+ var setUpdateStatus = updateCheckState[1];
856
+ var updateTickState = useState(0);
857
+ var updateTick = updateTickState[0];
858
+ var setUpdateTick = updateTickState[1];
859
+ var workflowState = useState({ status: "idle", opId: "", prepareId: "", candidate: null, transaction: false, message: "" });
860
+ var workflow = workflowState[0];
861
+ var setWorkflow = workflowState[1];
862
+ var logsState = useState({ events: [], lastSeq: 0, firstSeq: 0, truncated: false });
863
+ var logs = logsState[0];
864
+ var setLogs = logsState[1];
865
+ var logExpandedState = useState(false);
866
+ var logExpanded = logExpandedState[0];
867
+ var setLogExpanded = logExpandedState[1];
868
+ var showAllLogsState = useState(false);
869
+ var showAllLogs = showAllLogsState[0];
870
+ var setShowAllLogs = showAllLogsState[1];
871
+ // 检查日志展开状态与复制:桌面端与手机端使用同一份宿主 checkLog。
872
+ var requestGeneration = 0;
873
+
874
+ var copyUpdateDetails = function () {
875
+ var lines = pluginUpdateDetailLines(updateStatus, logs.events);
876
+ if (!lines.length) return;
877
+ var text = lines.join("\n");
878
+ try {
879
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
880
+ navigator.clipboard.writeText(text);
881
+ }
882
+ } catch (_) { /* 复制失败不影响日志展示 */ }
883
+ };
884
+
885
+ useEffect(function () {
886
+ var alive = true;
887
+ var timer = null;
888
+ var attempts = 0;
889
+ var generation = ++requestGeneration;
890
+ var stillCurrent = function () {
891
+ return alive && generation === requestGeneration;
892
+ };
893
+ var load = function () {
894
+ fetch(QR_CONFIG, { headers: { accept: "application/json" } })
895
+ .then(function (res) {
896
+ if (!res.ok) throw new Error("HTTP " + res.status);
897
+ return res.json();
898
+ })
899
+ .then(function (body) {
900
+ if (!stillCurrent()) return;
901
+ var urls = Array.isArray(body.urls) ? body.urls : [];
902
+ // v3.0.0 review:path 从 qr-config 读取(不再硬编码 /m——插件挂载路径可配置)
903
+ // #35:下载码数据同样只从 qr-config 取,不改写/不自行拼地址
904
+ // #36 follow-up:connection/download 是端点可用性的权威视图(旧响应可能没有)。
905
+ var retry = shouldAutoRetry(body.connection, attempts);
906
+ setData({
907
+ status: "ready",
908
+ urls: urls,
909
+ token: String(body.token ?? ""),
910
+ basePath: String(body.path || "/m"),
911
+ appDownloadUrl: typeof body.appDownloadUrl === "string" ? body.appDownloadUrl : null,
912
+ appArtifact: body.appArtifact && typeof body.appArtifact === "object" ? body.appArtifact : null,
913
+ connection: body.connection && typeof body.connection === "object" ? body.connection : null,
914
+ download: body.download && typeof body.download === "object" ? body.download : null,
915
+ localUrl: typeof body.localUrl === "string" ? body.localUrl : "",
916
+ retryExhausted: Boolean(body.connection && body.connection.state === "starting" && !retry),
917
+ });
918
+ // 桥启动中:每秒重取,最多 CONNECTION_POLL_LIMIT 次后停止(避免无限后台轮询;
919
+ // 之后 UI 明确提示"自动重试已停止,请手动刷新")。
920
+ if (retry) {
921
+ attempts += 1;
922
+ timer = setTimeout(load, 1000);
923
+ }
924
+ })
925
+ .catch(function (err) {
926
+ if (!stillCurrent()) return;
927
+ setData({
928
+ status: "error",
929
+ urls: [],
930
+ token: "",
931
+ message: String(err?.message ?? err),
932
+ });
933
+ });
934
+ };
935
+ load();
936
+ return function () {
937
+ alive = false;
938
+ if (timer) clearTimeout(timer);
939
+ };
940
+ }, [refreshTick]);
941
+
942
+ // Automatically check the host's NPM candidate when the connection token becomes available.
943
+ useEffect(function () {
944
+ if (data.status !== "ready" || !data.token) {
945
+ if (data.status === "ready") setUpdateStatus({ state: "unavailable", reason: "auth-token-unavailable", hasUpdate: false, canSelfUpdate: false, candidate: null });
946
+ return;
947
+ }
948
+ var alive = true;
949
+ setUpdateStatus(function (previous) { return Object.assign({}, previous, { state: "loading", reason: "checking" }); });
950
+ fetch(apiRoute(data.basePath, "/plugin-update/check"), {
951
+ method: "GET",
952
+ headers: { accept: "application/json", "x-mobile-token": data.token },
953
+ })
954
+ .then(function (res) { if (!res.ok) throw new Error("HTTP " + res.status); return res.json(); })
955
+ .then(function (body) {
956
+ if (!alive) return;
957
+ var model = buildPluginUpdateModel(body.pluginUpdate);
958
+ setUpdateStatus(model);
959
+ var tx = model.transaction;
960
+ if (tx && tx.opId) {
961
+ var terminal = ["settled", "rolled-back", "failed"].includes(String(tx.phase));
962
+ if (!terminal) setLogExpanded(true);
963
+ setWorkflow(function (previous) {
964
+ if (previous.opId === tx.opId && previous.transaction) return previous;
965
+ return { status: terminal ? (tx.phase === "settled" ? "done" : "failed") : "running",
966
+ opId: tx.opId, prepareId: "", candidate: model.candidate, transaction: true,
967
+ message: terminal ? (tx.phase === "settled" ? "最近一次插件更新已完成。" : "最近一次插件更新结束:" + tx.phase) : "接续电脑端正在进行的插件更新。" };
968
+ });
969
+ }
970
+ })
971
+ .catch(function (err) {
972
+ if (alive) setUpdateStatus({ state: "unavailable", reason: String(err?.message ?? "update-check-failed"), hasUpdate: false, canSelfUpdate: false, candidate: null });
973
+ });
974
+ return function () { alive = false; };
975
+ }, [data.status, data.basePath, data.token, updateTick]);
976
+
977
+ var refresh = function () {
978
+ setRefreshTick(function (n) { return n + 1; });
979
+ };
980
+ var checkPluginUpdate = function () { setUpdateTick(function (n) { return n + 1; }); };
981
+ var makeOperationId = function () {
982
+ if (window.crypto && typeof window.crypto.randomUUID === "function") return window.crypto.randomUUID();
983
+ return "web-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 12);
984
+ };
985
+ var postUpdateJson = function (route, payload) {
986
+ return fetch(apiRoute(data.basePath, route), {
987
+ method: "POST",
988
+ headers: { accept: "application/json", "content-type": "application/json", "x-mobile-token": data.token },
989
+ body: JSON.stringify(payload),
990
+ }).then(function (res) {
991
+ return res.json().catch(function () { return {}; }).then(function (body) {
992
+ if (!res.ok) {
993
+ var error = new Error(String(body.error || ("HTTP " + res.status)));
994
+ error.code = String(body.error || ("http-" + res.status));
995
+ error.status = res.status;
996
+ error.body = body;
997
+ throw error;
998
+ }
999
+ return body;
1000
+ });
1001
+ });
1002
+ };
1003
+ var beginPluginUpdate = function () {
1004
+ var candidate = updateStatus.candidate;
1005
+ if (!updateStatus.hasUpdate || !candidate) return;
1006
+ var opId = makeOperationId();
1007
+ setLogs({ events: [], lastSeq: 0, firstSeq: 0, truncated: false });
1008
+ setWorkflow({ status: updateStatus.needsPreparation ? "preparing" : "confirming", opId: opId,
1009
+ prepareId: "", candidate: candidate, transaction: false, message: "" });
1010
+ if (!updateStatus.needsPreparation) return;
1011
+ postUpdateJson("/plugin-update/prepare", { opId: opId })
1012
+ .then(function (body) {
1013
+ if (body.action === "prepared" && body.candidate && body.prepareId) {
1014
+ var preparedModel = buildPluginUpdateModel({ state: "update-available", canSelfUpdate: true,
1015
+ candidate: body.candidate, transaction: updateStatus.transaction });
1016
+ setUpdateStatus(preparedModel);
1017
+ setWorkflow({ status: "confirming", opId: opId, prepareId: body.prepareId,
1018
+ candidate: body.candidate, transaction: false, message: "Carrier 已验证,请确认实际 App build 后继续。" });
1019
+ } else if (body.action === "confirmation-required") {
1020
+ setWorkflow({ status: "confirming", opId: opId, prepareId: "", candidate: body.candidate || candidate,
1021
+ transaction: false, message: "请确认更新目标后继续。" });
1022
+ } else {
1023
+ setWorkflow({ status: "failed", opId: opId, prepareId: "", candidate: candidate, transaction: false,
1024
+ message: "无法准备更新:" + String(body.error || body.action || "no-update-candidate") });
1025
+ }
1026
+ })
1027
+ .catch(function (err) {
1028
+ var tx = err.body && err.body.transaction && typeof err.body.transaction === "object" ? err.body.transaction : null;
1029
+ if (tx && typeof tx.opId === "string" && tx.opId) {
1030
+ setLogs({ events: [], lastSeq: 0, firstSeq: 0, truncated: false });
1031
+ setUpdateStatus(buildPluginUpdateModel({ state: "up-to-date", canSelfUpdate: false, candidate: null, transaction: tx }));
1032
+ setWorkflow({ status: "running", opId: tx.opId, prepareId: "", candidate: null,
1033
+ transaction: true, message: "电脑端已有另一客户端启动的更新,接续查看同一事务日志。" });
1034
+ setLogExpanded(true);
1035
+ checkPluginUpdate();
1036
+ return;
1037
+ }
1038
+ setWorkflow({ status: "failed", opId: opId, prepareId: "", candidate: candidate, transaction: false,
1039
+ message: "Carrier 预检失败:" + String(err.code || "npm-preparation-failed") });
1040
+ });
1041
+ };
1042
+ var confirmPluginUpdate = function () {
1043
+ if (!workflow.opId || !workflow.candidate) return;
1044
+ var plugin = workflow.candidate.plugin || {};
1045
+ var payload = { opId: workflow.opId, targetVersion: String(plugin.version || "") };
1046
+ if (workflow.prepareId) payload.prepareId = workflow.prepareId;
1047
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "submitting", message: "正在提交电脑端安装事务…" }); });
1048
+ postUpdateJson("/plugin-update", payload)
1049
+ .then(function (body) {
1050
+ if (body.action === "accepted") {
1051
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "running", transaction: true, message: "安装/重启进行中;日志会在重连后继续显示。" }); });
1052
+ setLogExpanded(true);
1053
+ checkPluginUpdate();
1054
+ } else if (body.action === "prepare-required") {
1055
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "failed", transaction: false, message: "更新目标需要重新准备;请再次点击准备更新。" }); });
1056
+ checkPluginUpdate();
1057
+ } else if (body.action === "none") {
1058
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "idle", transaction: false, message: "检查后未发现可安装的新版本。" }); });
1059
+ checkPluginUpdate();
1060
+ } else {
1061
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "failed", transaction: false, message: "更新未接受:" + String(body.error || body.action || "unknown") }); });
1062
+ }
1063
+ })
1064
+ .catch(function (err) {
1065
+ var tx = err.body && err.body.transaction && typeof err.body.transaction === "object" ? err.body.transaction : null;
1066
+ if (tx && typeof tx.opId === "string" && tx.opId) {
1067
+ var terminal = ["settled", "rolled-back", "failed"].includes(String(tx.phase));
1068
+ setLogs({ events: [], lastSeq: 0, firstSeq: 0, truncated: false });
1069
+ setUpdateStatus(buildPluginUpdateModel({ state: "up-to-date", canSelfUpdate: false, candidate: null, transaction: tx }));
1070
+ setWorkflow({ status: terminal ? (tx.phase === "settled" ? "done" : "failed") : "running",
1071
+ opId: tx.opId, prepareId: "", candidate: null, transaction: true,
1072
+ message: "电脑端已有另一客户端启动的更新,接续查看同一事务日志。" });
1073
+ setLogExpanded(true);
1074
+ checkPluginUpdate();
1075
+ return;
1076
+ }
1077
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: "failed", transaction: false, message: "更新请求失败:" + String(err.code || "request-failed") }); });
1078
+ if (err.code === "update-busy") checkPluginUpdate();
1079
+ });
1080
+ };
1081
+ var cancelPluginUpdateConfirm = function () {
1082
+ setWorkflow({ status: "idle", opId: "", prepareId: "", candidate: null, transaction: false, message: "" });
1083
+ };
1084
+
1085
+ useEffect(function () {
1086
+ if (data.status !== "ready" || !data.token || !workflow.opId || !workflow.transaction) return;
1087
+ var alive = true;
1088
+ var timer = null;
1089
+ var cursor = 0;
1090
+ var poll = function () {
1091
+ var route = apiRoute(data.basePath, "/plugin-update/log") + "?opId=" + encodeURIComponent(workflow.opId) + "&afterSeq=" + cursor;
1092
+ fetch(route, { method: "GET", headers: { accept: "application/json", "x-mobile-token": data.token } })
1093
+ .then(function (res) { if (!res.ok) throw new Error("HTTP " + res.status); return res.json(); })
1094
+ .then(function (body) {
1095
+ if (!alive) return;
1096
+ var events = Array.isArray(body.events) ? body.events : [];
1097
+ if (Number.isSafeInteger(body.lastSeq)) cursor = Math.max(cursor, body.lastSeq);
1098
+ if (events.length) {
1099
+ setLogs(function (previous) {
1100
+ var bySeq = new Map((previous.events || []).map(function (event) { return [event.seq, event]; }));
1101
+ events.forEach(function (event) { bySeq.set(event.seq, event); });
1102
+ var merged = Array.from(bySeq.values()).sort(function (a, b) { return a.seq - b.seq; }).slice(-400);
1103
+ return { events: merged, lastSeq: cursor, firstSeq: body.firstSeq || previous.firstSeq || 0, truncated: Boolean(body.truncated) };
1104
+ });
1105
+ }
1106
+ if (["settled", "rolled-back", "failed"].includes(String(body.phase))) {
1107
+ setWorkflow(function (previous) { return Object.assign({}, previous, { status: body.phase === "settled" ? "done" : "failed", message: body.phase === "settled" ? "电脑端插件更新完成。" : "电脑端插件更新结束:" + body.phase }); });
1108
+ return;
1109
+ }
1110
+ timer = setTimeout(poll, 1000);
1111
+ })
1112
+ .catch(function () { if (alive) timer = setTimeout(poll, 1500); });
1113
+ };
1114
+ poll();
1115
+ return function () { alive = false; if (timer) clearTimeout(timer); };
1116
+ }, [data.status, data.basePath, data.token, workflow.opId, workflow.transaction]);
1117
+
1118
+ var toggleLog = function () { setLogExpanded(function (open) { return !open; }); };
1119
+ var toggleAllLogs = function () { setShowAllLogs(function (open) { return !open; }); };
1120
+
1121
+ if (data.status === "loading") {
1122
+ return jsxRuntime.jsx("div", { style: { padding: "20px 0", color: "var(--dsw-alias-label-secondary, #888)" }, children: "加载中…" });
1123
+ }
1124
+ if (data.status === "error") {
1125
+ return jsxRuntime.jsx("div", {
1126
+ style: { padding: "20px 0", color: "var(--dsw-alias-text-danger, #e5484d)" },
1127
+ children: "无法获取连接信息:" + data.message + "(确认 dsh-mobile-remote 插件已启用,且路径为 /m)",
1128
+ });
1129
+ }
1130
+
1131
+ var model = buildSectionModel(data);
1132
+ var activeIsDownload = activeTab === "download";
1133
+ return jsxRuntime.jsx("div", {
1134
+ style: { display: "flex", flexDirection: "column", gap: "6px", padding: "4px 0 16px" },
1135
+ children: [
1136
+ jsxRuntime.jsx("div", {
1137
+ style: { display: "flex", gap: "8px", padding: "2px 0 10px" },
1138
+ children: model.tabs.map(function (tab) {
1139
+ return tabButton(tab, activeTab === tab.id, setActiveTab);
1140
+ }),
1141
+ }),
1142
+ activeIsDownload ? downloadPane(model, refresh) : connectionPane(model, data, refresh, data.retryExhausted === true),
1143
+ pluginUpdatePane(updateStatus, workflow, logs, logExpanded, showAllLogs, {
1144
+ onCheck: checkPluginUpdate,
1145
+ onBegin: beginPluginUpdate,
1146
+ onConfirm: confirmPluginUpdate,
1147
+ onCancel: cancelPluginUpdateConfirm,
1148
+ onToggleLogs: toggleLog,
1149
+ onToggleAllLogs: toggleAllLogs,
1150
+ onCopyDetails: copyUpdateDetails,
1151
+ }),
1152
+ ],
1153
+ });
1154
+ }
1155
+
1156
+ /** 注册设置页 section(id: mobile-device)。 */
1157
+ function apply(ctx) {
1158
+ ctx.slots.inject("settings.section", function () {
1159
+ return ctx.slots.register(
1160
+ {
1161
+ name: "settings.section",
1162
+ id: "mobile-device",
1163
+ order: 90,
1164
+ label: "连接移动端设备",
1165
+ },
1166
+ MobileDeviceSection
1167
+ );
1168
+ });
1169
+ }
1170
+ exports.apply = apply;
1171
+
1172
+ return module.exports;
1173
+ },
1174
+ });