dsh-plugin-mobile-link 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,14 +24,14 @@ plugins/mobile-link/
24
24
  ├── package.json # type: module, dsh.bundle.patch, bin: dsh-mobile-link
25
25
  ├── cordis.patch.yml # bundle patch: the loader row + config reference
26
26
  ├── lib/
27
- │ ├── index.js # cordis plugin: starts the agent, registers 3 routes
27
+ │ ├── index.js # cordis plugin: starts the agent, registers 5 routes
28
28
  │ ├── link.js # the DLP agent transport: identity, socket, heartbeat, reconnect
29
29
  │ ├── router.js # frames ⇄ local DSH mux streams, per-device `$events`
30
30
  │ ├── dlp.js # DLP v1 frame codec (shared with the relay)
31
31
  │ ├── files.js # phone → computer file transfers (`_link/file*` methods)
32
- │ ├── enroll.js # invite-code redemption, writes ~/.dsh/mobile-link/agent.json
32
+ │ ├── enroll.js # open enrollment (no invite code), writes ~/.dsh/mobile-link/agent.json
33
33
  │ ├── journal.js # lifecycle journal: why a relay link was dropped, after the fact
34
- │ ├── routes.js # GET /mobile-link/status, POST /pair-code, GET /qr
34
+ │ ├── routes.js # status, enroll, pair-code, pair-code/regenerate, devices/revoke
35
35
  │ ├── qr.js # dependency-free QR SVG
36
36
  │ └── cli.js # dsh-mobile-link: enroll / mint-pair-code
37
37
  └── test/ # node --test, 100+ tests (no DSH, no network)
@@ -74,29 +74,43 @@ dsh plugin --profile web add ./plugins/mobile-link
74
74
 
75
75
  ## Use
76
76
 
77
- 1. Register this computer with a relay (once). Either redeem an invite code:
77
+ 1. Register this computer with a relay (once). Open the **移动端连接** panel at the
78
+ bottom of the DSH sidebar: the relay address is prefilled (editable) from the
79
+ installation default; click **登记**. The connector derives the computer name
80
+ automatically, writes `~/.dsh/mobile-link/agent.json` and reconnects in place —
81
+ no terminal and no DSH restart. Open enrollment is the default; the relay
82
+ rate-limits by source. If an installation has no panel (headless, CI), the CLI
83
+ still works:
78
84
 
79
85
  ```bash
80
- dsh-mobile-link enroll --invite <邀请码> --relay wss://<你的站点>/dsh-link
86
+ dsh-mobile-link enroll --relay wss://<你的站点>/dsh-link
81
87
  ```
82
88
 
83
89
  …or register it on the relay host with `dsh-relay admin agent-register
84
90
  --write-config ~/.dsh/mobile-link/agent.json`.
85
91
 
86
- 2. Mint a pairing code, and show it as a QR the phone camera can read:
92
+ 2. The same panel shows the pairing code and its QR once the relay link is up.
93
+ The CLI can still mint one directly:
87
94
 
88
95
  ```bash
89
96
  dsh-mobile-link --mint-pair-code # prints JSON: code + relay + agentId
90
97
  ```
91
98
 
92
- Or open the **移动端连接** panel at the bottom of the sidebar in the DSH client
93
- (spec 010) — same code, same QR, with the validity countdown.
99
+ The code is persistent: it stays valid until you click 重新生成. Each paired device
100
+ has a quiet 撤销 action; revoking one device leaves the code and other devices alone.
94
101
 
95
- 3. Scan it in the iOS app. After that the phone reconnects on its own — nothing
96
- else has to be run by hand.
102
+ 3. Scan it in the iOS app. The code can be claimed again for another phone, a reinstall
103
+ or a new device; only 重新生成 revokes it (already-paired devices stay connected).
104
+ After that the phone reconnects on its own — nothing else has to be run by hand.
97
105
 
98
106
  `GET /mobile-link/status` reports whether the link is up, which devices are
99
- attached, and whether this computer is registered yet. Its `journal` block carries
107
+ attached, and whether this computer is registered yet. Its `enroll` block also
108
+ carries `panel` (this connector can enroll from the DSH panel), `relayPrefill`
109
+ and `relayLocked` (the address to prefill and whether it came from config).
110
+ `POST /mobile-link/enroll` performs that enrollment (open enrollment; no invite code); it returns the agent id and
111
+ name, never `agentSecret`, and is behind the same DSH request fence as pairing. `GET /mobile-link/devices` lists the full paired roster (including offline) for the panel, and
112
+ `POST /mobile-link/devices/revoke` revokes one paired device by `deviceId` through the relay; both are behind
113
+ the fence and authenticated with the agent secret. Its `journal` block carries
100
114
  the current process's newest lifecycle events (link opened/closed with the reason,
101
115
  device attached/detached); the counters in it reset when the plugin hot-reloads, so
102
116
  the full history — across restarts — is the JSONL file at `journal.path`.
@@ -109,7 +123,8 @@ plugin's config block (`cordis.patch.yml`), not by editing this file.
109
123
  | Key | Default | Meaning |
110
124
  | --- | --- | --- |
111
125
  | `enabled` | `true` | `false` stops the outbound link without uninstalling |
112
- | `relayUrl` | from `agent.json` | Relay base URL; a bare deployment dials `<relay>/link/agent` |
126
+ | `relayUrl` | from `agent.json` | Relay base URL; a bare deployment dials `<relay>/link/agent`. Pinning a real value here outranks the enrolled identity and makes the panel field read-only |
127
+ | `defaultRelayUrl` | empty | Form-only default: prefills the panel's relay input, but never outranks the enrolled identity. Official installs set this so users can still edit it |
113
128
  | `agentId` / `agentSecret` | from `agent.json` | Override the enrolled identity |
114
129
  | `stateFile` | `~/.dsh/mobile-link/agent.json` | Where the identity lives |
115
130
  | `journalFile` | `~/.dsh/mobile-link/link-events.jsonl` | Lifecycle journal (rotated once at 256 KiB); `false` keeps it in memory only |
package/cordis.patch.yml CHANGED
@@ -17,6 +17,11 @@
17
17
  # ws://127.0.0.1:8787). Left unset here on purpose: a value
18
18
  # pinned in this file outranks both DSH_RELAY_URL and the
19
19
  # address already enrolled in ~/.dsh/mobile-link/agent.json.
20
+ # defaultRelayUrl Form-only default for the 移动端连接 panel. It prefills the
21
+ # relay input but does not outrank the identity file, so a user
22
+ # can edit it and the edit survives a restart. Official installs
23
+ # set this (not `relayUrl`) to prefill the real address.
24
+ # Status: enroll.relayPrefill / relayLocked.
20
25
  # agentId overrides ~/.dsh/mobile-link/agent.json
21
26
  # agentSecret overrides ~/.dsh/mobile-link/agent.json
22
27
  # stateFile agent identity file (default ~/.dsh/mobile-link/agent.json)
package/lib/cli.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * prefix-preserving URL join the live agent uses.
18
18
  *
19
19
  * `enroll` is the first-run path for someone else's computer: it redeems an
20
- * invite code for an identity and writes `~/.dsh/mobile-link/agent.json`, then
20
+ * relay address for an identity and writes `~/.dsh/mobile-link/agent.json`, then
21
21
  * exits. Everything after that is the ordinary agent.
22
22
  */
23
23
 
@@ -28,6 +28,7 @@ import { MobileLinkAgent } from './link.js'
28
28
  import { mintPairCode } from './pairing.js'
29
29
  import { qrTerminal } from './qr.js'
30
30
  import { INVITE_ENV, defaultStatePath, resolveIdentity } from './state.js'
31
+ import { T, setLanguage } from './i18n.js'
31
32
 
32
33
  /** Atomic status snapshot so readers never observe a half-written file. */
33
34
  async function writeJsonAtomic(path, value) {
@@ -60,14 +61,16 @@ function parseArgs(argv) {
60
61
 
61
62
  async function main() {
62
63
  const args = parseArgs(process.argv.slice(2))
64
+ if (args.lang) setLanguage(args.lang)
65
+ else if (process.env.DSH_LANG) setLanguage(process.env.DSH_LANG)
63
66
  if (args.help) {
64
- process.stdout.write(`usage: cli.js enroll --invite <code> [--relay <ws url>] [--name <label>]
65
- Redeem a one-time invite code for an identity in ${defaultStatePath()}.
66
- The invite may also arrive as ${INVITE_ENV}. Exits non-zero on failure.
67
+ process.stdout.write(`usage: cli.js enroll [--relay <ws url>] [--name <label>]
68
+ Register this computer with open enrollment in ${defaultStatePath()}. Exits non-zero on failure.
67
69
 
68
70
  cli.js --relay <ws url> --agent-id <id> --agent-secret <secret>
69
71
  [--state-file <path>] [--dsh-url <url>]
70
72
  [--status-file <path>] [--heartbeat-ms <n>] [--log-level debug|info|warn]
73
+ [--lang en|zh-Hans|zh-Hant|ja|ko|es|pt-BR|fr|de]
71
74
 
72
75
  Note: run this inside a DSH host (plugin) when you can. A standalone process has no
73
76
  in-process context, so on the official desktop app it cannot learn the local address by
@@ -101,21 +104,16 @@ async function main() {
101
104
  // 反正人已经坐在终端前、手机就在手边,索性把配对码也申请出来、画成终端二维码:
102
105
  // 扫这一屏等于在 App 里填「中转服务器 + 配对码」,少一次开页面找端口的往返。
103
106
  // 申请失败不算失败——登记本身已经成功,退回到"去界面里的连接入口取"的说法。
104
- process.stdout.write('这台电脑已登记。接下来还有两步:\n'
105
- + ' 1. 重启 DSH,让连接器用上这个身份;\n'
106
- + ' 2. 手机 App 的「扫码配对」扫下面的二维码(与 DSH 界面左下角「移动端连接」里那张等价)。\n')
107
+ process.stdout.write(`${T('cli.registered')}\n ${T('cli.step1')}\n ${T('cli.step2')}\n`)
107
108
  try {
108
109
  const identity = await resolveIdentity({ stateFile: settled.stateFile })
109
110
  const minted = await mintPairCode({ identity, ttlMs: 10 * 60 * 1000 })
110
111
  const minutes = Math.round((minted.expiresAt - Date.now()) / 60000)
111
112
  process.stdout.write('\n' + qrTerminal(minted.qrPayload) + '\n')
112
- process.stdout.write(`配对码 ${minted.code}(约 ${minutes} 分钟有效,一次性)\n`)
113
- process.stdout.write('也可以在这台电脑上点 DSH 界面左下角的「移动端连接」取新码;\n'
114
- + '连接器还会把带令牌地址写进 ~/.dsh/mobile-link/endpoint.json。\n')
113
+ process.stdout.write(`${T('cli.code', { code: minted.code, minutes })}\n`)
114
+ process.stdout.write(`${T('cli.hint')}\n`)
115
115
  } catch (error) {
116
- process.stdout.write('(这次没能预先申请配对码:' + (error?.message ?? error) + ')\n'
117
- + '重启 DSH 后,在这台电脑的 DSH 界面左下角点「移动端连接」,'
118
- + '用手机 App 的「扫码配对」扫里面的二维码。\n')
116
+ process.stdout.write(`${T('cli.prefetchFailed', { error: error?.message ?? error })}\n${T('cli.prefetchHint')}\n`)
119
117
  }
120
118
  return 0
121
119
  }