dsh-remote-control-plugin 0.3.0 → 0.3.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/INSTALL.md CHANGED
@@ -5,17 +5,17 @@ build step, no postinstall script.
5
5
 
6
6
  ## 1. Install it
7
7
 
8
- Open DSH on the other desktop, start a session, and send:
8
+ In the Web GUI: **Plugins → Add plugin**, enter `dsh-remote-control-plugin@0.3.1`, keep the
9
+ official npm registry selected, then **Install** and **Enable now**. Restart DSH.
9
10
 
10
- > Install the DSH plugin bundle `dsh-remote-control-plugin@0.3.0` with plugin_manager, then tell me when it is applied.
11
+ A **Devices** icon appears in the sidebar.
11
12
 
12
- Then restart DSH. A **Devices** icon appears in the sidebar.
13
+ From a chat, the same install works where the plugin tool is enabled (Standard or Creator
14
+ preset — PTC disables `tool-plugin-manager`):
13
15
 
14
- `dsh plugin add` does not work for this: it forwards to `pnpm`, which is not on `PATH` outside
15
- the app. The agent route uses the bundled pnpm and also registers the bundle in the profile,
16
- which a bare `pnpm add` would not do.
16
+ > Install the DSH plugin bundle `dsh-remote-control-plugin@0.3.1` with plugin_manager, then tell me when it is applied.
17
17
 
18
- ## 2. Connect the two machines
18
+ ## 2. Connect two machines
19
19
 
20
20
  Pick the machine with a reachable address (same LAN, VPN, or a forwarded port):
21
21
 
@@ -23,6 +23,8 @@ Pick the machine with a reachable address (same LAN, VPN, or a forwarded port):
23
23
  2. On the other machine: **Devices → Relay**, paste the invite into the URL field, **Connect**.
24
24
  3. Either machine: **Request** next to the peer, then **Allow** on the target.
25
25
 
26
+ Two machines on the same LAN need no relay at all — LAN discovery lists them directly.
27
+
26
28
  No reachable machine? Run a relay anywhere both can reach (`PORT=8787 node relay/server.js`) and
27
29
  paste `http://<host>:8787 <workspace key>` into both panels.
28
30
 
@@ -31,5 +33,6 @@ paste `http://<host>:8787 <workspace key>` into both panels.
31
33
  - The account label is cosmetic; trust comes from the one-time **Allow** on the target device.
32
34
  - Approvals persist in `$DSH_HOME/remote-control-peers.json`; deleting it revokes every peer.
33
35
  - The relay and the control API share one port (default `57330`), so one forwarded port is enough.
34
- - Device trusts are per install: the published plugin and a locally built copy share the protocol
35
- but keep separate approval stores.
36
+ - Peer traffic is signed but not encrypted. Use a private network or TLS in front of the port.
37
+ - 0.3.1 fixes two relay bugs: peers no longer linger as `503 relay-offline` after a relay goes
38
+ away, and a failed Connect no longer tears down a working hosted relay.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hkrds1996
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/host/fabric.js CHANGED
@@ -122,7 +122,9 @@ export function createFabric({ log, config, self, sessions, stateFile }) {
122
122
  */
123
123
  function syncRelayPeers() {
124
124
  if (!relay) return
125
+ const present = new Set()
125
126
  for (const item of relay.peers()) {
127
+ present.add(item.deviceId)
126
128
  const existing = peers.get(item.deviceId)
127
129
  if (existing && existing.proto !== 'relay') continue
128
130
  peers.set(item.deviceId, {
@@ -137,6 +139,11 @@ export function createFabric({ log, config, self, sessions, stateFile }) {
137
139
  secret: approvals.get(item.deviceId)?.secret ?? existing?.secret,
138
140
  })
139
141
  }
142
+ // Drop relay peers the relay no longer lists, so a device that leaves or a
143
+ // relay that goes away cannot leave a peer that always answers 503.
144
+ for (const [deviceId, peer] of peers) {
145
+ if (peer.proto === 'relay' && !present.has(deviceId)) peers.delete(deviceId)
146
+ }
140
147
  }
141
148
 
142
149
  async function loadApprovals() {
@@ -469,7 +476,17 @@ export function createFabric({ log, config, self, sessions, stateFile }) {
469
476
  callPeer,
470
477
  proxyStream,
471
478
  handlePeer,
472
- setRelay(link) { relay = link },
479
+ setRelay(link) {
480
+ relay = link
481
+ if (!link) {
482
+ // Without a relay every relay-only peer is unreachable; keeping it
483
+ // would make each call fail with a misleading 503.
484
+ for (const [deviceId, peer] of peers) {
485
+ if (peer.proto === 'relay') peers.delete(deviceId)
486
+ }
487
+ }
488
+ },
489
+
473
490
  snapshot() {
474
491
  syncRelayPeers()
475
492
  const who = self()
package/index.js CHANGED
@@ -177,7 +177,6 @@ export function apply(ctx, rawConfig) {
177
177
  }
178
178
 
179
179
  async function startRelay(url, key) {
180
- stopRelay()
181
180
  const link = createRelayLink({
182
181
  url,
183
182
  key,
@@ -187,7 +186,9 @@ export function apply(ctx, rawConfig) {
187
186
  localPort: info.port,
188
187
  log,
189
188
  })
189
+ // Connect first: a failed join must not tear down a working relay link.
190
190
  await link.start()
191
+ stopRelay()
191
192
  relayLink = link
192
193
  fabric.setRelay(link)
193
194
  log.info(`[remote-control] relay connected: ${url}`)
@@ -316,11 +317,11 @@ export function apply(ctx, rawConfig) {
316
317
  return json(res, 400, { error: 'bad-key', message: 'Workspace key must be at least 16 characters.' })
317
318
  }
318
319
  try {
319
- stopHostedRelay()
320
320
  await startRelay(url, key)
321
321
  } catch (error) {
322
322
  return json(res, 502, { error: 'relay-unreachable', message: String(error?.message || error) })
323
323
  }
324
+ stopHostedRelay()
324
325
  await saveRelayConfig({ hosted: false, url, key })
325
326
  return json(res, 200, { ok: true })
326
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-control-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Remote control for DSH: a Devices panel that discovers other DSH desktops, pairs with them once, and drives their sessions across LAN or a relay.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,6 +27,7 @@
27
27
  "icon.svg",
28
28
  "README.md",
29
29
  "INSTALL.md",
30
+ "LICENSE",
30
31
  "web/index.html",
31
32
  "web/app.js",
32
33
  "web/app.css",