@tonyruan/dsh-bridge-host 0.1.1-beta.4 → 0.1.1-beta.7
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 +14 -9
- package/lib/agent.mjs +12 -6
- package/lib/child.mjs +38 -38
- package/lib/client.js +10 -7
- package/lib/controller.mjs +154 -26
- package/lib/index.mjs +1708 -1426
- package/lib/local-management.mjs +304 -47
- package/lib/management.mjs +177 -49
- package/lib/multi-client-profile.mjs +2 -2
- package/lib/multi-client-session.mjs +54 -42
- package/lib/profile-store.mjs +2 -2
- package/lib/session.mjs +57 -45
- package/package.json +48 -48
- package/scripts/prepublish-check.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
# DSH Bridge host beta
|
|
2
|
-
|
|
3
|
-
A community DSH-compatible phone-access plugin, not an official DeepSeek product. Requires Node.js >=24.9.0 and DSH0.2.2-rc.1.
|
|
4
|
-
|
|
5
|
-
Package: @tonyruan/dsh-bridge-host. Use the official DSH plugin manager for installation; stop the target profile before package-name migration. Cordis ID dsh-phone-access, plugin name phone-access, API routes and existing profileDirectory/identity remain stable. The default profile is the existing .dsh-phone-profile; changing an npm name does not authorize replacing data.
|
|
6
|
-
|
|
7
|
-
This generic build contains no personal relay preset or update root/feed. Import the original trusted deployment configuration or use the private migration bridge locally before replacing an old configured build. Existing paired profiles without deployment configuration fail recoverably and retain identity, pairing and login data. Strict LAN-only use does not enable update or diagnostic networking.
|
|
8
|
-
|
|
9
|
-
Own project code is licensed under the standard PolyForm Noncommercial License1.0.0 in LICENSE. This is not OSI open-source; permitted purposes and exceptions are governed by that exact text, not a blanket promise about every business/payment scenario. Third-party components retain their own permission, copyright and NOTICE terms in THIRD-PARTY-NOTICES.txt and oss-licenses/. These own-code terms do not restrict upstream rights. The complete own development project/Git repository is not included; npm runtime JavaScript is publicly readable. Native platform download assets are not included in this npm release.
|
|
1
|
+
# DSH Bridge host beta
|
|
2
|
+
|
|
3
|
+
A community DSH-compatible phone-access plugin, not an official DeepSeek product. Requires Node.js >=24.9.0 and DSH0.2.2-rc.1.
|
|
4
|
+
|
|
5
|
+
Package: @tonyruan/dsh-bridge-host. Use the official DSH plugin manager for installation; stop the target profile before package-name migration. Cordis ID dsh-phone-access, plugin name phone-access, API routes and existing profileDirectory/identity remain stable. The default profile is the existing .dsh-phone-profile; changing an npm name does not authorize replacing data.
|
|
6
|
+
|
|
7
|
+
This generic build contains no personal relay preset or update root/feed. Import the original trusted deployment configuration or use the private migration bridge locally before replacing an old configured build. Existing paired profiles without deployment configuration fail recoverably and retain identity, pairing and login data. Strict LAN-only use does not enable update or diagnostic networking.
|
|
8
|
+
|
|
9
|
+
Own project code is licensed under the standard PolyForm Noncommercial License1.0.0 in LICENSE. This is not OSI open-source; permitted purposes and exceptions are governed by that exact text, not a blanket promise about every business/payment scenario. Third-party components retain their own permission, copyright and NOTICE terms in THIRD-PARTY-NOTICES.txt and oss-licenses/. These own-code terms do not restrict upstream rights. The complete own development project/Git repository is not included; npm runtime JavaScript is publicly readable. Native platform download assets are not included in this npm release.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Beta6/build26 retains the original validated persisted connection realm across upgrades and LAN-only restarts, including previously paired WSS realms without an unused relay leaf. Existing trusted deployment is reused without import or rewrite. AUTO still requires trusted TLS material. Legacy private code-only presets require the separately delivered Linux pre-upgrade migration helper before replacement; it statically validates the original local package and pin, never executes old code or fetches a new network leaf. No identity/pairing/schema reinitialization or client update-source JSON is required.
|
|
13
|
+
|
|
14
|
+
Beta7/build27 fixes the setting sidebar icon through the native settings.section.icon slot, without changing the phone-connection name or the general settings icon. An already-native-authenticated hostname may be used only for independently proven direct LAN sockets; the private internal flag cannot authorize relay, unknown, virtual/VPN or unverified interfaces, or bypass native Cookie/Host/Origin. The UI distinguishes native admission403 from the plugin fixed management403, and both remain blocked. Targeted raw-LAN positives use synthetic interface facts; the actual rc.1 browser/navigation checks use an isolated loopback profile. The real user Ubuntu version and route were not inspected, so site-specific resolution and a real physical-LAN success are not claimed.
|
package/lib/agent.mjs
CHANGED
|
@@ -332,7 +332,13 @@ var require_is_electron = __commonJS({
|
|
|
332
332
|
}
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
//
|
|
335
|
+
// host-config-migration-r1/host/phone-access-plugin/src/lan-provenance.mjs
|
|
336
|
+
var accepted = /* @__PURE__ */ new WeakMap();
|
|
337
|
+
function acceptedLanEndpoint(socket) {
|
|
338
|
+
return socket.destroyed ? null : accepted.get(socket) ?? null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// host-config-migration-r1/host/phone-access-plugin/src/agent.mjs
|
|
336
342
|
import net from "node:net";
|
|
337
343
|
import { once } from "node:events";
|
|
338
344
|
|
|
@@ -15327,7 +15333,7 @@ function noise(init = {}) {
|
|
|
15327
15333
|
return (components) => new Noise(components, init);
|
|
15328
15334
|
}
|
|
15329
15335
|
|
|
15330
|
-
//
|
|
15336
|
+
// host-config-migration-r1/host/phone-access-plugin/src/lan-trace.mjs
|
|
15331
15337
|
var LAN_DETAIL_CODES = Object.freeze(["LAN_TCP_ACCEPTED", "LAN_TCP_CLOSED_REJECTED", "LAN_TCP_DISABLED_REJECTED", "LAN_TCP_POLICY_REJECTED", "LAN_TCP_SUBNET_REJECTED", "LAN_TCP_LIMIT_REJECTED", "LAN_TCP_GLOBAL_RATE_REJECTED", "LAN_TCP_SOURCE_RATE_REJECTED", "LAN_TCP_RATE_KEYS_REJECTED", "LAN_AGENT_CLOSED_REJECTED", "LAN_AGENT_LIMIT_REJECTED", "LAN_AGENT_HANDSHAKE_LIMIT_REJECTED", "LAN_AGENT_PEER_LIMIT_REJECTED", "LAN_AGENT_ADMISSION_REJECTED", "LAN_AGENT_READMISSION_REJECTED", "LAN_AGENT_FIRST_FRAME_EOF", "LAN_AGENT_NOISE_FAILED", "LAN_AGENT_FIRST_FRAME_TIMEOUT", "LAN_AGENT_FIRST_FRAME_FAILED", "LAN_AGENT_BACKEND_CONNECT_FAILED", "LAN_AGENT_REGISTER_FAILED", "LAN_AGENT_FORWARD_FAILED", "LAN_AGENT_CHANNEL_READY", "LAN_AGENT_CHANNEL_CLOSED"]);
|
|
15332
15338
|
var codes = new Set(LAN_DETAIL_CODES);
|
|
15333
15339
|
var bounded = (v, max) => Number.isSafeInteger(v) && v >= 0 ? Math.min(max, v) : 0;
|
|
@@ -15395,7 +15401,7 @@ function createLanTrace(diagnostic = () => {
|
|
|
15395
15401
|
return { sample, lifetime, now };
|
|
15396
15402
|
}
|
|
15397
15403
|
|
|
15398
|
-
//
|
|
15404
|
+
// host-config-migration-r1/host/noise-lab/core.mjs
|
|
15399
15405
|
var silent = Object.assign(() => {
|
|
15400
15406
|
}, { enabled: false, error: () => {
|
|
15401
15407
|
}, trace: () => {
|
|
@@ -15435,7 +15441,7 @@ async function temporaryIdentity() {
|
|
|
15435
15441
|
return { privateKey, peerId: peerIdFromPrivateKey(privateKey) };
|
|
15436
15442
|
}
|
|
15437
15443
|
|
|
15438
|
-
//
|
|
15444
|
+
// host-config-migration-r1/host/phone-access-plugin/src/agent.mjs
|
|
15439
15445
|
async function createPcAgent({ backendPort, listenPort = 0, listen = true, identity: providedIdentity, pairing = () => ({ confirmed: false, pin: null }), acquireLease, registerBackend, diagnostic = () => {
|
|
15440
15446
|
}, factories = {} }) {
|
|
15441
15447
|
if (!Number.isInteger(backendPort) || backendPort < 1 || backendPort > 65535) throw Error("INVALID_FIXED_BACKEND");
|
|
@@ -15452,7 +15458,7 @@ async function createPcAgent({ backendPort, listenPort = 0, listen = true, ident
|
|
|
15452
15458
|
let handshaking = 0, closed = false, attempts = 0;
|
|
15453
15459
|
const stats = { handshakes: 0, rejected: 0, handshakeFailed: 0, dshConnections: 0, upBytes: 0, downBytes: 0 };
|
|
15454
15460
|
const acceptWire = (wire, { route = "RELAY", pairId } = {}) => {
|
|
15455
|
-
const attempt = ++attempts, began = trace.now();
|
|
15461
|
+
const attempt = ++attempts, began = trace.now(), lanEndpoint = route === "LAN" ? acceptedLanEndpoint(wire) : null;
|
|
15456
15462
|
const event = (code3) => {
|
|
15457
15463
|
try {
|
|
15458
15464
|
diagnostic({ stage: route === "LAN" ? "LAN" : "AUTH", code: code3, attempt });
|
|
@@ -15629,7 +15635,7 @@ async function createPcAgent({ backendPort, listenPort = 0, listen = true, ident
|
|
|
15629
15635
|
check();
|
|
15630
15636
|
if (registerBackend) {
|
|
15631
15637
|
phase = "REGISTER";
|
|
15632
|
-
const registration = Promise.resolve(registerBackend({ localAddress: backend.localAddress, localPort: backend.localPort, remoteAddress: backend.remoteAddress, remotePort: backend.remotePort })).then((fn) => {
|
|
15638
|
+
const registration = Promise.resolve(registerBackend({ localAddress: backend.localAddress, localPort: backend.localPort, remoteAddress: backend.remoteAddress, remotePort: backend.remotePort }, lease && lease.valid() ? { schema: 1, route: route === "LAN" && lanEndpoint ? "LAN" : route === "RELAY" ? "RELAY" : "UNKNOWN", peerId: lease.peerId, pairId: lease.pairId, generation: lease.generation, lanEndpoint: route === "LAN" ? lanEndpoint : null } : null)).then((fn) => {
|
|
15633
15639
|
let released = false;
|
|
15634
15640
|
const once2 = () => {
|
|
15635
15641
|
if (released) return;
|
package/lib/child.mjs
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import {createPcAgent} from './agent.mjs';
|
|
2
|
-
import {createPhoneSession} from './session.mjs';
|
|
3
|
-
import {randomUUID} from 'node:crypto';
|
|
4
|
-
let agent,closing=false,heartbeat;
|
|
5
|
-
const registrations=new Map();
|
|
6
|
-
function registerBackend(tuple){return new Promise((resolve,reject)=>{
|
|
7
|
-
const registrationId=randomUUID(),timer=setTimeout(()=>{registrations.delete(registrationId);process.send?.({op:'backend.close',registrationId},()=>{});reject(Error('BACKEND_REGISTRATION_TIMEOUT'));},3000);
|
|
8
|
-
registrations.set(registrationId,{resolve,reject,timer});
|
|
9
|
-
process.send?.({op:'backend.open',registrationId,tuple},error=>{if(error){clearTimeout(timer);registrations.delete(registrationId);reject(Error('BACKEND_REGISTRATION_FAILED'));}});
|
|
10
|
-
});}
|
|
11
|
-
const finish=async()=>{if(closing)return;closing=true;clearInterval(heartbeat);await agent?.close();process.exit(0);};
|
|
12
|
-
process.on('disconnect',finish);process.on('SIGTERM',finish);process.on('SIGINT',finish);
|
|
13
|
-
// Parent IPC is the only controller. No unauthenticated control HTTP listener exists.
|
|
14
|
-
process.on('message',async message=>{
|
|
15
|
-
if(closing)return;
|
|
16
|
-
try{
|
|
17
|
-
if(message?.op==='backend.ack'){
|
|
18
|
-
const r=registrations.get(message.registrationId);if(!r)return;registrations.delete(message.registrationId);clearTimeout(r.timer);
|
|
19
|
-
if(!message.ok)r.reject(Error('BACKEND_REGISTRATION_REJECTED'));else {let released=false;r.resolve(()=>{if(released)return;released=true;process.send?.({op:'backend.close',registrationId:message.registrationId},()=>{});});}return;
|
|
20
|
-
}
|
|
21
|
-
if(message?.op==='init'){
|
|
22
|
-
if(agent)throw Error('ALREADY_INITIALIZED');
|
|
23
|
-
if(message.profileDirectory&&!message.relay)throw Error('PERSISTENT_PROFILE_REQUIRES_FIXED_RELAY');
|
|
24
|
-
agent=message.relay?await createPhoneSession({backendPort:message.backendPort,connectionMode:message.connectionMode??'auto',lanAddress:message.lanAddress,relay:message.relay,relayCa:message.relayCa,relayLeaf:message.relayLeaf,profileDirectory:message.profileDirectory,lanEnabled:message.lanEnabled===true,registerBackend,diagnostics:{record:event=>{if(process.connected)process.send?.({op:'diagnostic',event:{...event,generation:Number.isInteger(message.diagnosticGeneration)?message.diagnosticGeneration:0}},()=>{});}}}):await createPcAgent({backendPort:message.backendPort,registerBackend});
|
|
25
|
-
heartbeat=setInterval(()=>{if(process.connected)process.send?.({op:'diagnostic',event:{stage:'LOCAL',code:'COMPANION_HEARTBEAT',count:1,generation:Number.isInteger(message.diagnosticGeneration)?message.diagnosticGeneration:0}},()=>{});},30000);heartbeat.unref?.();
|
|
26
|
-
process.send?.({id:message.id,ok:true,status:{pid:process.pid,...agent.status()}});
|
|
27
|
-
}else if(message?.op==='status'&&agent)process.send?.({id:message.id,ok:true,status:{pid:process.pid,...agent.status()}});
|
|
28
|
-
else if(message?.op==='confirmReverseScan'&&agent?.confirmReverseScan){await agent.confirmReverseScan(message.qr);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
29
|
-
else if(message?.op==='pair/start'&&agent?.beginPairing){await agent.beginPairing();process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
30
|
-
else if(message?.op==='pair/cancel'&&agent?.cancelPairing){await agent.cancelPairing(message.sessionId);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
31
|
-
else if(message?.op==='revoke'&&agent?.revoke){await agent.revoke(message.target);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
32
|
-
else if(message?.op==='lan/settings'&&agent?.setLanEnabled){await agent.setLanEnabled(message.enabled);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
33
|
-
else if(message?.op==='reconnect'&&agent?.reconnect){await agent.reconnect();process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
34
|
-
else if(message?.op==='stop')await finish();
|
|
35
|
-
}catch(error){const code=error?.code??error?.message;process.send?.({id:message?.id,ok:false,error:/^[A-Z_]{3,70}$/.test(code??'')?code:'AGENT_OPERATION_FAILED'});if(!agent)await finish();}
|
|
36
|
-
});
|
|
37
|
-
// Failed owners must not leave a child waiting forever before init.
|
|
38
|
-
setTimeout(()=>{if(!agent)finish();},10000).unref();
|
|
1
|
+
import {createPcAgent} from './agent.mjs';
|
|
2
|
+
import {createPhoneSession} from './session.mjs';
|
|
3
|
+
import {randomUUID} from 'node:crypto';
|
|
4
|
+
let agent,closing=false,heartbeat;
|
|
5
|
+
const registrations=new Map();
|
|
6
|
+
function registerBackend(tuple,provenance){return new Promise((resolve,reject)=>{
|
|
7
|
+
const registrationId=randomUUID(),timer=setTimeout(()=>{registrations.delete(registrationId);process.send?.({op:'backend.close',registrationId},()=>{});reject(Error('BACKEND_REGISTRATION_TIMEOUT'));},3000);
|
|
8
|
+
registrations.set(registrationId,{resolve,reject,timer});
|
|
9
|
+
process.send?.({op:'backend.open',registrationId,tuple,provenance},error=>{if(error){clearTimeout(timer);registrations.delete(registrationId);reject(Error('BACKEND_REGISTRATION_FAILED'));}});
|
|
10
|
+
});}
|
|
11
|
+
const finish=async()=>{if(closing)return;closing=true;clearInterval(heartbeat);await agent?.close();process.exit(0);};
|
|
12
|
+
process.on('disconnect',finish);process.on('SIGTERM',finish);process.on('SIGINT',finish);
|
|
13
|
+
// Parent IPC is the only controller. No unauthenticated control HTTP listener exists.
|
|
14
|
+
process.on('message',async message=>{
|
|
15
|
+
if(closing)return;
|
|
16
|
+
try{
|
|
17
|
+
if(message?.op==='backend.ack'){
|
|
18
|
+
const r=registrations.get(message.registrationId);if(!r)return;registrations.delete(message.registrationId);clearTimeout(r.timer);
|
|
19
|
+
if(!message.ok)r.reject(Error('BACKEND_REGISTRATION_REJECTED'));else {let released=false;r.resolve(()=>{if(released)return;released=true;process.send?.({op:'backend.close',registrationId:message.registrationId},()=>{});});}return;
|
|
20
|
+
}
|
|
21
|
+
if(message?.op==='init'){
|
|
22
|
+
if(agent)throw Error('ALREADY_INITIALIZED');
|
|
23
|
+
if(message.profileDirectory&&!message.relay)throw Error('PERSISTENT_PROFILE_REQUIRES_FIXED_RELAY');
|
|
24
|
+
agent=message.relay?await createPhoneSession({backendPort:message.backendPort,connectionMode:message.connectionMode??'auto',lanAddress:message.lanAddress,relay:message.relay,relayCa:message.relayCa,relayLeaf:message.relayLeaf,profileDirectory:message.profileDirectory,lanEnabled:message.lanEnabled===true,registerBackend,diagnostics:{record:event=>{if(process.connected)process.send?.({op:'diagnostic',event:{...event,generation:Number.isInteger(message.diagnosticGeneration)?message.diagnosticGeneration:0}},()=>{});}}}):await createPcAgent({backendPort:message.backendPort,registerBackend});
|
|
25
|
+
heartbeat=setInterval(()=>{if(process.connected)process.send?.({op:'diagnostic',event:{stage:'LOCAL',code:'COMPANION_HEARTBEAT',count:1,generation:Number.isInteger(message.diagnosticGeneration)?message.diagnosticGeneration:0}},()=>{});},30000);heartbeat.unref?.();
|
|
26
|
+
process.send?.({id:message.id,ok:true,status:{pid:process.pid,...agent.status()}});
|
|
27
|
+
}else if(message?.op==='status'&&agent)process.send?.({id:message.id,ok:true,status:{pid:process.pid,...agent.status()}});
|
|
28
|
+
else if(message?.op==='confirmReverseScan'&&agent?.confirmReverseScan){await agent.confirmReverseScan(message.qr);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
29
|
+
else if(message?.op==='pair/start'&&agent?.beginPairing){await agent.beginPairing();process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
30
|
+
else if(message?.op==='pair/cancel'&&agent?.cancelPairing){await agent.cancelPairing(message.sessionId);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
31
|
+
else if(message?.op==='revoke'&&agent?.revoke){await agent.revoke(message.target);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
32
|
+
else if(message?.op==='lan/settings'&&agent?.setLanEnabled){await agent.setLanEnabled(message.enabled);process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
33
|
+
else if(message?.op==='reconnect'&&agent?.reconnect){await agent.reconnect();process.send?.({id:message.id,ok:true,status:agent.status()});}
|
|
34
|
+
else if(message?.op==='stop')await finish();
|
|
35
|
+
}catch(error){const code=error?.code??error?.message;process.send?.({id:message?.id,ok:false,error:/^[A-Z_]{3,70}$/.test(code??'')?code:'AGENT_OPERATION_FAILED'});if(!agent)await finish();}
|
|
36
|
+
});
|
|
37
|
+
// Failed owners must not leave a child waiting forever before init.
|
|
38
|
+
setTimeout(()=>{if(!agent)finish();},10000).unref();
|
package/lib/client.js
CHANGED
|
@@ -31,8 +31,9 @@ window.__ModuleLoader__.load({ id: "@tonyruan/dsh-bridge-host", factory: (requir
|
|
|
31
31
|
decidePairing:'/api/phone-access/pair/confirm', revoke:'/api/phone-access/revoke',
|
|
32
32
|
reconnect:'/api/phone-access/start', diagnosticInitialize:'/api/phone-access/diagnostics/initialize', diagnosticSettings:'/api/phone-access/diagnostics/settings', diagnosticExport:'/api/phone-access/diagnostics/export', lanSettings:'/api/phone-access/lan/settings', startupSettings:'/api/phone-access/startup/settings'
|
|
33
33
|
});
|
|
34
|
-
const messages = { CLIENT_TARGET_REQUIRED:"请选择要撤销的客户端。",PAIRING_STALE:"身份或代次已变化,请刷新后重试。",PAIRING_BUSY:"已有配对正在进行。",PROFILE_CAPACITY:"已达到四个客户端上限。",PROFILE_UNAVAILABLE:"安全存储状态不确定,访问已关闭。请恢复存储并读取状态,不要删除配对或重新初始化。", STARTUP_SETTINGS_UNAVAILABLE:"自动启动设置无法保存,请检查本地存储。", AUTOSTART_PAIR_REQUIRED:"请先手动完成手机配对,自动启动不会创建身份。", DIAGNOSTICS_STORAGE_UNAVAILABLE:"日志存储不可用,业务连接可继续使用。", DIAGNOSTICS_INITIALIZE_REQUIRED:"请先初始化独立日志身份。", DIAGNOSTICS_CONFIRM_REQUIRED:"请确认日志操作。", CONFIG_REQUIRED: "未找到有效的预置连接配置,请安装已配置中继与公开证书的用户专用插件包。", TRUST_EXPIRED: "relay 证书已过期。请更新插件包中的公开证书配置后重试。", BACKEND_NOT_CONNECTED: "配对管理接口尚未接入。请更新插件后重试。", SAME_ORIGIN_REQUIRED: "管理请求被拒绝。请从这台电脑的 DSH 重新打开页面。", LOCAL_MANAGEMENT_REQUIRED: "
|
|
34
|
+
const messages = { CLIENT_TARGET_REQUIRED:"请选择要撤销的客户端。",PAIRING_STALE:"身份或代次已变化,请刷新后重试。",PAIRING_BUSY:"已有配对正在进行。",PROFILE_CAPACITY:"已达到四个客户端上限。",PROFILE_UNAVAILABLE:"安全存储状态不确定,访问已关闭。请恢复存储并读取状态,不要删除配对或重新初始化。", STARTUP_SETTINGS_UNAVAILABLE:"自动启动设置无法保存,请检查本地存储。", AUTOSTART_PAIR_REQUIRED:"请先手动完成手机配对,自动启动不会创建身份。", DIAGNOSTICS_STORAGE_UNAVAILABLE:"日志存储不可用,业务连接可继续使用。", DIAGNOSTICS_INITIALIZE_REQUIRED:"请先初始化独立日志身份。", DIAGNOSTICS_CONFIRM_REQUIRED:"请确认日志操作。", CONFIG_REQUIRED: "未找到有效的预置连接配置,请安装已配置中继与公开证书的用户专用插件包。", TRUST_EXPIRED: "relay 证书已过期。请更新插件包中的公开证书配置后重试。", BACKEND_NOT_CONNECTED: "配对管理接口尚未接入。请更新插件后重试。", SAME_ORIGIN_REQUIRED: "管理请求被拒绝。请从这台电脑的 DSH 重新打开页面。", LOCAL_MANAGEMENT_REQUIRED: "请在本机或已验证的局域网连接中管理插件;公网中继不允许管理。也可保留原有 SSH 转发方式。", STALE_REQUEST: "手机请求已变化或过期,请刷新后重新核对指纹。", PAIRING_EXPIRED: "二维码已过期,请重新开始配对。", RELAY_UNREACHABLE: "无法连接 relay。请检查地址、网络和证书配置。", TRUST_MISMATCH: "relay 证书校验失败,请核对公开证书或 SHA-256 指纹。", TIMEOUT: "请求超时。操作结果尚未确认,请刷新状态后再决定是否重试。", NETWORK: "无法读取连接状态。请检查 DSH 是否在线,再刷新。" };
|
|
35
35
|
Object.assign(messages,{"CONFIGURATION_REQUIRED": "未找到有效的预置连接配置,请更新用户专用插件包。", "INITIALIZE_REQUIRED": "请先点击“初始化连接”,再开始配对。", "PAIR_REQUEST_CHANGED": "手机请求已变化或过期,请重新开始配对并核对指纹。", "ALREADY_PAIRED": "已配对一部手机,请先撤销现有配对。", "INVALID_ACTION_BODY": "请求未被接受,请刷新页面后重试。", "RELAY_OFFLINE": "中继离线或连接超时,请检查网络后重新诊断。", "RELAY_DISCONNECTED": "中继连接已断开,请重新连接后再试。", "TLS_LEAF_EXPIRED": "中继公开证书已过期或尚未生效,请更新用户专用插件包。", "TLS_LEAF_SAN_MISMATCH": "中继证书的域名或 IP 不匹配,请核对插件配置。", "TLS_LEAF_PIN_MISMATCH": "中继证书指纹不匹配,已拒绝连接。请更新正确的公开证书配置。", "TLS_LEAF_CA_FORBIDDEN": "需要固定中继的公开叶证书,请更新插件配置。", "PERSISTENCE_UNSUPPORTED_PLATFORM": "当前系统尚不支持本机身份存储。", "PROFILE_NOT_PROVISIONED": "请先初始化本机连接身份。", "PROFILE_RELAY_MISMATCH": "本机身份绑定了其他中继,请核对已安装的插件配置。", "LINUX_VAULT_UNSAFE_IN_USE_OR_RECOVERY_REQUIRED": "本机身份目录不可用、被占用或需要恢复。请关闭其他实例后检查目录。", "VAULT_UNAVAILABLE_OR_IN_USE": "Windows 安全存储不可用或被其他实例占用,请关闭其他实例后重试。", "LOCAL_AGENT_OPERATION_FAILED": "本机连接操作失败,请刷新并检查设备与中继状态。"});
|
|
36
|
+
messages.NATIVE_ACCESS_DENIED = "DSH 拒绝了此页面的认证或来源,请从原 DSH 入口重新打开。插件不会放宽认证或代理规则。";
|
|
36
37
|
class UiError extends Error {
|
|
37
38
|
constructor(code) {
|
|
38
39
|
super(messages[code] || "操作未完成。请刷新状态;如仍失败,请检查插件和 relay 配置。");
|
|
@@ -96,13 +97,13 @@ window.__ModuleLoader__.load({ id: "@tonyruan/dsh-bridge-host", factory: (requir
|
|
|
96
97
|
const abort = new AbortController(), timer = setTimeout(() => abort.abort(), 12e3);
|
|
97
98
|
try {
|
|
98
99
|
const r = await request(path, { method: body === void 0 ? "GET" : "POST", credentials: "same-origin", cache: "no-store", signal: abort.signal, headers: body === void 0 ? {} : { "content-type": "application/json" }, ...body === void 0 ? {} : { body: JSON.stringify(body) } });
|
|
99
|
-
if (r.status === 403) throw new UiError('LOCAL_MANAGEMENT_REQUIRED');
|
|
100
100
|
let data;
|
|
101
101
|
try {
|
|
102
102
|
data = await r.json();
|
|
103
103
|
} catch {
|
|
104
|
-
throw new UiError("INVALID_RESPONSE");
|
|
104
|
+
throw new UiError(r.status === 403 ? 'NATIVE_ACCESS_DENIED' : "INVALID_RESPONSE");
|
|
105
105
|
}
|
|
106
|
+
if (r.status === 403) throw new UiError(data?.error?.code === 'LOCAL_MANAGEMENT_REQUIRED' ? 'LOCAL_MANAGEMENT_REQUIRED' : 'NATIVE_ACCESS_DENIED');
|
|
106
107
|
if (!r.ok) throw new UiError(errorCode(data?.error) || 'REQUEST_FAILED');
|
|
107
108
|
return data;
|
|
108
109
|
} catch (e) {
|
|
@@ -122,9 +123,10 @@ window.__ModuleLoader__.load({ id: "@tonyruan/dsh-bridge-host", factory: (requir
|
|
|
122
123
|
}
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
function PhoneIcon({ size = 20, ...props } = {}) {
|
|
127
|
+
return h("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, focusable: false, ...props },
|
|
128
|
+
h("rect", { x: 3, y: 3, width: 10, height: 18, rx: 2 }),
|
|
129
|
+
h("path", { d: "M6.5 6h3M7 18h2M16 8a5 5 0 0 1 0 8M19 5a10 10 0 0 1 0 14" }));
|
|
128
130
|
}
|
|
129
131
|
const css = `.dsh-phone{font-family:inherit;font-size:14px;line-height:1.6;color:var(--dsw-alias-label-primary,#202329);max-width:690px;min-width:0}.dsh-phone *{box-sizing:border-box}.dsh-phone h2{font-size:20px;line-height:1.4;font-weight:600;margin:0}.dsh-phone h3{font-size:15px;font-weight:600;margin:0 0 6px}.dsh-phone p{margin:5px 0 12px}.dsh-phone .muted{color:var(--dsw-alias-label-secondary,#69707b);font-size:13px}.dsh-phone .head,.dsh-phone .actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap}.dsh-phone .head{margin-bottom:8px}.dsh-phone .state{border:1px solid var(--dsw-alias-border-main,#e1e4e9);border-radius:10px;padding:14px 16px;margin:18px 0}.dsh-phone .state strong{font-weight:600}.dsh-phone .dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#959ba6;margin-right:8px}.dsh-phone .dot.online{background:#2b8b65}.dsh-phone .dot.wait{background:#b28122}.dsh-phone .block{padding:18px 0;border-top:1px solid var(--dsw-alias-border-main,#e7e9ed)}.dsh-phone label{display:block;font-size:13px;margin:12px 0 5px}.dsh-phone input,.dsh-phone select,.dsh-phone textarea{width:100%;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-main,#cfd4dd);border-radius:7px;padding:8px 10px;min-width:0}.dsh-phone textarea{resize:vertical}.dsh-phone button{font:inherit;font-size:13px;cursor:pointer;line-height:20px;padding:7px 12px;border:1px solid var(--dsw-alias-border-main,#d9dde4);border-radius:7px;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit}.dsh-phone button.primary{background:var(--dsw-alias-brand-primary-new-colorprimary-new-color,#4d6bfe);color:white;border-color:transparent}.dsh-phone button.danger{color:#b13d3d}.dsh-phone button:disabled{opacity:.48;cursor:not-allowed}.dsh-phone :is(button,input,select,textarea):focus-visible{outline:2px solid #4d6bfe;outline-offset:2px}.dsh-phone .alert{border-left:3px solid #bb7830;padding:9px 12px;background:var(--dsw-alias-bg-layer-1,#faf7f2);margin:12px 0;overflow-wrap:anywhere}.dsh-phone .fingerprint,.dsh-phone pre{font:12px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere;white-space:pre-wrap;background:var(--dsw-alias-bg-layer-1,#f5f6f8);border-radius:7px;padding:10px;user-select:text;margin:8px 0}.dsh-phone .qr-layout{display:flex;align-items:flex-start;gap:20px;flex-wrap:wrap;margin:14px 0}.dsh-phone .qr{background:#fff;color:#111;max-width:100%;width:196px;height:auto;border:1px solid #e4e7ec;border-radius:7px;flex:none;image-rendering:pixelated}.dsh-phone .qr-copy{flex:1;min-width:180px}.dsh-phone .check{display:flex;align-items:flex-start;gap:9px;margin:12px 0}.dsh-phone .check input{width:16px;height:16px;flex:none;margin-top:3px}.dsh-phone .diagnostics{padding-left:20px}.dsh-phone .diagnostics li{padding:3px 0}.dsh-phone .tag{font-size:12px;font-weight:400;margin-left:8px}.dsh-phone .actions{margin-top:12px}.dsh-phone .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}@media(max-width:460px){.dsh-phone .state{padding:12px}.dsh-phone .qr-layout{gap:12px}.dsh-phone .qr{width:180px}.dsh-phone button{min-height:38px}}`;
|
|
130
132
|
function createPhonePage(api) {
|
|
@@ -421,7 +423,8 @@ function installSubmissionGuard(conversation, sessions, enabled, options={}) {
|
|
|
421
423
|
function apply(ctx) {
|
|
422
424
|
installPhoneCompatibility(ctx);
|
|
423
425
|
const Page = createPhonePage(createPhoneApi());
|
|
424
|
-
ctx.slots.inject("settings.section", () => ctx.slots.register({ name: "settings.section", id: "phone-access", order: 18, label: "手机连接"
|
|
426
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({ name: "settings.section", id: "phone-access", order: 18, label: "手机连接" }, Page));
|
|
427
|
+
ctx.slots.inject("settings.section.icon", () => ctx.slots.register({ name: "settings.section.icon", id: "phone-access" }, ({size = 16}) => h(PhoneIcon, {size})));
|
|
425
428
|
}
|
|
426
429
|
return { inject: ["slots"], apply, createPhoneApi, createPhonePage, PhoneIcon, publicStatus };
|
|
427
430
|
} });
|
package/lib/controller.mjs
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// host-config-migration-r1/host/diagnostics/lifecycle-trace.mjs
|
|
9
9
|
var STOP_REASON = Object.freeze({ USER_STOP: 1, PLUGIN_SHUTDOWN: 2, USER_RESTART: 3, REPLACE_CHILD: 4, INIT_FAILURE: 5, OWNER_DISCONNECT: 6 });
|
|
10
10
|
var SIGNAL = Object.freeze({ NONE: 0, SIGTERM: 1, SIGKILL: 2, SIGINT: 3, SIGABRT: 4, SIGSEGV: 5, SIGBUS: 6, SIGHUP: 7, OTHER: 8 });
|
|
11
11
|
var CRITICAL_CODES = Object.freeze(["COMPANION_START_BEGIN", "COMPANION_START_READY", "COMPANION_START_FAILED", "COMPANION_STOP_REQUESTED", "COMPANION_EXIT", "COMPANION_SIGNAL", "COMPANION_IPC_LOST", "DIAG_UPLOAD_FAILED", "RELAY_CONTROL_ENDED", "RELAY_CONTROL_TIMEOUT", "RELAY_CONTROL_CONNECT_FAILED", "RELAY_RECOVERY_BLOCKED"]);
|
|
@@ -41,7 +41,7 @@ function createLifecycleTrace(record) {
|
|
|
41
41
|
} };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// host-config-migration-r1/host/phone-access-plugin/src/controller.mjs
|
|
45
45
|
import { fork } from "node:child_process";
|
|
46
46
|
|
|
47
47
|
// ../../2026-10-22/task-2/node_modules/@libp2p/interface/dist/src/errors.js
|
|
@@ -8055,7 +8055,7 @@ function isSha256Multihash(multihash) {
|
|
|
8055
8055
|
return multihash.code === sha256.code;
|
|
8056
8056
|
}
|
|
8057
8057
|
|
|
8058
|
-
//
|
|
8058
|
+
// host-config-migration-r1/host/phone-access-plugin/src/linux-vault.mjs
|
|
8059
8059
|
import * as nativeFs from "node:fs/promises";
|
|
8060
8060
|
import { constants as nativeConstants } from "node:fs";
|
|
8061
8061
|
import { posix } from "node:path";
|
|
@@ -8214,7 +8214,7 @@ async function openLinuxVault(directory, { platform = process.platform, fs = nat
|
|
|
8214
8214
|
};
|
|
8215
8215
|
}
|
|
8216
8216
|
|
|
8217
|
-
//
|
|
8217
|
+
// host-config-migration-r1/host/phone-access-plugin/src/profile-store.mjs
|
|
8218
8218
|
async function openWindowsVault() {
|
|
8219
8219
|
throw Error("PERSISTENCE_UNSUPPORTED_PLATFORM");
|
|
8220
8220
|
}
|
|
@@ -8250,7 +8250,7 @@ function openProfileVault(directory, options = {}) {
|
|
|
8250
8250
|
throw Error("PERSISTENCE_UNSUPPORTED_PLATFORM");
|
|
8251
8251
|
}
|
|
8252
8252
|
|
|
8253
|
-
//
|
|
8253
|
+
// host-config-migration-r1/host/phone-access-plugin/src/multi-client-profile.mjs
|
|
8254
8254
|
var LIMITS = Object.freeze({ maxPaired: 4, maxPending: 1, maxRecords: 16, maxBytes: 32768, maxPairingMs: 12e4 });
|
|
8255
8255
|
var copy = (value) => structuredClone(value);
|
|
8256
8256
|
var keys = (value, expected) => value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).sort().join(",") === expected;
|
|
@@ -8646,7 +8646,7 @@ var MultiClientProfile = class {
|
|
|
8646
8646
|
}
|
|
8647
8647
|
};
|
|
8648
8648
|
|
|
8649
|
-
//
|
|
8649
|
+
// host-config-migration-r1/host/phone-access-plugin/src/multi-client-runtime-profile.mjs
|
|
8650
8650
|
async function loadMultiClientProfile(directory, options = {}) {
|
|
8651
8651
|
const vault = await (options.openVault ?? openProfileVault)(directory), profile = new MultiClientProfile(vault, options);
|
|
8652
8652
|
try {
|
|
@@ -8673,43 +8673,165 @@ async function initializeMultiClientProfile(directory, relay, { openVault = open
|
|
|
8673
8673
|
}
|
|
8674
8674
|
}
|
|
8675
8675
|
|
|
8676
|
-
//
|
|
8676
|
+
// host-config-migration-r1/host/phone-access-plugin/src/controller.mjs
|
|
8677
8677
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8678
8678
|
import { fileURLToPath } from "node:url";
|
|
8679
8679
|
|
|
8680
|
-
//
|
|
8680
|
+
// host-config-migration-r1/host/phone-access-plugin/src/local-management.mjs
|
|
8681
|
+
import { Server as HttpServer } from "node:http";
|
|
8682
|
+
import { Server as HttpsServer } from "node:https";
|
|
8681
8683
|
import { isIP } from "node:net";
|
|
8682
|
-
|
|
8684
|
+
|
|
8685
|
+
// host-config-migration-r1/host/phone-access-plugin/src/lan-protocol.mjs
|
|
8686
|
+
var MAGIC = Buffer.from("DSHLAN01");
|
|
8687
|
+
|
|
8688
|
+
// host-config-migration-r1/host/phone-access-plugin/src/management-lan.mjs
|
|
8689
|
+
var normalizedAddress = (value) => typeof value === "string" && value.startsWith("::ffff:") ? value.slice(7) : value;
|
|
8690
|
+
|
|
8691
|
+
// host-config-migration-r1/host/phone-access-plugin/src/local-management.mjs
|
|
8683
8692
|
var loopback = (value) => {
|
|
8684
|
-
const a =
|
|
8693
|
+
const a = normalizedAddress(value);
|
|
8685
8694
|
return a === "::1" || isIP(a ?? "") === 4 && a.startsWith("127.");
|
|
8686
8695
|
};
|
|
8687
8696
|
var port = (value) => Number.isInteger(value) && value > 0 && value <= 65535;
|
|
8688
|
-
var key = (s) => [
|
|
8697
|
+
var key = (s) => [normalizedAddress(s.remoteAddress), s.remotePort, normalizedAddress(s.localAddress), s.localPort].join("|");
|
|
8698
|
+
var exact = (o, keys2) => o && typeof o === "object" && !Array.isArray(o) && Object.keys(o).sort().join(",") === keys2.slice().sort().join(",");
|
|
8699
|
+
function provenance(p) {
|
|
8700
|
+
if (!exact(p, ["schema", "route", "peerId", "pairId", "generation", "lanEndpoint"]) || p.schema !== 1 || !["LAN", "RELAY"].includes(p.route) || !/^\w{16,128}$/.test(p.peerId ?? "") || !/^[a-f0-9]{32}$/.test(p.pairId ?? "") || !Number.isSafeInteger(p.generation) || p.generation < 1) return null;
|
|
8701
|
+
if (p.route === "RELAY") return p.lanEndpoint === null ? Object.freeze({ ...p }) : null;
|
|
8702
|
+
const e = p.lanEndpoint;
|
|
8703
|
+
if (!exact(e, ["localAddress", "localPort", "remoteAddress", "remotePort"]) || isIP(normalizedAddress(e.localAddress)) !== 4 || isIP(normalizedAddress(e.remoteAddress)) !== 4 || e.localPort !== 38445 || !port(e.remotePort)) return null;
|
|
8704
|
+
return Object.freeze({ ...p, lanEndpoint: Object.freeze({ ...e }) });
|
|
8705
|
+
}
|
|
8689
8706
|
function createBackendRegistry(backendPort) {
|
|
8690
|
-
const connections = /* @__PURE__ */ new Map();
|
|
8707
|
+
const connections = /* @__PURE__ */ new Map(), tuples = /* @__PURE__ */ new Map(), sockets = /* @__PURE__ */ new WeakMap(), observed = /* @__PURE__ */ new Map(), waiting = /* @__PURE__ */ new Map(), denied = /* @__PURE__ */ new Set();
|
|
8708
|
+
let registryEpoch = 0, owner = /* @__PURE__ */ Symbol("standalone"), observer = null;
|
|
8709
|
+
const invalidate = () => {
|
|
8710
|
+
registryEpoch++;
|
|
8711
|
+
for (const w of waiting.values()) {
|
|
8712
|
+
clearTimeout(w.timer);
|
|
8713
|
+
w.reject(Error("BACKEND_OWNER_CLOSED"));
|
|
8714
|
+
}
|
|
8715
|
+
waiting.clear();
|
|
8716
|
+
for (const r of connections.values()) r.active = false;
|
|
8717
|
+
connections.clear();
|
|
8718
|
+
};
|
|
8691
8719
|
return {
|
|
8692
|
-
|
|
8693
|
-
if (
|
|
8694
|
-
|
|
8720
|
+
attachServer(server) {
|
|
8721
|
+
if (observer || !(server instanceof HttpServer || server instanceof HttpsServer) || !server.listening || server.address()?.port !== backendPort) throw Error("MANAGEMENT_SOCKET_OBSERVER_UNAVAILABLE");
|
|
8722
|
+
const event = server instanceof HttpsServer ? "secureConnection" : "connection";
|
|
8723
|
+
const accept = (socket) => {
|
|
8724
|
+
const k = key(socket);
|
|
8725
|
+
if (observed.size >= 2048 || observed.has(k)) return;
|
|
8726
|
+
observed.set(k, socket);
|
|
8727
|
+
const w = waiting.get(k);
|
|
8728
|
+
if (w) {
|
|
8729
|
+
waiting.delete(k);
|
|
8730
|
+
clearTimeout(w.timer);
|
|
8731
|
+
w.resolve(socket);
|
|
8732
|
+
}
|
|
8733
|
+
socket.once("close", () => {
|
|
8734
|
+
if (observed.get(k) === socket) observed.delete(k);
|
|
8735
|
+
const r = sockets.get(socket);
|
|
8736
|
+
if (r) {
|
|
8737
|
+
r.active = false;
|
|
8738
|
+
if (connections.get(r.id) === r) connections.delete(r.id);
|
|
8739
|
+
if (tuples.get(k) === r) tuples.delete(k);
|
|
8740
|
+
}
|
|
8741
|
+
});
|
|
8742
|
+
};
|
|
8743
|
+
server.on(event, accept);
|
|
8744
|
+
observer = { server, event, accept };
|
|
8745
|
+
return () => {
|
|
8746
|
+
if (observer?.accept !== accept) return;
|
|
8747
|
+
server.off(event, accept);
|
|
8748
|
+
observer = null;
|
|
8749
|
+
invalidate();
|
|
8750
|
+
owner = null;
|
|
8751
|
+
};
|
|
8752
|
+
},
|
|
8753
|
+
beginOwner(next) {
|
|
8754
|
+
invalidate();
|
|
8755
|
+
denied.clear();
|
|
8756
|
+
owner = next;
|
|
8757
|
+
},
|
|
8758
|
+
isOwner(value) {
|
|
8759
|
+
return owner === value && owner !== null;
|
|
8695
8760
|
},
|
|
8696
|
-
|
|
8697
|
-
|
|
8761
|
+
endOwner(value) {
|
|
8762
|
+
if (owner !== value) return;
|
|
8763
|
+
invalidate();
|
|
8764
|
+
owner = null;
|
|
8765
|
+
},
|
|
8766
|
+
async add(id, tuple, p, source = owner) {
|
|
8767
|
+
if (!observer || source !== owner || owner === null || typeof id !== "string" || id.length > 80 || connections.has(id) || connections.size + waiting.size >= 64 || tuples.size >= 512 || !tuple || !loopback(tuple.localAddress) || !loopback(tuple.remoteAddress) || !port(tuple.localPort) || tuple.remotePort !== backendPort) throw Error("BACKEND_REGISTRATION_REJECTED");
|
|
8768
|
+
const began = registryEpoch, k = [normalizedAddress(tuple.localAddress), tuple.localPort, normalizedAddress(tuple.remoteAddress), tuple.remotePort].join("|");
|
|
8769
|
+
if (tuples.has(k) || waiting.has(k) || [...waiting.values()].some((w) => w.id === id)) throw Error("BACKEND_TUPLE_IN_USE");
|
|
8770
|
+
let socket = observed.get(k);
|
|
8771
|
+
if (!socket) socket = await new Promise((resolve, reject) => {
|
|
8772
|
+
const timer = setTimeout(() => {
|
|
8773
|
+
waiting.delete(k);
|
|
8774
|
+
reject(Error("BACKEND_NATIVE_SOCKET_TIMEOUT"));
|
|
8775
|
+
}, 2e3);
|
|
8776
|
+
waiting.set(k, { id, resolve, reject, timer, provenance: provenance(p) });
|
|
8777
|
+
});
|
|
8778
|
+
if (began !== registryEpoch || source !== owner || owner === null || !observer || tuples.has(k) || connections.has(id) || !socket || socket.destroyed || observed.get(k) !== socket) throw Error("BACKEND_NATIVE_SOCKET_REQUIRED");
|
|
8779
|
+
let meta = provenance(p);
|
|
8780
|
+
if (meta && denied.has(meta.peerId + "|" + meta.generation)) meta = null;
|
|
8781
|
+
const r = { id, key: k, owner, active: true, provenance: meta, socket };
|
|
8782
|
+
connections.set(id, r);
|
|
8783
|
+
tuples.set(k, r);
|
|
8784
|
+
sockets.set(socket, r);
|
|
8785
|
+
},
|
|
8786
|
+
remove(id, source = owner) {
|
|
8787
|
+
if (source !== owner) return;
|
|
8788
|
+
for (const [k, w] of waiting) if (w.id === id) {
|
|
8789
|
+
waiting.delete(k);
|
|
8790
|
+
clearTimeout(w.timer);
|
|
8791
|
+
w.reject(Error("BACKEND_CLOSED"));
|
|
8792
|
+
}
|
|
8793
|
+
const r = connections.get(id);
|
|
8794
|
+
if (r) {
|
|
8795
|
+
r.active = false;
|
|
8796
|
+
connections.delete(id);
|
|
8797
|
+
}
|
|
8698
8798
|
},
|
|
8699
8799
|
clear() {
|
|
8700
|
-
|
|
8800
|
+
invalidate();
|
|
8801
|
+
},
|
|
8802
|
+
lookup(socket) {
|
|
8803
|
+
if (!socket) return null;
|
|
8804
|
+
const r = sockets.get(socket);
|
|
8805
|
+
return r ?? (tuples.has(key(socket)) ? { active: false, provenance: null } : null);
|
|
8806
|
+
},
|
|
8807
|
+
active(r, socket) {
|
|
8808
|
+
return !!r && !!observer && r.active && r.owner === owner && owner !== null && r.socket === socket && !socket.destroyed && connections.get(r.id) === r;
|
|
8809
|
+
},
|
|
8810
|
+
denyPeer(peer2, generation) {
|
|
8811
|
+
if (peer2 && Number.isSafeInteger(generation)) denied.add(peer2 + "|" + generation);
|
|
8812
|
+
for (const [id, r] of connections) if (!peer2 || r.provenance?.peerId === peer2 && (generation === void 0 || r.provenance?.generation === generation)) {
|
|
8813
|
+
if (r.provenance) denied.add(r.provenance.peerId + "|" + r.provenance.generation);
|
|
8814
|
+
r.active = false;
|
|
8815
|
+
connections.delete(id);
|
|
8816
|
+
}
|
|
8817
|
+
for (const w of waiting.values()) if (w.provenance && (!peer2 || w.provenance.peerId === peer2 && (generation === void 0 || w.provenance.generation === generation))) denied.add(w.provenance.peerId + "|" + w.provenance.generation);
|
|
8818
|
+
},
|
|
8819
|
+
nativeObserved(socket) {
|
|
8820
|
+
return !!observer && !!socket && !socket.destroyed && observed.get(key(socket)) === socket;
|
|
8701
8821
|
},
|
|
8702
8822
|
has(socket) {
|
|
8703
|
-
|
|
8704
|
-
return [...connections.values()].includes(k);
|
|
8823
|
+
return !!this.lookup(socket);
|
|
8705
8824
|
},
|
|
8706
8825
|
get size() {
|
|
8707
8826
|
return connections.size;
|
|
8827
|
+
},
|
|
8828
|
+
get tracked() {
|
|
8829
|
+
return { observed: observed.size, tuples: tuples.size, waiting: waiting.size };
|
|
8708
8830
|
}
|
|
8709
8831
|
};
|
|
8710
8832
|
}
|
|
8711
8833
|
|
|
8712
|
-
//
|
|
8834
|
+
// host-config-migration-r1/host/phone-access-plugin/src/controller.mjs
|
|
8713
8835
|
var AgentController = class {
|
|
8714
8836
|
constructor({ backendPort, connectionMode = "auto", lanAddress, relay, relayCa, relayLeaf, profileDirectory, childEntry = new URL("./child.mjs", import.meta.url), onDiagnostic = () => {
|
|
8715
8837
|
}, forkProcess = fork, timers = { setInterval, clearInterval } }) {
|
|
@@ -8801,7 +8923,9 @@ var AgentController = class {
|
|
|
8801
8923
|
this.starts++;
|
|
8802
8924
|
this.lifecycle.observe(child, this.starts);
|
|
8803
8925
|
const generation = this.starts;
|
|
8804
|
-
|
|
8926
|
+
this.backendRegistry.beginOwner(child);
|
|
8927
|
+
child.on("message", async (message2) => {
|
|
8928
|
+
if (this.child !== child || this.starts !== generation || !this.backendRegistry.isOwner(child) || !["starting", "running"].includes(this.phase)) return;
|
|
8805
8929
|
if (message2?.op === "diagnostic") {
|
|
8806
8930
|
try {
|
|
8807
8931
|
this.onDiagnostic(message2.event);
|
|
@@ -8812,21 +8936,22 @@ var AgentController = class {
|
|
|
8812
8936
|
if (message2?.op === "backend.open") {
|
|
8813
8937
|
let ok = false;
|
|
8814
8938
|
try {
|
|
8815
|
-
this.backendRegistry.add(message2.registrationId, message2.tuple);
|
|
8816
|
-
ok =
|
|
8939
|
+
await this.backendRegistry.add(message2.registrationId, message2.tuple, message2.provenance, child);
|
|
8940
|
+
ok = this.child === child && this.starts === generation && this.backendRegistry.isOwner(child) && ["starting", "running"].includes(this.phase);
|
|
8817
8941
|
} catch {
|
|
8818
8942
|
}
|
|
8819
8943
|
if (child.connected) child.send({ op: "backend.ack", registrationId: message2.registrationId, ok }, () => {
|
|
8820
8944
|
});
|
|
8821
|
-
} else if (message2?.op === "backend.close") this.backendRegistry.remove(message2.registrationId);
|
|
8945
|
+
} else if (message2?.op === "backend.close") this.backendRegistry.remove(message2.registrationId, child);
|
|
8822
8946
|
});
|
|
8823
8947
|
child.on("error", () => {
|
|
8948
|
+
if (this.child !== child || this.starts !== generation) return;
|
|
8824
8949
|
this.lifecycle.emit("COMPANION_START_FAILED", generation, 1);
|
|
8825
8950
|
this.lastError = "AGENT_SPAWN_FAILED";
|
|
8826
8951
|
this.phase = "failed";
|
|
8827
8952
|
});
|
|
8828
8953
|
child.once("exit", () => {
|
|
8829
|
-
this.backendRegistry.
|
|
8954
|
+
this.backendRegistry.endOwner(child);
|
|
8830
8955
|
if (this.child === child) {
|
|
8831
8956
|
this.child = null;
|
|
8832
8957
|
if (this.phase !== "stopping") {
|
|
@@ -8886,6 +9011,7 @@ var AgentController = class {
|
|
|
8886
9011
|
return this.ask(this.child, "pair/cancel", { sessionId }).then((s) => this.snapshot(s));
|
|
8887
9012
|
}
|
|
8888
9013
|
revoke(target) {
|
|
9014
|
+
this.backendRegistry.denyPeer(target?.remotePeer, target?.generation);
|
|
8889
9015
|
if (this.child && this.phase === "running") return this.ask(this.child, "revoke", { target }).then((s) => this.snapshot(s));
|
|
8890
9016
|
return this.serialize(async () => {
|
|
8891
9017
|
if (this.child) return this.snapshot(await this.ask(this.child, "revoke", { target }));
|
|
@@ -8911,6 +9037,7 @@ var AgentController = class {
|
|
|
8911
9037
|
});
|
|
8912
9038
|
}
|
|
8913
9039
|
async stopChild(child, reason = "USER_STOP") {
|
|
9040
|
+
this.backendRegistry.endOwner(child);
|
|
8914
9041
|
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
8915
9042
|
this.lifecycle.stop(child, reason);
|
|
8916
9043
|
const exited = new Promise((resolve) => child.once("exit", resolve));
|
|
@@ -8935,6 +9062,7 @@ var AgentController = class {
|
|
|
8935
9062
|
if (child.exitCode === null && child.signalCode === null) throw Error("OWNED_CHILD_NOT_STOPPED");
|
|
8936
9063
|
}
|
|
8937
9064
|
stop(reason = "USER_STOP") {
|
|
9065
|
+
if (this.child) this.backendRegistry.endOwner(this.child);
|
|
8938
9066
|
return this.serialize(async () => {
|
|
8939
9067
|
this.phase = "stopping";
|
|
8940
9068
|
if (this.child) await this.stopChild(this.child, reason);
|