@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.9
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.i18n.yaml +2 -2
- package/README.md +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +955 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +73 -39
- package/lib/types/client/locales.d.ts +26 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -22
- package/lib/types/client/mobile-adapt.d.ts +40 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +967 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +77 -39
- package/src/client/locales.ts +36 -22
- package/src/client/mobile-adapt.ts +913 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firewall.d.ts","sourceRoot":"","sources":["../../src/firewall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,yBAAyB,CAAA;AAExD,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,UAAU,CAAA;AAEhF,2EAA2E;AAC3E,eAAO,MAAM,WAAW,EAAE,aAazB,CAAA;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAClC;AA4ED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,GAAG,GAAE,aAA2B,GAC/B,eAAe,GAAG,SAAS,CAW7B;AAID,+DAA+D;AAC/D,wBAAgB,eAAe,IAAI,eAAe,GAAG,SAAS,CAM7D;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,EAAE,EAAE,OAAO,CAAA;IACX,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAA;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,eAAe,GAAG,SAAS,GACnC,eAAe,CAGjB;AAED,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAI7C,4DAA4D;AAC5D,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,eAAe,CASlF"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host firewall management for the LAN bind toggle: while LAN access is on,
|
|
3
|
+
* keep one inbound allow rule for the bound port; when it turns off, remove
|
|
4
|
+
* the rule. Windows manages the Windows Defender Firewall rule via netsh;
|
|
5
|
+
* Linux uses the first available manager among firewalld / ufw / iptables.
|
|
6
|
+
* On every other platform (macOS included) the plugin reports the firewall
|
|
7
|
+
* as unmanaged: the port usually needs no rule there, and managing pf or the
|
|
8
|
+
* application firewall is out of scope for a distributable plugin.
|
|
9
|
+
*
|
|
10
|
+
* Ported from the dsh-LAN reference implementation (MIT), adapted to this
|
|
11
|
+
* package's structure and test seams.
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
/** Rule name shared by every backend so re-runs recreate the same rule. */
|
|
15
|
+
export const FIREWALL_RULE_NAME = 'remote-web-ui (auto)';
|
|
16
|
+
/** Production runner: spawn without a shell, capture text, 20s timeout. */
|
|
17
|
+
export const spawnRunner = (cmd, args) => {
|
|
18
|
+
const result = spawnSync(cmd, args, {
|
|
19
|
+
shell: false,
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
windowsHide: process.platform === 'win32',
|
|
22
|
+
timeout: 20_000,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
ok: result.status === 0,
|
|
26
|
+
out: result.stdout ?? '',
|
|
27
|
+
err: result.stderr ?? '',
|
|
28
|
+
missing: result.error !== undefined,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
function netshBackend(run) {
|
|
32
|
+
// `show rule name=` exits 0 even when nothing matches (the localized "no
|
|
33
|
+
// rules match" note), so exit-code-only probes always report the rule as
|
|
34
|
+
// present. The verbose listing echoes the full rule name — the one
|
|
35
|
+
// locale-independent field — and is matched literally instead.
|
|
36
|
+
const show = () => {
|
|
37
|
+
const result = run('netsh', ['advfirewall', 'firewall', 'show', 'rule', `name=${FIREWALL_RULE_NAME}`, 'verbose']);
|
|
38
|
+
return result.ok && result.out.includes(FIREWALL_RULE_NAME);
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
label: 'netsh',
|
|
42
|
+
ruleExists: show,
|
|
43
|
+
addRule: (port) => run('netsh', [
|
|
44
|
+
'advfirewall', 'firewall', 'add', 'rule', `name=${FIREWALL_RULE_NAME}`,
|
|
45
|
+
'dir=in', 'action=allow', 'protocol=TCP', `localport=${String(port)}`, 'profile=private,domain',
|
|
46
|
+
]).ok,
|
|
47
|
+
removeRule: () => {
|
|
48
|
+
if (!show())
|
|
49
|
+
return true;
|
|
50
|
+
return run('netsh', ['advfirewall', 'firewall', 'delete', 'rule', `name=${FIREWALL_RULE_NAME}`]).ok;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function firewalldBackend(run) {
|
|
55
|
+
return {
|
|
56
|
+
label: 'firewalld',
|
|
57
|
+
ruleExists: (port) => run('firewall-cmd', ['--permanent', '--query-port', `${String(port)}/tcp`]).ok,
|
|
58
|
+
addRule: (port) => {
|
|
59
|
+
const add = run('firewall-cmd', ['--permanent', '--add-port', `${String(port)}/tcp`]);
|
|
60
|
+
const reload = run('firewall-cmd', ['--reload']);
|
|
61
|
+
return add.ok && reload.ok;
|
|
62
|
+
},
|
|
63
|
+
removeRule: (port) => {
|
|
64
|
+
const del = run('firewall-cmd', ['--permanent', '--remove-port', `${String(port)}/tcp`]);
|
|
65
|
+
const reload = run('firewall-cmd', ['--reload']);
|
|
66
|
+
return del.ok && reload.ok;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function ufwBackend(run) {
|
|
71
|
+
return {
|
|
72
|
+
label: 'ufw',
|
|
73
|
+
ruleExists: (port) => {
|
|
74
|
+
const result = run('ufw', ['status']);
|
|
75
|
+
// Anchored so a superset port's row (1443/tcp) cannot satisfy the
|
|
76
|
+
// probe for a shorter one (443/tcp).
|
|
77
|
+
return result.ok && new RegExp(`(^|\\s)${String(port)}/tcp\\s+ALLOW`, 'i').test(result.out);
|
|
78
|
+
},
|
|
79
|
+
addRule: (port) => run('ufw', ['allow', `${String(port)}/tcp`]).ok,
|
|
80
|
+
removeRule: (port) => run('ufw', ['delete', 'allow', `${String(port)}/tcp`]).ok,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function iptablesBackend(run) {
|
|
84
|
+
const rule = (port) => ['INPUT', '-p', 'tcp', '--dport', String(port), '-j', 'ACCEPT'];
|
|
85
|
+
return {
|
|
86
|
+
label: 'iptables',
|
|
87
|
+
ruleExists: (port) => run('iptables', ['-C', ...rule(port)]).ok,
|
|
88
|
+
addRule: (port) => run('iptables', ['-A', ...rule(port)]).ok,
|
|
89
|
+
removeRule: (port) => run('iptables', ['-D', ...rule(port)]).ok,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function toolAvailable(run, cmd) {
|
|
93
|
+
try {
|
|
94
|
+
// A missing binary reports through the runner's missing flag (spawnSync
|
|
95
|
+
// sets result.error for ENOENT); any graceful answer counts as present.
|
|
96
|
+
return run(cmd, ['--version']).missing !== true;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Detect the platform firewall manager. Returns undefined when the platform
|
|
104
|
+
* has no supported manager (macOS, unknown Linux without tools): the port
|
|
105
|
+
* then usually needs no rule and the UI reports "unmanaged".
|
|
106
|
+
*/
|
|
107
|
+
export function detectFirewallBackend(platform, run = spawnRunner) {
|
|
108
|
+
if (platform === 'win32')
|
|
109
|
+
return netshBackend(run);
|
|
110
|
+
if (platform !== 'linux')
|
|
111
|
+
return undefined;
|
|
112
|
+
if (toolAvailable(run, 'firewall-cmd')) {
|
|
113
|
+
// firewalld only counts when its daemon answers; an installed-but-idle
|
|
114
|
+
// firewall-cmd would otherwise claim management it cannot exercise.
|
|
115
|
+
if (run('firewall-cmd', ['--state']).ok)
|
|
116
|
+
return firewalldBackend(run);
|
|
117
|
+
}
|
|
118
|
+
if (toolAvailable(run, 'ufw'))
|
|
119
|
+
return ufwBackend(run);
|
|
120
|
+
if (toolAvailable(run, 'iptables'))
|
|
121
|
+
return iptablesBackend(run);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
let cachedBackend;
|
|
125
|
+
/** Cached detection for the running platform (per process). */
|
|
126
|
+
export function firewallBackend() {
|
|
127
|
+
const platform = process.platform;
|
|
128
|
+
if (cachedBackend === undefined || cachedBackend.platform !== platform) {
|
|
129
|
+
cachedBackend = { platform, backend: detectFirewallBackend(platform) };
|
|
130
|
+
}
|
|
131
|
+
return cachedBackend.backend;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Delete-and-add: recreating the rule is idempotent and locale-proof (netsh
|
|
135
|
+
* output is localized, so parsing the live rule's port is fragile; the Linux
|
|
136
|
+
* backends follow the same recreate pattern).
|
|
137
|
+
*/
|
|
138
|
+
export function ensureFirewallRule(port) {
|
|
139
|
+
invalidateFirewallSummary();
|
|
140
|
+
const backend = firewallBackend();
|
|
141
|
+
if (backend === undefined)
|
|
142
|
+
return true;
|
|
143
|
+
backend.removeRule(port);
|
|
144
|
+
return backend.addRule(port);
|
|
145
|
+
}
|
|
146
|
+
export function removeFirewallRule(port) {
|
|
147
|
+
invalidateFirewallSummary();
|
|
148
|
+
const backend = firewallBackend();
|
|
149
|
+
if (backend === undefined)
|
|
150
|
+
return true;
|
|
151
|
+
return backend.removeRule(port);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Human-readable firewall state for the status endpoint. A missing backend
|
|
155
|
+
* means the platform has nothing to manage; detection stays at the call site
|
|
156
|
+
* so an explicit undefined cannot silently re-probe the real OS mid-test.
|
|
157
|
+
*/
|
|
158
|
+
export function computeFirewallSummary(port, lanEnabled, backend) {
|
|
159
|
+
if (backend === undefined)
|
|
160
|
+
return { ok: true, managed: false };
|
|
161
|
+
return { ok: lanEnabled ? backend.ruleExists(port) : !backend.ruleExists(port), managed: true, note: backend.label };
|
|
162
|
+
}
|
|
163
|
+
/** How long a firewallSummary answer is reused before the backend is probed again. */
|
|
164
|
+
export const FIREWALL_SUMMARY_TTL_MS = 30_000;
|
|
165
|
+
let summaryCache;
|
|
166
|
+
/** Forget the cached summary (rule mutations call this). */
|
|
167
|
+
export function invalidateFirewallSummary() {
|
|
168
|
+
summaryCache = undefined;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The settings card polls this every ten seconds; the probes are blocking
|
|
172
|
+
* spawnSync subprocesses on managed platforms, so short-TTL memoization
|
|
173
|
+
* keeps one poll from freezing the host event loop per request.
|
|
174
|
+
*/
|
|
175
|
+
export function firewallSummary(port, lanEnabled) {
|
|
176
|
+
const key = `${String(port)}|${lanEnabled ? '1' : '0'}`;
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
if (summaryCache !== undefined && summaryCache.key === key && now - summaryCache.at < FIREWALL_SUMMARY_TTL_MS) {
|
|
179
|
+
return summaryCache.value;
|
|
180
|
+
}
|
|
181
|
+
const value = computeFirewallSummary(port, lanEnabled, firewallBackend());
|
|
182
|
+
summaryCache = { key, at: now, value };
|
|
183
|
+
return value;
|
|
184
|
+
}
|
package/lib/types/gate.d.ts
CHANGED
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Policy: loopback requests (the desktop) pass without a device identity;
|
|
8
8
|
* every non-loopback /api request must carry a live, non-revoked device
|
|
9
|
-
* cookie
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* cookie — intended to make the QR the only way into a LAN-exposed dsh web
|
|
10
|
+
* and to give "停止" real teeth for direct /api callers.
|
|
11
|
+
*
|
|
12
|
+
* Cohort reality (0.1.2-alpha.2): NOTHING emits api/gate in the official
|
|
13
|
+
* runtime, so this listener never fires there and direct /api is governed
|
|
14
|
+
* solely by the harness fence + browser-auth cookie. The pairing gate with
|
|
15
|
+
* real teeth lives on the plugin's own /remote channel (remote-api.ts); the
|
|
16
|
+
* listener stays wired for cohort lines that do carry the seam, and the
|
|
17
|
+
* README security model states the gap plainly.
|
|
12
18
|
*/
|
|
13
19
|
import type { IncomingMessage } from 'node:http';
|
|
14
20
|
import type { PairingService } from './pairing.ts';
|
|
@@ -52,8 +58,8 @@ export declare function isLoopbackClient(request: IncomingMessage): boolean;
|
|
|
52
58
|
export declare function makeGateListener(service: PairingService, requirePairingForLan?: boolean | (() => boolean), enabled?: boolean | (() => boolean)): (request: IncomingMessage, method: string | undefined, next: () => boolean | Promise<boolean>) => boolean | Promise<boolean>;
|
|
53
59
|
/**
|
|
54
60
|
* Whether a request carries a live, non-revoked paired-device cookie for
|
|
55
|
-
* this service. Sibling host routes outside /api (
|
|
56
|
-
* the same check via the remoteWebUiPairing service.
|
|
61
|
+
* this service. Sibling host routes outside /api (the right-panel
|
|
62
|
+
* /sidebar/* routes, etc.) use the same check via the remoteWebUiPairing service.
|
|
57
63
|
* @param service - the pairing service that owns the device table.
|
|
58
64
|
* @param request - the incoming HTTP request.
|
|
59
65
|
* @returns true when the cookie names a live session (and lastSeenAt was refreshed).
|
package/lib/types/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,oBAAoB,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,EACtD,OAAO,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,GACxC,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAc9H;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAIhG"}
|
package/lib/types/gate.js
CHANGED
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Policy: loopback requests (the desktop) pass without a device identity;
|
|
8
8
|
* every non-loopback /api request must carry a live, non-revoked device
|
|
9
|
-
* cookie
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* cookie — intended to make the QR the only way into a LAN-exposed dsh web
|
|
10
|
+
* and to give "停止" real teeth for direct /api callers.
|
|
11
|
+
*
|
|
12
|
+
* Cohort reality (0.1.2-alpha.2): NOTHING emits api/gate in the official
|
|
13
|
+
* runtime, so this listener never fires there and direct /api is governed
|
|
14
|
+
* solely by the harness fence + browser-auth cookie. The pairing gate with
|
|
15
|
+
* real teeth lives on the plugin's own /remote channel (remote-api.ts); the
|
|
16
|
+
* listener stays wired for cohort lines that do carry the seam, and the
|
|
17
|
+
* README security model states the gap plainly.
|
|
12
18
|
*/
|
|
13
19
|
import { isLoopbackAddress, isLoopbackHostname } from "./loopback.js";
|
|
14
20
|
/**
|
|
@@ -96,8 +102,8 @@ export function makeGateListener(service, requirePairingForLan = true, enabled =
|
|
|
96
102
|
}
|
|
97
103
|
/**
|
|
98
104
|
* Whether a request carries a live, non-revoked paired-device cookie for
|
|
99
|
-
* this service. Sibling host routes outside /api (
|
|
100
|
-
* the same check via the remoteWebUiPairing service.
|
|
105
|
+
* this service. Sibling host routes outside /api (the right-panel
|
|
106
|
+
* /sidebar/* routes, etc.) use the same check via the remoteWebUiPairing service.
|
|
101
107
|
* @param service - the pairing service that owns the device table.
|
|
102
108
|
* @param request - the incoming HTTP request.
|
|
103
109
|
* @returns true when the cookie names a live session (and lastSeenAt was refreshed).
|
package/lib/types/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { IncomingMessage } from 'node:http';
|
|
11
11
|
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
12
13
|
import z from 'schemastery';
|
|
13
14
|
import { type PairingConfig } from './pairing.ts';
|
|
14
15
|
declare module '@deepseek-ai/cordis' {
|
|
@@ -18,6 +19,12 @@ declare module '@deepseek-ai/cordis' {
|
|
|
18
19
|
* fires this per /api request before bridging to the API proxy on
|
|
19
20
|
* deployments that carry the pairing/revocation seam; call `next()` to
|
|
20
21
|
* delegate, return false (without calling it) to veto with 403.
|
|
22
|
+
*
|
|
23
|
+
* Cohort note (0.1.2-alpha.2): the official runtime ships NO emitter
|
|
24
|
+
* for this event, so the listener below never fires there and direct
|
|
25
|
+
* /api stays under the harness fence + browser auth. It is wired anyway
|
|
26
|
+
* so cohort lines that do carry the seam get pairing enforcement on
|
|
27
|
+
* direct /api without a plugin change.
|
|
21
28
|
*/
|
|
22
29
|
'api/gate'(this: Context, request: IncomingMessage, method: string | undefined, next: () => boolean | Promise<boolean>): boolean | Promise<boolean>;
|
|
23
30
|
}
|
|
@@ -31,7 +38,7 @@ export declare const inject: string[];
|
|
|
31
38
|
* settings surface edits. Spelled here rather than imported: the browser
|
|
32
39
|
* half spells the same value and must not depend on a Host package.
|
|
33
40
|
*/
|
|
34
|
-
export declare const REMOTE_WEB_UI_SETTINGS_NAMESPACE:
|
|
41
|
+
export declare const REMOTE_WEB_UI_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
35
42
|
/** Plugin config, validated by the same-named schemastery schema. */
|
|
36
43
|
export interface Config {
|
|
37
44
|
/** Token lifetime in ms; the QR link dies after this. */
|
|
@@ -50,9 +57,14 @@ export interface Config {
|
|
|
50
57
|
/**
|
|
51
58
|
* When true (default), a desktop Web GUI opened at a non-loopback origin
|
|
52
59
|
* rides the gated `/remote/api` channel and must carry a live paired-device
|
|
53
|
-
* cookie — the QR is the only way into remote desktop, and stop()
|
|
54
|
-
*
|
|
55
|
-
*
|
|
60
|
+
* cookie — the QR is the only way into remote desktop, and stop()/revoke()
|
|
61
|
+
* cut the /remote channel and the pairing cookie off immediately. Scope
|
|
62
|
+
* note for this cohort: direct /api is governed by the harness fence +
|
|
63
|
+
* browser-auth cookie (the api/gate seam has no emitter on 0.1.2-alpha.2),
|
|
64
|
+
* so a harness browser credential a device has already redeemed is not
|
|
65
|
+
* invalidated by stop() — see the README security model. Set false to keep
|
|
66
|
+
* the desktop on plain `/api` (only useful when that origin is already
|
|
67
|
+
* trusted for `/api`).
|
|
56
68
|
*/
|
|
57
69
|
requirePairingForLan?: boolean;
|
|
58
70
|
/**
|
|
@@ -80,12 +92,26 @@ export interface Config {
|
|
|
80
92
|
*/
|
|
81
93
|
autoTunnel?: boolean;
|
|
82
94
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
95
|
+
* LAN bind toggle. When the user flips it (true or false) the plugin
|
|
96
|
+
* writes the managed webserver block into the profile patch — true pins
|
|
97
|
+
* the bind default to 0.0.0.0 (an explicit --host flag still wins), false
|
|
98
|
+
* pins it back to 127.0.0.1 — and maintains the matching host firewall
|
|
99
|
+
* rule (Windows netsh; Linux firewalld/ufw/iptables; other platforms
|
|
100
|
+
* report the firewall as unmanaged). The block takes effect when the
|
|
101
|
+
* process next applies the profile — the live patch watcher recomposes on
|
|
102
|
+
* some profile shapes, otherwise on the next start — and the settings
|
|
103
|
+
* card reports the divergence (pendingRestart) instead of guessing.
|
|
104
|
+
* While the toggle has never been set (undefined), the plugin does not
|
|
105
|
+
* touch the patch file at all.
|
|
87
106
|
*/
|
|
88
|
-
|
|
107
|
+
lanBind?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The profile whose cordis.patch.yml the LAN bind toggle manages.
|
|
110
|
+
* Defaults to the DSH_PROFILE environment variable, then "web". Must be
|
|
111
|
+
* a single safe path segment (the DSH_PROFILE env fallback bypasses this
|
|
112
|
+
* schema, so the path builder asserts containment independently).
|
|
113
|
+
*/
|
|
114
|
+
profile?: string;
|
|
89
115
|
/** Master switch for the plugin (browser half + host pairing surfaces). */
|
|
90
116
|
enabled?: boolean;
|
|
91
117
|
}
|
|
@@ -95,9 +121,12 @@ export declare const Config: z<Config>;
|
|
|
95
121
|
* which legitimately resolves to `undefined` when unset (the schema keeps it
|
|
96
122
|
* optional, so `Required` alone would over-narrow it to `string`).
|
|
97
123
|
*/
|
|
98
|
-
type ResolvedConfig = Required<Omit<Config, 'publicBaseUrl' | 'devicesFile'>> & {
|
|
124
|
+
type ResolvedConfig = Required<Omit<Config, 'publicBaseUrl' | 'devicesFile' | 'lanBind' | 'profile'>> & {
|
|
99
125
|
publicBaseUrl: string | undefined;
|
|
100
126
|
devicesFile: string;
|
|
127
|
+
/** undefined until the user flips the toggle once; undefined never writes the patch. */
|
|
128
|
+
lanBind: boolean | undefined;
|
|
129
|
+
profile: string;
|
|
101
130
|
};
|
|
102
131
|
/**
|
|
103
132
|
* The single mapping from resolved plugin config to the pairing service
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,CAAC,MAAM,aAAa,CAAA;AAO3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA8BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;;;;;;;WAWG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA+C,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,EAAsB,iBAAiB,CAAA;AAEpF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;;;;OAWG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAa3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC,GAAG;IACtG,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAkBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAgetD"}
|