@marktrue/dsh-mobile-remote 3.2.3-rc.1 → 3.2.4-alpha.1-app

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 (353) hide show
  1. package/DSH-Remote-v3.2.4.apk +4 -0
  2. package/README.md +6 -254
  3. package/manifest.json +21 -0
  4. package/package.json +12 -65
  5. package/CHANGELOG.md +0 -1362
  6. package/CONTRIBUTING.md +0 -59
  7. package/FAQ.md +0 -111
  8. package/cordis.patch.yml +0 -14
  9. package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +0 -125
  10. package/docs/01-PRD.md +0 -148
  11. package/docs/02-architecture.md +0 -249
  12. package/docs/03-api.md +0 -930
  13. package/docs/04-security.md +0 -100
  14. package/docs/05-test-cases.md +0 -405
  15. package/docs/06-install-run.md +0 -372
  16. package/docs/07-user-manual.md +0 -70
  17. package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +0 -86
  18. package/docs/09-compatibility.md +0 -227
  19. package/docs/10-release.md +0 -929
  20. package/docs/screenshots/01-home.png +0 -0
  21. package/docs/screenshots/02-notifications.png +0 -0
  22. package/docs/screenshots/03-sessions.png +0 -0
  23. package/docs/screenshots/04-chat.png +0 -0
  24. package/docs/screenshots/05-settings.png +0 -0
  25. package/docs/screenshots/06-new-session.png +0 -0
  26. package/docs/screenshots/pgy-appstore.jpg +0 -0
  27. package/docs/screenshots/pgy-mobile-app.jpg +0 -0
  28. package/docs/screenshots/pgy-pc-client.jpg +0 -0
  29. package/docs/spec-app-auto-update.md +0 -143
  30. package/lib/account-usage.js +0 -145
  31. package/lib/app-alignment.js +0 -112
  32. package/lib/app-carrier-store.js +0 -1972
  33. package/lib/app-carrier.js +0 -609
  34. package/lib/client.js +0 -1174
  35. package/lib/desktop-form.js +0 -738
  36. package/lib/endpoint-registry.js +0 -165
  37. package/lib/git-read-bundled-provider.js +0 -255
  38. package/lib/git-read-service.js +0 -322
  39. package/lib/index.js +0 -9609
  40. package/lib/npm-update-source.js +0 -226
  41. package/lib/plugin-content-digest.js +0 -80
  42. package/lib/plugin-self-update.js +0 -997
  43. package/lib/plugin-update-controller.js +0 -274
  44. package/lib/plugin-update-log.js +0 -164
  45. package/lib/plugin-update-transaction.js +0 -322
  46. package/lib/plugin-update.js +0 -309
  47. package/lib/runtime-dependency-snapshot.js +0 -345
  48. package/lib/session-title-cache.js +0 -123
  49. package/lib/session-title-refresh.js +0 -261
  50. package/lib/tool-process.js +0 -20
  51. package/lib/update-fetch.js +0 -24
  52. package/lib/update-lock-arbiter.js +0 -170
  53. package/lib/update-supervisor.js +0 -346
  54. package/node_modules/ansi-regex/index.d.ts +0 -37
  55. package/node_modules/ansi-regex/index.js +0 -10
  56. package/node_modules/ansi-regex/license +0 -9
  57. package/node_modules/ansi-regex/package.json +0 -55
  58. package/node_modules/ansi-regex/readme.md +0 -78
  59. package/node_modules/ansi-styles/index.d.ts +0 -345
  60. package/node_modules/ansi-styles/index.js +0 -163
  61. package/node_modules/ansi-styles/license +0 -9
  62. package/node_modules/ansi-styles/package.json +0 -56
  63. package/node_modules/ansi-styles/readme.md +0 -152
  64. package/node_modules/camelcase/index.d.ts +0 -63
  65. package/node_modules/camelcase/index.js +0 -76
  66. package/node_modules/camelcase/license +0 -9
  67. package/node_modules/camelcase/package.json +0 -43
  68. package/node_modules/camelcase/readme.md +0 -99
  69. package/node_modules/cliui/CHANGELOG.md +0 -76
  70. package/node_modules/cliui/LICENSE.txt +0 -14
  71. package/node_modules/cliui/README.md +0 -115
  72. package/node_modules/cliui/index.js +0 -354
  73. package/node_modules/cliui/package.json +0 -65
  74. package/node_modules/color-convert/CHANGELOG.md +0 -54
  75. package/node_modules/color-convert/LICENSE +0 -21
  76. package/node_modules/color-convert/README.md +0 -68
  77. package/node_modules/color-convert/conversions.js +0 -839
  78. package/node_modules/color-convert/index.js +0 -81
  79. package/node_modules/color-convert/package.json +0 -48
  80. package/node_modules/color-convert/route.js +0 -97
  81. package/node_modules/color-name/LICENSE +0 -8
  82. package/node_modules/color-name/README.md +0 -11
  83. package/node_modules/color-name/index.js +0 -152
  84. package/node_modules/color-name/package.json +0 -28
  85. package/node_modules/decamelize/index.js +0 -13
  86. package/node_modules/decamelize/license +0 -21
  87. package/node_modules/decamelize/package.json +0 -38
  88. package/node_modules/decamelize/readme.md +0 -48
  89. package/node_modules/dijkstrajs/.travis.yml +0 -4
  90. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  91. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  92. package/node_modules/dijkstrajs/README.md +0 -18
  93. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  94. package/node_modules/dijkstrajs/package.json +0 -60
  95. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  96. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  97. package/node_modules/emoji-regex/README.md +0 -73
  98. package/node_modules/emoji-regex/es2015/index.js +0 -6
  99. package/node_modules/emoji-regex/es2015/text.js +0 -6
  100. package/node_modules/emoji-regex/index.d.ts +0 -23
  101. package/node_modules/emoji-regex/index.js +0 -6
  102. package/node_modules/emoji-regex/package.json +0 -50
  103. package/node_modules/emoji-regex/text.js +0 -6
  104. package/node_modules/find-up/index.d.ts +0 -137
  105. package/node_modules/find-up/index.js +0 -89
  106. package/node_modules/find-up/license +0 -9
  107. package/node_modules/find-up/package.json +0 -53
  108. package/node_modules/find-up/readme.md +0 -156
  109. package/node_modules/get-caller-file/LICENSE.md +0 -6
  110. package/node_modules/get-caller-file/README.md +0 -41
  111. package/node_modules/get-caller-file/index.d.ts +0 -2
  112. package/node_modules/get-caller-file/index.js +0 -22
  113. package/node_modules/get-caller-file/index.js.map +0 -1
  114. package/node_modules/get-caller-file/package.json +0 -42
  115. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  116. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  117. package/node_modules/is-fullwidth-code-point/license +0 -9
  118. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  119. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  120. package/node_modules/locate-path/index.d.ts +0 -83
  121. package/node_modules/locate-path/index.js +0 -65
  122. package/node_modules/locate-path/license +0 -9
  123. package/node_modules/locate-path/package.json +0 -45
  124. package/node_modules/locate-path/readme.md +0 -122
  125. package/node_modules/p-limit/index.d.ts +0 -38
  126. package/node_modules/p-limit/index.js +0 -57
  127. package/node_modules/p-limit/license +0 -9
  128. package/node_modules/p-limit/package.json +0 -52
  129. package/node_modules/p-limit/readme.md +0 -101
  130. package/node_modules/p-locate/index.d.ts +0 -64
  131. package/node_modules/p-locate/index.js +0 -52
  132. package/node_modules/p-locate/license +0 -9
  133. package/node_modules/p-locate/package.json +0 -53
  134. package/node_modules/p-locate/readme.md +0 -90
  135. package/node_modules/p-try/index.d.ts +0 -39
  136. package/node_modules/p-try/index.js +0 -9
  137. package/node_modules/p-try/license +0 -9
  138. package/node_modules/p-try/package.json +0 -42
  139. package/node_modules/p-try/readme.md +0 -58
  140. package/node_modules/path-exists/index.d.ts +0 -28
  141. package/node_modules/path-exists/index.js +0 -23
  142. package/node_modules/path-exists/license +0 -9
  143. package/node_modules/path-exists/package.json +0 -39
  144. package/node_modules/path-exists/readme.md +0 -52
  145. package/node_modules/pngjs/.eslintignore +0 -2
  146. package/node_modules/pngjs/.eslintrc.json +0 -19
  147. package/node_modules/pngjs/.prettierignore +0 -3
  148. package/node_modules/pngjs/LICENSE +0 -20
  149. package/node_modules/pngjs/README.md +0 -429
  150. package/node_modules/pngjs/browser.js +0 -16096
  151. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  152. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  153. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  154. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  155. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  156. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  157. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  158. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  159. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  160. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  161. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  162. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  163. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  164. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  165. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  166. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  167. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  168. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  169. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  170. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  171. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  172. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  173. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  174. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  175. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  176. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  177. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  178. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  179. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  180. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  181. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  182. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  183. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  184. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  185. package/node_modules/pngjs/lib/constants.js +0 -32
  186. package/node_modules/pngjs/lib/crc.js +0 -40
  187. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  188. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  189. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  190. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  191. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  192. package/node_modules/pngjs/lib/interlace.js +0 -95
  193. package/node_modules/pngjs/lib/packer-async.js +0 -50
  194. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  195. package/node_modules/pngjs/lib/packer.js +0 -129
  196. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  197. package/node_modules/pngjs/lib/parser-async.js +0 -165
  198. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  199. package/node_modules/pngjs/lib/parser.js +0 -290
  200. package/node_modules/pngjs/lib/png-sync.js +0 -12
  201. package/node_modules/pngjs/lib/png.js +0 -194
  202. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  203. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  204. package/node_modules/pngjs/package.json +0 -73
  205. package/node_modules/qrcode/README.md +0 -770
  206. package/node_modules/qrcode/bin/qrcode +0 -159
  207. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  208. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  209. package/node_modules/qrcode/lib/browser.js +0 -76
  210. package/node_modules/qrcode/lib/can-promise.js +0 -7
  211. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  212. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  213. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  214. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  215. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  216. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  217. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  218. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  219. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  220. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  221. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  222. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  223. package/node_modules/qrcode/lib/core/mode.js +0 -167
  224. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  225. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  226. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  227. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  228. package/node_modules/qrcode/lib/core/regex.js +0 -31
  229. package/node_modules/qrcode/lib/core/segments.js +0 -330
  230. package/node_modules/qrcode/lib/core/utils.js +0 -63
  231. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  232. package/node_modules/qrcode/lib/core/version.js +0 -163
  233. package/node_modules/qrcode/lib/index.js +0 -12
  234. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  235. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  236. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  237. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  238. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  239. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  240. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  241. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  242. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  243. package/node_modules/qrcode/lib/server.js +0 -138
  244. package/node_modules/qrcode/license +0 -10
  245. package/node_modules/qrcode/package.json +0 -77
  246. package/node_modules/require-directory/.jshintrc +0 -67
  247. package/node_modules/require-directory/.npmignore +0 -1
  248. package/node_modules/require-directory/.travis.yml +0 -3
  249. package/node_modules/require-directory/LICENSE +0 -22
  250. package/node_modules/require-directory/README.markdown +0 -184
  251. package/node_modules/require-directory/index.js +0 -86
  252. package/node_modules/require-directory/package.json +0 -40
  253. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  254. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  255. package/node_modules/require-main-filename/README.md +0 -26
  256. package/node_modules/require-main-filename/index.js +0 -18
  257. package/node_modules/require-main-filename/package.json +0 -35
  258. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  259. package/node_modules/set-blocking/LICENSE.txt +0 -14
  260. package/node_modules/set-blocking/README.md +0 -31
  261. package/node_modules/set-blocking/index.js +0 -7
  262. package/node_modules/set-blocking/package.json +0 -42
  263. package/node_modules/string-width/index.d.ts +0 -29
  264. package/node_modules/string-width/index.js +0 -47
  265. package/node_modules/string-width/license +0 -9
  266. package/node_modules/string-width/package.json +0 -56
  267. package/node_modules/string-width/readme.md +0 -50
  268. package/node_modules/strip-ansi/index.d.ts +0 -17
  269. package/node_modules/strip-ansi/index.js +0 -4
  270. package/node_modules/strip-ansi/license +0 -9
  271. package/node_modules/strip-ansi/package.json +0 -54
  272. package/node_modules/strip-ansi/readme.md +0 -46
  273. package/node_modules/which-module/LICENSE +0 -13
  274. package/node_modules/which-module/README.md +0 -58
  275. package/node_modules/which-module/index.js +0 -9
  276. package/node_modules/which-module/package.json +0 -41
  277. package/node_modules/wrap-ansi/index.js +0 -186
  278. package/node_modules/wrap-ansi/license +0 -9
  279. package/node_modules/wrap-ansi/package.json +0 -61
  280. package/node_modules/wrap-ansi/readme.md +0 -97
  281. package/node_modules/y18n/CHANGELOG.md +0 -35
  282. package/node_modules/y18n/LICENSE +0 -13
  283. package/node_modules/y18n/README.md +0 -109
  284. package/node_modules/y18n/index.js +0 -188
  285. package/node_modules/y18n/package.json +0 -39
  286. package/node_modules/yargs/CHANGELOG.md +0 -420
  287. package/node_modules/yargs/LICENSE +0 -21
  288. package/node_modules/yargs/README.md +0 -140
  289. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  290. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  291. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  292. package/node_modules/yargs/build/lib/argsert.js +0 -65
  293. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  294. package/node_modules/yargs/build/lib/command.js +0 -416
  295. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  296. package/node_modules/yargs/build/lib/common-types.js +0 -25
  297. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  298. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  299. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  300. package/node_modules/yargs/build/lib/completion.js +0 -135
  301. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  302. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  303. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  304. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  305. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  306. package/node_modules/yargs/build/lib/middleware.js +0 -57
  307. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  308. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  309. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  310. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  311. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  312. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  313. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  314. package/node_modules/yargs/build/lib/usage.js +0 -540
  315. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  316. package/node_modules/yargs/build/lib/validation.js +0 -330
  317. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  318. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  319. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  320. package/node_modules/yargs/build/lib/yerror.js +0 -11
  321. package/node_modules/yargs/index.js +0 -40
  322. package/node_modules/yargs/locales/be.json +0 -46
  323. package/node_modules/yargs/locales/de.json +0 -46
  324. package/node_modules/yargs/locales/en.json +0 -51
  325. package/node_modules/yargs/locales/es.json +0 -46
  326. package/node_modules/yargs/locales/fi.json +0 -49
  327. package/node_modules/yargs/locales/fr.json +0 -53
  328. package/node_modules/yargs/locales/hi.json +0 -49
  329. package/node_modules/yargs/locales/hu.json +0 -46
  330. package/node_modules/yargs/locales/id.json +0 -50
  331. package/node_modules/yargs/locales/it.json +0 -46
  332. package/node_modules/yargs/locales/ja.json +0 -51
  333. package/node_modules/yargs/locales/ko.json +0 -49
  334. package/node_modules/yargs/locales/nb.json +0 -44
  335. package/node_modules/yargs/locales/nl.json +0 -49
  336. package/node_modules/yargs/locales/nn.json +0 -44
  337. package/node_modules/yargs/locales/pirate.json +0 -13
  338. package/node_modules/yargs/locales/pl.json +0 -49
  339. package/node_modules/yargs/locales/pt.json +0 -45
  340. package/node_modules/yargs/locales/pt_BR.json +0 -48
  341. package/node_modules/yargs/locales/ru.json +0 -46
  342. package/node_modules/yargs/locales/th.json +0 -46
  343. package/node_modules/yargs/locales/tr.json +0 -48
  344. package/node_modules/yargs/locales/zh_CN.json +0 -48
  345. package/node_modules/yargs/locales/zh_TW.json +0 -47
  346. package/node_modules/yargs/package.json +0 -92
  347. package/node_modules/yargs/yargs.js +0 -14
  348. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  349. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  350. package/node_modules/yargs-parser/README.md +0 -449
  351. package/node_modules/yargs-parser/index.js +0 -1032
  352. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  353. package/node_modules/yargs-parser/package.json +0 -46
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,143 +0,0 @@
1
- # Spec — App 自动更新检查(更新源:GitHub Releases / dsh 运行主机)
2
-
3
- > 状态:已与 maintainer 完成 grill 确认(决策树已闭合)。实现目标分支:`feature/app-auto-update`。
4
-
5
- ## Problem Statement
6
-
7
- 用户通过手机 App(DSH Remote)远端操作 PC 上的 dsh。App 与插件迭代频繁(版本一致配对,见 README「版本与兼容」),但:
8
-
9
- 1. 用户不知道何时有新版本 —— 只能去 GitHub Releases 手动翻,而国内网络访问 GitHub 慢且不稳定;
10
- 2. 即使知道有新版本,也要自己下载 APK、手动安装,72MB 的包在手机浏览器里下载体验差;
11
- 3. 无法利用「手机本来就连着 dsh 主机(局域网/蒲公英)」这一现成通道 —— 主机上明明已经有构建好的 APK,却没有一个能让手机拿到它的渠道;
12
- 4. 安装时若签名与已装版本不一致,系统会直接拒绝并给出令人困惑的「应用未安装」,没有任何前置说明。
13
-
14
- 目标:App 能**检查到更新**,并能通过**可切换的更新源**(GitHub Releases / dsh 运行主机)**下载并安装**,全程有清晰的进度、校验与签名防护。
15
-
16
- ## Solution
17
-
18
- 在 App「设置 → 关于」增加更新能力:
19
-
20
- - **更新源**(单选,持久化,默认 GitHub):
21
- - **GitHub 源**:查询本项目 GitHub Releases 最新 tag(`v3.0.0` 形式),取 `DSH-Remote-*.apk` 资产,经 HTTPS 直连下载。网络不可达时给出明确失败提示(提示可切主机源),不内置代理/镜像。
22
- - **主机源(dsh 运行主机)**:插件新增更新目录配置与两个端点 —— `/m/api/update/manifest`(返回版本元数据)+ `/m/api/update/apk`(流式下发 APK),均走现有 `authToken` 鉴权。发布时把 APK 与 `manifest.json` 放入插件配置的目录即可。
23
- - **检查触发**:设置页「检查更新」手动按钮(明确结果:无更新 / 有新版 / 失败原因)+ 启动时自动检查(默认开,静默:命中才提示,失败静默跳过)。
24
- - **版本比较**:先比 `major.minor.patch`,相等再比 build 号(主机 manifest 精确到 `+N`;GitHub tag 无 build 视为 0)。同版本不提示;主机 manifest 版本**低于**本机视为异常(防降级)。
25
- - **下载与安装**:确认弹窗(版本 / 说明 / 大小 / 来源)→ App 内下载(进度可取消)→ 主机源校验 sha256 → **签名预检**(比对自身与下载 APK 证书 SHA-256)→ 一致则拉起系统安装器;不一致或读取异常则**取消更新**并明确提示,由用户自行处理。
26
- - **发布脚本**:新增 Linux/WSL 等价发布脚本(与现有 Windows `package-release.ps1` 等价),并让两个脚本都能生成 `manifest.json`(version 含 build / sha256 / notes)。
27
-
28
- ## User Stories
29
-
30
- 1. As a 手机用户, I want to 在设置里手动检查 App 是否有新版本, so that 我随时能知道是否有更新可装。
31
- 2. As a 手机用户, I want to 打开 App 时自动静默检查一次更新, so that 我不需要记得手动去查,有新版时自然被提醒。
32
- 3. As a 手机用户, I want to 自动检查失败时不要打扰我, so that 网络不好或 GitHub 不通时打开 App 不受影响。
33
- 4. As a 手机用户, I want to 在设置里把更新源切换为 GitHub 或 dsh 主机, so that 我能按所在网络环境选择最可靠的获取渠道。
34
- 5. As a 手机用户, I want to 首次使用默认走 GitHub Releases 源, so that 无需任何配置即可获得公开版本更新。
35
- 6. As a 手机用户, I want to 通过主机源获取更新, so that 在局域网/蒲公英里能利用手机本来就连着的主机拿到 APK,速度快且不依赖 GitHub 可达性。
36
- 7. As a 手机用户, I want to 看到「检查更新」的明确结果(没有新版本 / 有新版本 / 失败原因), so that 我知道操作是否生效,失败时知道该做什么(如切换更新源)。
37
- 8. As a 手机用户, I want to 在检测到新版本时看到确认弹窗(版本号、更新说明、文件大小、来源), so that 我在下载前能判断是否值得更新。
38
- 9. As a 手机用户, I want to 下载时看到进度与已下载量,并能随时取消, so that 大 APK(约 72MB)下载期间我有掌控感,误触可撤回。
39
- 10. As a 手机用户, I want to 主机源下载完成后文件经 sha256 校验, so that 传输损坏或文件被篡改时不会安装坏包。
40
- 11. As a 手机用户, I want to 下载的 APK 与我已装的版本签名一致才允许安装, so that 不会被「签名不一致」的系统报错搞糊涂,也不会装错来源的包。
41
- 12. As a 手机用户, I want to 在签名不一致时看到「已取消更新」的明确提示, so that 我明白为什么没装,并能自行决定(如先卸载旧版再装)。
42
- 13. As a 手机用户, I want to 首次安装更新时系统引导我授权「安装未知应用」, so that Android 8+ 的安装来源限制不会让我卡住。
43
- 14. As a 手机用户, I want to 主机源 manifest 的版本号精确到 build(如 3.0.0+8), so that 同一版本内的热修(+5/+6/+7…)也能被正确识别为可更新。
44
- 15. As a 手机用户, I want to 已装版本与最新 release 同版本(如本地 3.0.0+7 vs release v3.0.0)时不提示更新, so that 热修分支不会被误判为「需要更新」。
45
- 16. As a 手机用户, I want to 主机源版本低于已装版本时不更新并提示异常, so that 部署失误不会导致手机被降级。
46
- 17. As a 部署者, I want to 把构建好的 APK 和 manifest.json 放进插件配置的更新目录即可发布主机源更新, so that 局域网内用户一条命令都不用配置就能更新。
47
- 18. As a 部署者, I want to 插件提供带鉴权的 manifest 与 APK 端点, so that 更新通道不会暴露给未授权设备。
48
- 19. As a 部署者, I want to 在 Linux/WSL 上也能一键打包(APK + 插件 tgz + manifest.json), so that 我不依赖 Windows 的 PowerShell 发布脚本。
49
- 20. As a 部署者, I want to Windows 发布脚本与 Linux 脚本行为等价(都能生成 manifest.json), so that 团队无论在哪台机器发布,产物一致。
50
- 21. As a 维护者, I want to manifest.json 的 notes 自动取自 CHANGELOG 最新条目, so that 用户确认弹窗里的更新说明无需手写。
51
- 22. As a 手机用户, I want to 版本行旁出现常驻的「有新版本」提示, so that 自动检查命中后我一进设置页就能看到。
52
- 23. As a 手机用户, I want to 更新源切换后立即生效(检查走当前选中的源), so that 从 GitHub 切到主机源后不用重启 App。
53
- 24. As a 手机用户, I want to GitHub 源在 latest release 里找不到 APK 资产时得到明确提示, so that 不会误报「没有更新」或卡死。
54
- 25. As a 维护者, I want to 更新决策逻辑(版本比较、manifest 解析、防降级)是纯函数可单测, so that 版本规则的正确性能被自动化测试兜住。
55
-
56
- ## Implementation Decisions
57
-
58
- ### 更新源与配置
59
-
60
- - App 设置「更新源」为**单选持久化偏好**(`github` | `host`),默认 `github`。切换立即影响下一次「检查更新」。
61
- - 插件配置项 `updateDir`(字符串路径,默认空串 = 未配置主机源;部署者必须显式配置更新目录)。**manifest 是唯一权威**:App 只读 manifest,不枚举目录内 APK;插件不扫描目录里的历史版本。
62
-
63
- ### 主机源端点契约(插件侧)
64
-
65
- - `GET {path}/api/update/manifest`(现有鉴权体系):
66
- - 200 → `{ version: "3.0.0+8", apk: "DSH-Remote-v3.0.0.apk", sha256: "<hex>", size?: <字节>, notes?: "…" }`
67
- - `updateDir` 未配置 / 无 manifest / 文件缺失 → 对应错误码(App 明确提示「主机源未配置更新」)。
68
- - `GET {path}/api/update/apk`(现有鉴权体系):按 manifest 的 `apk` 文件名流式下发文件字节,含 `content-length`;读取失败回 404/500。
69
- - 两个端点都走现有 `authToken` 鉴权,与既有安全边界一致(LAN 桥现成可用)。
70
-
71
- ### GitHub 源契约
72
-
73
- - 版本查询:`GET api.github.com/repos/201222-L/dsh-mobile-remote/releases/latest`;解析 `tag_name`(去前缀 `v`)作为版本,build 视为 0。
74
- - 资产选择:取 `assets[]` 中第一个名形如 `DSH-Remote-*.apk` 的条目;无 APK 资产 → 「无可用更新」明确提示。
75
- - 下载:该资产的 `browser_download_url`,HTTPS 直连流式下载。无 sha256 校验(依赖 HTTPS + 安装器签名核对)。失败/超时 → 明确错误,建议切主机源。不内置代理/镜像。
76
-
77
- ### 版本比较规则
78
-
79
- - 解析 `major.minor.patch` 与可选 `+build`。比较顺序:`major` → `minor` → `patch` → `build`(缺失 build 视为 0)。
80
- - `needUpdate(local, remote)`:remote 任一主段更大 → 更新;主段相等且 build 更大 → 更新;相等 → 不更新;**remote 低于 local → 异常态(不更新,提示「更新源版本低于当前版本」)**。
81
- - **实现取舍(buildSet)**:远端版本串未显式带 `+build` 时(GitHub tag 必然如此),主段相等即判定「不提示」——否则本地 `3.0.0+7` vs release tag `v3.0.0` 会因 build 补 0 被误判成降级异常,违背 US15。即:build 比较仅在远端显式带 `+build` 时参与;远端显式 build 更小仍按防降级处理。
82
-
83
- ### 下载、校验与安装
84
-
85
- - 确认弹窗:版本 / notes / APK 大小 / 来源名(GitHub / 主机 · 不区分文案风格,均写来源名)。
86
- - 下载:App 内 http 流式下载到私有缓存目录,弹窗展示进度(百分比 + 已下载量),可取消(取消即清理文件)。
87
- - sha256 校验:**仅主机源**(manifest 提供 sha256),不一致 → 失败提示,不进入安装。
88
- - 签名预检(下载后、拉起安装器前):读取本机已装版本证书 SHA-256 与下载 APK 证书 SHA-256 比对:
89
- - 一致 → 拉起系统安装器(`ACTION_VIEW` + FileProvider,`requestLegacyExternalStorage` 不引入,用应用私有目录);
90
- - 不一致 → 弹「签名不一致,已取消更新」,不进入安装;
91
- - 任一签名读取异常(含无法解析下载 APK 签名)→ 保守**取消**并提示。
92
- - Android 权限与兼容:
93
- - `AndroidManifest.xml` 增加 `REQUEST_INSTALL_PACKAGES`;
94
- - 配置 FileProvider(含 `file_paths.xml`,指向缓存/私有文件目录);
95
- - Android 8+ 首次安装触发系统「安装未知应用」授权引导;
96
- - `build.gradle.kts` release 显式 `v1SigningEnabled true`(v1+v2 双签名),使 `getPackageArchiveInfo` 能可靠读取 APK 签名做预检(纯本地分发,无平台副作用)。
97
- - 签名一致性边界:**依赖系统安装器做最终签名核对**(双保险);预检只做前置防呆。
98
-
99
- ### 交互与 UI
100
-
101
- - 设置「关于」卡片新增:
102
- - 「更新源」行:host / GitHub 单选(默认 GitHub);
103
- - 「检查更新」行:手动触发,含「检查中… / 下载中…」状态;
104
- - 版本行旁「● 有新版本」常驻提示(自动检查命中后显示,直到更新或版本变更)。
105
- - 自动检查:应用启动后(连接成功场景)静默执行一次;命中 → 弹确认窗;未命中/失败 → 静默。
106
- - 手动检查:无论结果都明确反馈(「已是最新版本」/ 确认窗 / 失败原因 + 可切源的引导)。
107
-
108
- ### 发布脚本(双端等价)
109
-
110
- - 新增 Linux/WSL 等价脚本(bash),行为与 Windows `package-release.ps1` 对齐:
111
- 1. 从 `pubspec.yaml` 解析版本(`X.Y.Z+BUILD`,build 取自 `+N`);
112
- 2. 拷贝已构建 `app-release.apk` → `dist/DSH-Remote-vX.Y.Z.apk`;
113
- 3. `npm pack` 插件 → `dist/dsh-mobile-remote-vX.Y.Z.tgz`;
114
- 4. 生成 `manifest.json`:`version`(含 build)、`apk` 文件名、`sha256`(对 APK 计算)、`size`(APK 字节数,确认弹窗展示用)、`notes`(取 CHANGELOG 最新条目全文);
115
- 5. (可选参数)把 APK + manifest 拷贝到插件 `updateDir`。
116
- - manifest 生成收敛为**共享生成器** `tools/gen-manifest.js`(Node),两个发布脚本都调用之——JSON 合法性(notes 特殊字符自动转义)与无 BOM 编码由同一份代码保证,双端产出严格等价。
117
- - 同步扩展 Windows ps1,使两脚本产出等价(含 manifest 生成)。
118
-
119
- ## Testing Decisions
120
-
121
- - **主 seam(自动化必达)**:更新决策逻辑做成 **App 端纯 Dart 模块**(无 I/O:manifest / GitHub release JSON 解析、版本比较(semver + build)、需要更新判定、防降级判定、下载 URL 构造、签名校验的输入组装),在**现有 `dsh-mobile-app/test/` 单测 seam** 下用 `flutter test` 覆盖(prior art:`api_logic_test.dart`、`md_link_test.dart` —— 同款「纯逻辑模块 + 单测」形态;本轮已验证 `flutter analyze` 无问题、`flutter test` 全量通过)。
122
- - 测例方向:`3.0.0+7` vs tag `v3.0.0` → 不更新;`3.0.0+8` vs `3.0.0+7` → 更新;`3.1.0` vs `3.0.0+999` → 更新;remote 低于 local → 异常不更新;manifest 缺 build / 非法版本 → 容错解析;GitHub 资产选择(含无 APK 资产)。
123
- - **副 seam(已落地)**:发布脚本生成的 `manifest.json` 由同款 verify 脚本校验(`dsh-mobile-app/tools/verify-update-manifest.mjs`:version 含 build、sha256/size 与 APK 实算一致、notes 非空、apk 文件存在);服务端 manifest 读取 helper(`readUpdateManifest`)如需进一步提取式单测沿用 `tools/verify-*.mjs` 形态(prior art:`verify-image-sniff.mjs`)。
124
- - **明确不自动化(人工验收)**:真实 APK 下载 + 签名预检 + 系统安装器拉起 + Android 8+/11+ 授权引导 —— 需要真机/模拟器,列为发布前人工验收清单(对应 docs/06 §9 风格)。
125
- - 不考虑引入:widget 测试脚手架、`integration_test` 新 harness(不新增 seam 层)。
126
-
127
- ## Out of Scope
128
-
129
- - iOS 端任何形态的更新检查(当前 App 仅 Android)。
130
- - 周期性/定时推送式更新检查(仅启动时静默 + 手动)。
131
- - GitHub 镜像 / 代理 / 预置中转域名支持(保持直连 + 明确失败提示)。
132
- - 自动静默安装(更新必须经用户确认弹窗,不后台装)。
133
- - 目录内多版本 APK 枚举与历史版本清理(manifest 唯一权威,滞留旧文件由部署者自行清理)。
134
- - 内测渠道 / 灰度 / Play 商店发布流程。
135
- - 已装版本与下载 APK 的**包名**核对之外的加固(签名比对已覆盖主要风险)。
136
- - 对 dsh 内核侧的任何改动(纯插件 + App + 脚本)。
137
-
138
- ## Further Notes
139
-
140
- - **签名一致性**:本机 debug 签名构建的 release 与正式 keystore 签名不同;换签名需先卸载(docs/06 §8.3 既有约定)。更新签名预检正是为了把这种「签名不一致」提前到下载后、安装前说清楚,而不是让用户在系统安装器报错时困惑。
141
- - **宿主源部署形态**:发布机执行发布脚本后,把 `dist/` 产物拷入主机 `updateDir`(或脚本直接带参数写入);插件重启后主机源即可用。局域网/蒲公英场景手机直达主机,无 GitHub 依赖。
142
- - **manifest 唯一权威的另一面**:`updateDir` 里旧 APK 不会被 App 枚举到;部署者负责保持目录整洁(文档提示)。
143
- - **与既有验收清单的关系**:本 feature 的人工验收项并入 docs/06 §9 验收清单(新增「App 更新:双源检查 / 下载 / 签名预检 / 安装」一组)。
@@ -1,145 +0,0 @@
1
- // 用量与额度领域归一化:保持无运行时依赖,便于服务端单测与其它宿主复用。
2
-
3
- /** 额度窗口标签:优先使用 provider 约定名称,未知窗口保持稳定可读。 */
4
- export function usageWindowLabel(seconds) {
5
- const value = Number(seconds);
6
- if (!Number.isFinite(value) || value <= 0) return null;
7
- if (value === 18_000) return '5h';
8
- if (value === 604_800) return 'weekly';
9
- if (value === 2_592_000) return 'monthly';
10
- if (value % 604_800 === 0) return `${value / 604_800}w`;
11
- if (value % 3_600 === 0) return `${value / 3_600}h`;
12
- if (value % 60 === 0) return `${value / 60}m`;
13
- return `${value}s`;
14
- }
15
-
16
- function finiteNumber(value) {
17
- if (value === null || value === undefined || value === '' || typeof value === 'boolean') return undefined;
18
- const number = typeof value === 'number' ? value : Number(value);
19
- return Number.isFinite(number) ? number : undefined;
20
- }
21
-
22
- function clampPercent(value) {
23
- const number = finiteNumber(value);
24
- return number === undefined || number < 0 || number > 100 ? undefined : number;
25
- }
26
-
27
- function isoFromUnixSeconds(value) {
28
- const seconds = finiteNumber(value);
29
- if (seconds === undefined || seconds <= 0) return undefined;
30
- const date = new Date(seconds * 1000);
31
- return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
32
- }
33
-
34
- function isoFromDate(value) {
35
- if (typeof value !== 'string' || value.trim() === '') return undefined;
36
- const date = new Date(value);
37
- return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
38
- }
39
-
40
- /** DeepSeek /user/balance 的顶层投影;优先选择 CNY 余额项。 */
41
- export function normalizeDeepSeekBalance(data) {
42
- const infos = data && typeof data === 'object' && !Array.isArray(data) ? data.balance_infos : undefined;
43
- const validInfos = Array.isArray(infos) ? infos.filter((item) => item && typeof item === 'object' && !Array.isArray(item)) : [];
44
- const first = validInfos.find((item) => item.currency === 'CNY') ?? validInfos[0];
45
- const amount = first ? finiteNumber(first.total_balance) : undefined;
46
- if (!first || amount === undefined) throw new Error('invalid deepseek balance');
47
- return {
48
- amount: String(first.total_balance),
49
- currency: typeof first.currency === 'string' && first.currency !== '' ? first.currency : 'CNY',
50
- available: data.is_available !== false && first.is_available !== false,
51
- };
52
- }
53
-
54
- /**
55
- * dsh-codex-connect 的无密钥投影 → 手机额度领域模型。
56
- * 保留主 bucket 与 additional_rate_limits 的全部有效窗口,仅返回百分比/金额/窗口,不接触 OAuth token。
57
- */
58
- export function normalizeCodexUsage(usage) {
59
- if (!usage || typeof usage !== 'object' || Array.isArray(usage)) throw new Error('invalid codex usage');
60
- if (usage.rateLimits !== undefined && !Array.isArray(usage.rateLimits)) throw new Error('invalid codex rate limits');
61
- const rateLimits = (usage.rateLimits ?? []).filter((bucket) => bucket && typeof bucket === 'object' && !Array.isArray(bucket));
62
- const primaryIndex = rateLimits.findIndex((bucket) => bucket.id === 'codex');
63
- const orderedBuckets = primaryIndex >= 0
64
- ? [rateLimits[primaryIndex], ...rateLimits.filter((_, index) => index !== primaryIndex)]
65
- : rateLimits;
66
- const windows = [];
67
- for (const [bucketIndex, bucket] of orderedBuckets.entries()) {
68
- if (!Array.isArray(bucket.windows)) continue;
69
- const bucketName = typeof bucket.name === 'string' && bucket.name.trim() !== ''
70
- ? bucket.name.trim().slice(0, 80)
71
- : typeof bucket.id === 'string' && bucket.id.trim() !== '' ? bucket.id.trim().slice(0, 80) : undefined;
72
- const seenInBucket = new Set();
73
- for (const item of bucket.windows) {
74
- if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
75
- const baseWindow = usageWindowLabel(item.windowSeconds);
76
- const remainingPercent = clampPercent(item.remainingPercent);
77
- const resetAt = isoFromUnixSeconds(item.resetAt);
78
- if (!baseWindow || seenInBucket.has(baseWindow) || remainingPercent === undefined) continue;
79
- seenInBucket.add(baseWindow);
80
- const window = bucketIndex === 0 || !bucketName ? baseWindow : `${bucketName} · ${baseWindow}`;
81
- windows.push({ window, remainingPercent, ...(resetAt ? { resetAt } : {}) });
82
- }
83
- }
84
- const individual = usage.individualLimit;
85
- let individualLimit;
86
- if (individual && typeof individual === 'object' && !Array.isArray(individual)) {
87
- const limit = finiteNumber(individual.limit);
88
- const used = finiteNumber(individual.used);
89
- const remaining = finiteNumber(individual.remaining);
90
- const remainingPercent = clampPercent(individual.remainingPercent ?? (limit > 0 && remaining !== undefined ? remaining / limit * 100 : undefined));
91
- if (limit !== undefined && used !== undefined && remaining !== undefined && limit > 0 && remainingPercent !== undefined) {
92
- individualLimit = {
93
- limit: String(individual.limit),
94
- used: String(individual.used),
95
- remaining: String(individual.remaining),
96
- remainingPercent,
97
- };
98
- }
99
- }
100
- const credits = usage.credits;
101
- let codexCredits;
102
- if (credits && typeof credits === 'object' && !Array.isArray(credits) && typeof credits.unlimited === 'boolean') {
103
- if (credits.unlimited) {
104
- codexCredits = { unlimited: true };
105
- } else if (typeof credits.balance === 'string' && credits.balance.trim() !== '') {
106
- codexCredits = { unlimited: false, balance: credits.balance };
107
- } else if (finiteNumber(credits.balance) !== undefined) {
108
- codexCredits = { unlimited: false, balance: String(credits.balance) };
109
- } else {
110
- // balance 是可选字段,但 unlimited=false 仍是一个有效、可展示的 Credits 状态。
111
- codexCredits = { unlimited: false };
112
- }
113
- }
114
- if (windows.length === 0 && individualLimit === undefined && codexCredits === undefined) throw new Error('codex usage has no usable allowance');
115
- return {
116
- windows,
117
- ...(individualLimit ? { individualLimit } : {}),
118
- ...(codexCredits ? { credits: codexCredits } : {}),
119
- };
120
- }
121
-
122
- /** OpenCode Go usage payload → 剩余百分比窗口;无效窗口单独跳过,限流窗口保留。 */
123
- export function normalizeOpenCodeUsage(payload) {
124
- const usage = payload && typeof payload === 'object' && !Array.isArray(payload) ? (payload.usage ?? payload) : undefined;
125
- if (!usage || typeof usage !== 'object' || Array.isArray(usage)) throw new Error('invalid opencode usage');
126
- const windows = [];
127
- for (const [key, label] of [['rolling', '5h'], ['weekly', 'weekly'], ['monthly', 'monthly']]) {
128
- const detail = usage[key];
129
- if (!detail || typeof detail !== 'object' || Array.isArray(detail)) continue;
130
- const status = detail.status;
131
- if (status !== undefined && status !== null && status !== 'ok' && status !== 'rate-limited') continue;
132
- const usedPercent = clampPercent(detail.percent);
133
- if (usedPercent === undefined) continue;
134
- // percent=0 时 OpenCode 返回的是 now+window 占位值,不是可展示的 resetAt。
135
- const resetAt = usedPercent === 0 ? undefined : isoFromDate(detail.resetsAt);
136
- windows.push({
137
- window: label,
138
- remainingPercent: 100 - usedPercent,
139
- ...(status === 'rate-limited' ? { limited: true } : {}),
140
- ...(resetAt ? { resetAt } : {}),
141
- });
142
- }
143
- if (windows.length === 0) throw new Error('opencode usage has no usable windows');
144
- return { windows };
145
- }
@@ -1,112 +0,0 @@
1
- // 两端对齐状态(CONTEXT「对齐提示」)。
2
- //
3
- // 判据是**版本号**,且只用于决定**提示文案**——不参与任何能力可用性判定
4
- // (能力看声明,见 ADR 0017 与 ADR 0019 的三条判据分离)。
5
- // 比较只看 core `X.Y.Z`:App 的 `+build` 是它自己的构建号,同一发布周期内每次构建
6
- // 都不同;插件可能是 prerelease(如 `3.2.1-alpha.1`,发布通道用)。两者都不参与配对,
7
- // 因此插件 `3.2.1-alpha.1` 与 App `3.2.1+24` 按 core `3.2.1` 视为对齐。
8
- //
9
- // 自举:已装在用户手机上的旧 App 不会上报版本头,因此「头缺失」本身就是
10
- // 「这是旧 App」的可靠信号——PC 端据此就能提示,不必等 App 先升级。
11
- import { parseVersion } from "./plugin-update.js";
12
-
13
- /** App 上报自身版本所用的请求头。 */
14
- export const APP_VERSION_HEADER = "x-mobile-app-version";
15
-
16
- /** 对齐状态。 */
17
- export const ALIGNMENT_STATE = {
18
- /** 两端语义版本一致。 */
19
- OK: "ok",
20
- /** 手机 App 落后于插件:应更新 App。 */
21
- APP_OUTDATED: "app-outdated",
22
- /** 插件落后于手机 App:应更新插件。 */
23
- PLUGIN_OUTDATED: "plugin-outdated",
24
- /** 无法判定(旧 App 未上报、或版本不可解析)——如实报出,不猜。 */
25
- UNKNOWN: "unknown",
26
- };
27
-
28
- /** 取 core `X.Y.Z`(prerelease 与 build 都剥离);不可解析返回 ""。 */
29
- export function semanticVersion(value) {
30
- const p = parseVersion(value);
31
- return p ? p.version : "";
32
- }
33
-
34
- /** 从请求头读 App 版本;缺失或形状异常一律返回空串。 */
35
- export function readAppVersionHeader(headers) {
36
- if (!headers || typeof headers !== "object") return "";
37
- const raw = headers[APP_VERSION_HEADER];
38
- const value = Array.isArray(raw) ? raw[0] : raw;
39
- return typeof value === "string" ? value.trim() : "";
40
- }
41
-
42
- /**
43
- * 计算两端对齐状态。返回结构可直接放进 bootstrap / diagnostics 响应。
44
- * `hint` 是给人看的说明(PC 端日志与诊断页用);手机端应按 `reason` 自行本地化。
45
- */
46
- export function computeAppAlignment({ appVersionHeader, pluginVersion }) {
47
- const pluginSemver = semanticVersion(pluginVersion);
48
- const appRaw = typeof appVersionHeader === "string" ? appVersionHeader.trim() : "";
49
- const base = {
50
- appVersion: appRaw === "" ? null : appRaw,
51
- pluginVersion: pluginVersion ?? "",
52
- expectedAppVersion: pluginSemver || null,
53
- };
54
- if (pluginSemver === "") {
55
- return { ...base, state: ALIGNMENT_STATE.UNKNOWN, reason: "plugin-version-unparseable", hint: "插件自身版本不可解析,无法判定两端是否对齐" };
56
- }
57
- if (appRaw === "") {
58
- return {
59
- ...base,
60
- state: ALIGNMENT_STATE.UNKNOWN,
61
- reason: "app-version-header-missing",
62
- hint: `手机 App 未上报版本(通常是旧版 App);无法确认两端是否对齐。插件为 ${pluginVersion},建议把 App 更新到同版本。`,
63
- };
64
- }
65
- const appSemver = semanticVersion(appRaw);
66
- if (appSemver === "") {
67
- return { ...base, state: ALIGNMENT_STATE.UNKNOWN, reason: "app-version-unparseable", hint: `手机 App 上报的版本无法解析:${appRaw}` };
68
- }
69
- if (appSemver === pluginSemver) {
70
- return { ...base, state: ALIGNMENT_STATE.OK, reason: "aligned", hint: "" };
71
- }
72
- const older = compareSemver(appSemver, pluginSemver) < 0;
73
- if (older) {
74
- return {
75
- ...base,
76
- state: ALIGNMENT_STATE.APP_OUTDATED,
77
- reason: "app-older-than-plugin",
78
- hint: `手机 App(${appRaw})落后于本机插件(${pluginVersion}):新功能会被明确提示而非静默失效。请在手机上把 App 更新到 ${pluginSemver}。`,
79
- };
80
- }
81
- return {
82
- ...base,
83
- state: ALIGNMENT_STATE.PLUGIN_OUTDATED,
84
- reason: "plugin-older-than-app",
85
- hint: `本机插件(${pluginVersion})落后于手机 App(${appRaw}):请在电脑上把插件更新到 ${appSemver} 并重启 DSH。`,
86
- };
87
- }
88
-
89
- /** 内部:比较两个语义版本(入参已保证可解析)。 */
90
- function compareSemver(a, b) {
91
- const pa = a.split(".").map(Number);
92
- const pb = b.split(".").map(Number);
93
- for (let i = 0; i < 3; i++) {
94
- if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
95
- }
96
- return 0;
97
- }
98
-
99
- /**
100
- * 只在结论变化时提示一次(bootstrap 会在启动、下拉刷新、重连时反复调用,
101
- * 每次都记日志会把 DSH 日志刷满)。`seen` 是该插件实例作用域内的去重载体。
102
- */
103
- export function noteAppAlignment(logger, alignment, seen) {
104
- if (!alignment || alignment.state === ALIGNMENT_STATE.OK) return false;
105
- const key = `${alignment.state}:${alignment.appVersion ?? "-"}`;
106
- if (seen.key === key) return false;
107
- seen.key = key;
108
- const line = `mobile-remote: ${alignment.hint}`;
109
- if (alignment.state === ALIGNMENT_STATE.UNKNOWN) logger?.info?.(line);
110
- else logger?.warn?.(line);
111
- return true;
112
- }