dshost-plugin 0.1.4 → 0.1.8
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/lib/core.js +263 -66
- package/lib/index.js +33 -5
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -105,14 +105,87 @@ function collectDisks() {
|
|
|
105
105
|
return disks;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Locate the dsh runtime this agent actually serves and reports.
|
|
110
|
+
*
|
|
111
|
+
* dsh >= 0.1.2 installs are frequently built from a source checkout with
|
|
112
|
+
* /usr/bin/dsh symlinked into it (e.g. /root/dsh-src/apps/cli/lib/bin.js),
|
|
113
|
+
* which leaves the historical hardcoded npm-global package.json stale the
|
|
114
|
+
* moment the host upgrades — the dashboard then keeps showing the previous
|
|
115
|
+
* release's version forever. Resolution order:
|
|
116
|
+
* 1. DSH_VERSION env var (explicit pin / tests)
|
|
117
|
+
* 2. the CLI entry that started this process (the plugin runs inside
|
|
118
|
+
* `dsh web`, so process.argv[1] IS the runtime, symlink included)
|
|
119
|
+
* 3. the first `dsh` executable on PATH (standalone agent / manual runs)
|
|
120
|
+
* 4. legacy npm global install path (dsh <= 0.1.1 behavior)
|
|
121
|
+
* Only a package.json of the dsh package itself is accepted while walking
|
|
122
|
+
* up, so an unrelated ancestor package (this repo's own package.json among
|
|
123
|
+
* them) can never be mistaken for the runtime.
|
|
124
|
+
* @returns {{version?: string, root?: string}}
|
|
125
|
+
*/
|
|
126
|
+
function findDshRuntime() {
|
|
127
|
+
if (process.env.DSH_VERSION) return { version: process.env.DSH_VERSION };
|
|
128
|
+
const entryPoints = [];
|
|
129
|
+
const pushEntry = (p) => { try { entryPoints.push(fs.realpathSync(p)); } catch {} };
|
|
130
|
+
if (process.argv[1]) pushEntry(process.argv[1]);
|
|
131
|
+
try {
|
|
132
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
133
|
+
if (!dir) continue;
|
|
134
|
+
const bin = path.join(dir, 'dsh');
|
|
135
|
+
let st = null;
|
|
136
|
+
try { st = fs.statSync(bin); } catch { continue; }
|
|
137
|
+
if (st.isFile()) { pushEntry(bin); break; }
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
for (const entry of entryPoints) {
|
|
141
|
+
let dir = path.dirname(entry);
|
|
142
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
143
|
+
try {
|
|
144
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
145
|
+
const name = pkg.name || '';
|
|
146
|
+
if (pkg.version && (name === '@deepseek-ai/dsh' || name === 'dsh' || name.startsWith('@deepseek-ai/dsh'))) {
|
|
147
|
+
return { version: pkg.version, root: dir };
|
|
148
|
+
}
|
|
149
|
+
} catch {}
|
|
150
|
+
const parent = path.dirname(dir);
|
|
151
|
+
if (parent === dir) break;
|
|
152
|
+
dir = parent;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const legacyRoot = '/usr/lib/node_modules/@deepseek-ai/dsh';
|
|
157
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(legacyRoot, 'package.json'), 'utf8'));
|
|
158
|
+
if (pkg.version) return { version: pkg.version, root: legacyRoot };
|
|
159
|
+
} catch {}
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Version of <name> as installed under <rootDir>/node_modules, or ''. */
|
|
164
|
+
function pluginVersionAt(rootDir, name) {
|
|
165
|
+
try {
|
|
166
|
+
return JSON.parse(fs.readFileSync(path.join(rootDir, 'node_modules', name, 'package.json'), 'utf8')).version || '';
|
|
167
|
+
} catch { return ''; }
|
|
168
|
+
}
|
|
169
|
+
|
|
108
170
|
/** Automatically detect local DSH runtime metadata (version + installed profile plugins with exact versions) */
|
|
109
171
|
function detectDshMetadata() {
|
|
110
|
-
|
|
172
|
+
const runtime = findDshRuntime();
|
|
173
|
+
const dshVersion = runtime.version || '0.1.0-rc.7';
|
|
174
|
+
// Where bundled dsh plugins may live: the runtime package itself plus the
|
|
175
|
+
// workspace roots above it (source-checkout / pnpm layouts hoist deps to
|
|
176
|
+
// the repo root), then the legacy npm global install.
|
|
177
|
+
const dshModuleRoots = [];
|
|
178
|
+
if (runtime.root) {
|
|
179
|
+
let dir = runtime.root;
|
|
180
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
181
|
+
dshModuleRoots.push(dir);
|
|
182
|
+
const parent = path.dirname(dir);
|
|
183
|
+
if (parent === dir) break;
|
|
184
|
+
dir = parent;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
dshModuleRoots.push('/usr/lib/node_modules/@deepseek-ai/dsh');
|
|
111
188
|
let plugins = [];
|
|
112
|
-
try {
|
|
113
|
-
const pkg = JSON.parse(fs.readFileSync('/usr/lib/node_modules/@deepseek-ai/dsh/package.json', 'utf8'));
|
|
114
|
-
if (pkg.version) dshVersion = pkg.version;
|
|
115
|
-
} catch {}
|
|
116
189
|
try {
|
|
117
190
|
const profileDir = os.homedir() + '/.dsh/profiles/web';
|
|
118
191
|
const profilePkgPath = profileDir + '/package.json';
|
|
@@ -129,12 +202,12 @@ function detectDshMetadata() {
|
|
|
129
202
|
const p1 = profileDir + '/node_modules/' + name + '/package.json';
|
|
130
203
|
if (fs.existsSync(p1)) ver = JSON.parse(fs.readFileSync(p1, 'utf8')).version || '';
|
|
131
204
|
} catch {}
|
|
132
|
-
// 2. Check dsh
|
|
205
|
+
// 2. Check dsh runtime node_modules (npm global / source checkout)
|
|
133
206
|
if (!ver) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
}
|
|
207
|
+
for (const root of dshModuleRoots) {
|
|
208
|
+
ver = pluginVersionAt(root, name);
|
|
209
|
+
if (ver) break;
|
|
210
|
+
}
|
|
138
211
|
}
|
|
139
212
|
// 3. Check global node_modules
|
|
140
213
|
if (!ver) {
|
|
@@ -193,6 +266,7 @@ export function collectSystemInfo(defaultVersion) {
|
|
|
193
266
|
* @param {string} [opts.version] agent version reported at registration
|
|
194
267
|
* @param {string} [opts.dshHost] local dsh host (default 127.0.0.1)
|
|
195
268
|
* @param {number} [opts.dshPort] local dsh port (default 3080)
|
|
269
|
+
* @param {string} [opts.webToken] local dsh web launch token (dsh >= 0.1.2 auth mode)
|
|
196
270
|
* @param {object} [opts.log] logger with log/warn/error (default console)
|
|
197
271
|
* @param {string} [opts.dshVersion] reported dsh version (default rc.6)
|
|
198
272
|
* @returns {{ stop: () => void }}
|
|
@@ -217,6 +291,60 @@ export function startAgent(opts) {
|
|
|
217
291
|
return { stop() {} };
|
|
218
292
|
}
|
|
219
293
|
|
|
294
|
+
// ── Local web auth (dsh >= 0.1.2 one-time launch token → signed cookie) ──
|
|
295
|
+
// dsh 0.1.2 gates its web UI behind a per-process launch token: GET
|
|
296
|
+
// /?token=X mints a signed authority-bound browser cookie, everything else
|
|
297
|
+
// 401s. When the plugin runs inside the dsh host process it hands us the
|
|
298
|
+
// live token via setWebToken() (see plugin index.js ctx.inject hook);
|
|
299
|
+
// standalone agents can use DSH_WEB_TOKEN. With a token on file we mint a
|
|
300
|
+
// cookie once and inject it into proxied HTTP/WS requests; a 401 drops the
|
|
301
|
+
// cookie and re-mints (covers dsh restarts rotating the token). On
|
|
302
|
+
// dsh <= 0.1.1 there is no web auth: with no token source nothing is
|
|
303
|
+
// injected and behavior is identical to plugin 0.1.5 (ApiProxy era).
|
|
304
|
+
let webToken = opts.webToken || process.env.DSH_WEB_TOKEN || null;
|
|
305
|
+
let authCookie = null;
|
|
306
|
+
let minting = null;
|
|
307
|
+
|
|
308
|
+
function mintAuthCookie() {
|
|
309
|
+
if (minting) return minting;
|
|
310
|
+
if (!webToken) return Promise.resolve(null);
|
|
311
|
+
minting = new Promise((resolve) => {
|
|
312
|
+
const req = http.get(
|
|
313
|
+
`http://${dshHost}:${dshPort}/?token=${encodeURIComponent(webToken)}`,
|
|
314
|
+
{ timeout: 5000 },
|
|
315
|
+
(res) => {
|
|
316
|
+
res.resume();
|
|
317
|
+
const set = res.headers['set-cookie'];
|
|
318
|
+
if (res.statusCode === 303 && Array.isArray(set) && set[0]) {
|
|
319
|
+
authCookie = set[0].split(';')[0];
|
|
320
|
+
log.log?.('[agent] local web auth cookie minted');
|
|
321
|
+
resolve(authCookie);
|
|
322
|
+
} else {
|
|
323
|
+
log.warn?.(`[agent] local web auth mint failed (HTTP ${res.statusCode})`);
|
|
324
|
+
resolve(null);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
req.on('timeout', () => req.destroy(new Error('mint timeout')));
|
|
329
|
+
req.on('error', (err) => {
|
|
330
|
+
log.warn?.(`[agent] local web auth mint error: ${err.message}`);
|
|
331
|
+
resolve(null);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
minting.then(() => { minting = null; }, () => { minting = null; });
|
|
335
|
+
return minting;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function setWebToken(token) {
|
|
339
|
+
if (!token || token === webToken) return;
|
|
340
|
+
webToken = token;
|
|
341
|
+
authCookie = null;
|
|
342
|
+
log.log?.('[agent] web launch token acquired (dsh 0.1.2 auth mode)');
|
|
343
|
+
mintAuthCookie();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (webToken) mintAuthCookie();
|
|
347
|
+
|
|
220
348
|
const streams = new Map();
|
|
221
349
|
let registered = false;
|
|
222
350
|
let reconnectTimer = null;
|
|
@@ -266,7 +394,9 @@ export function startAgent(opts) {
|
|
|
266
394
|
const stream = streams.get(msg.streamId);
|
|
267
395
|
if (stream && stream.dshReq) stream.dshReq.end();
|
|
268
396
|
} else if (msg.type === 'ws:open') {
|
|
269
|
-
|
|
397
|
+
// dsh >= 0.1.2: make sure the auth cookie exists before the upgrade.
|
|
398
|
+
if ((webToken || authCookie) && !authCookie) mintAuthCookie().then(() => handleWsOpen(ws, msg));
|
|
399
|
+
else handleWsOpen(ws, msg);
|
|
270
400
|
} else if (msg.type === 'ws:frame') {
|
|
271
401
|
const stream = streams.get(msg.streamId);
|
|
272
402
|
if (stream && stream.dshWs) {
|
|
@@ -408,76 +538,139 @@ export function startAgent(opts) {
|
|
|
408
538
|
let buf;
|
|
409
539
|
try { buf = fs.readFileSync(rawPath); } catch (e) { return fail(500, 'read failed: ' + e.message); }
|
|
410
540
|
const mime = ARTIFACT_MIME[ext] || 'text/plain; charset=utf-8';
|
|
541
|
+
// [EO实测] 响应头只保留 content-type + content-length:
|
|
542
|
+
// 携带 cache-control/content-disposition 时 EdgeOne 会静默吞掉该响应
|
|
411
543
|
artifactRespond(ws, sid, 200, {
|
|
412
544
|
'content-type': mime,
|
|
413
|
-
'content-
|
|
414
|
-
'cache-control': 'no-store',
|
|
545
|
+
'content-length': String(buf.length),
|
|
415
546
|
}, buf);
|
|
416
547
|
log.log?.(`[agent] artifact served: ${rawPath} (${buf.length}B)`);
|
|
417
548
|
return true;
|
|
418
549
|
}
|
|
419
550
|
|
|
420
|
-
function
|
|
421
|
-
|
|
551
|
+
function respondError(ws, streamId, status, message) {
|
|
552
|
+
send(ws, {
|
|
553
|
+
type: 'http:response',
|
|
554
|
+
streamId,
|
|
555
|
+
status,
|
|
556
|
+
headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' },
|
|
557
|
+
});
|
|
558
|
+
send(ws, { type: 'http:data', streamId, data: b64(Buffer.from(message)) });
|
|
559
|
+
send(ws, { type: 'http:end-response', streamId });
|
|
560
|
+
streams.delete(streamId);
|
|
561
|
+
}
|
|
422
562
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
host: `${dshHost}:${dshPort}`,
|
|
426
|
-
origin: `http://${dshHost}:${dshPort}`,
|
|
427
|
-
};
|
|
428
|
-
delete dshHeaders['content-length'];
|
|
429
|
-
|
|
430
|
-
const dshReq = http.request({
|
|
431
|
-
hostname: dshHost,
|
|
432
|
-
port: dshPort,
|
|
433
|
-
path,
|
|
434
|
-
method,
|
|
435
|
-
headers: dshHeaders,
|
|
436
|
-
}, (dshRes) => {
|
|
437
|
-
send(ws, {
|
|
438
|
-
type: 'http:response',
|
|
439
|
-
streamId,
|
|
440
|
-
status: dshRes.statusCode,
|
|
441
|
-
headers: dshRes.headers,
|
|
442
|
-
});
|
|
563
|
+
function handleHttpRequest(ws, msg, isRetry) {
|
|
564
|
+
const { streamId, method, path, headers } = msg;
|
|
443
565
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
566
|
+
// dsh >= 0.1.2 web auth: inject the minted browser cookie when we have
|
|
567
|
+
// one. dsh <= 0.1.1 has no web auth; with no token/cookie nothing is
|
|
568
|
+
// injected and requests go out exactly like plugin 0.1.5 sent them.
|
|
569
|
+
const issue = (cookie) => {
|
|
570
|
+
const dshHeaders = {
|
|
571
|
+
...headers,
|
|
572
|
+
host: `${dshHost}:${dshPort}`,
|
|
573
|
+
origin: `http://${dshHost}:${dshPort}`,
|
|
574
|
+
};
|
|
575
|
+
if (cookie) dshHeaders.cookie = cookie;
|
|
576
|
+
delete dshHeaders['content-length'];
|
|
577
|
+
|
|
578
|
+
const dshReq = http.request({
|
|
579
|
+
hostname: dshHost,
|
|
580
|
+
port: dshPort,
|
|
581
|
+
path,
|
|
582
|
+
method,
|
|
583
|
+
headers: dshHeaders,
|
|
584
|
+
}, (dshRes) => {
|
|
585
|
+
if (dshRes.statusCode === 401 && cookie) {
|
|
586
|
+
// Cached cookie rejected (launch token rotated, e.g. dsh restarted).
|
|
587
|
+
// Re-mint; body-less requests are retried once with the fresh
|
|
588
|
+
// cookie, everything else forwards the 401 and the browser app
|
|
589
|
+
// retries its next action with the fresh cookie.
|
|
590
|
+
dshRes.resume();
|
|
591
|
+
authCookie = null;
|
|
592
|
+
const remint = mintAuthCookie();
|
|
593
|
+
if ((method === 'GET' || method === 'HEAD') && !isRetry) {
|
|
594
|
+
log.log?.('[agent] local dsh 401, re-minting web auth cookie and retrying');
|
|
595
|
+
remint.then((c) => {
|
|
596
|
+
if (c) handleHttpRequest(ws, msg, true);
|
|
597
|
+
else respondError(ws, streamId, 502, 'dsh-remote: local web auth re-mint failed');
|
|
598
|
+
});
|
|
599
|
+
} else {
|
|
600
|
+
log.log?.('[agent] local dsh 401 on body request, re-minting web auth cookie for next requests');
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
461
603
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
604
|
+
const respHeaders = { ...dshRes.headers };
|
|
605
|
+
// The dsh index carries no cache-control; a heuristically cached stale
|
|
606
|
+
// HTML from a previous build references dead module revs and breaks
|
|
607
|
+
// the client module system. HTML must always revalidate.
|
|
608
|
+
if (String(respHeaders['content-type'] || '').includes('text/html')) {
|
|
609
|
+
respHeaders['cache-control'] = 'no-store';
|
|
610
|
+
}
|
|
611
|
+
send(ws, {
|
|
612
|
+
type: 'http:response',
|
|
613
|
+
streamId,
|
|
614
|
+
status: dshRes.statusCode,
|
|
615
|
+
headers: respHeaders,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Backpressure: pause reading from dsh when the relay link is slow.
|
|
619
|
+
// NOTE: the ws library does NOT emit 'drain' (verified), so resume is
|
|
620
|
+
// driven by polling bufferedAmount down below the high-water mark —
|
|
621
|
+
// a once('drain') here would never fire and would deadlock (truncated
|
|
622
|
+
// responses, hanging browsers).
|
|
623
|
+
dshRes.on('data', (chunk) => {
|
|
624
|
+
if (ws.bufferedAmount > WS_SEND_HIGH_WATER) {
|
|
625
|
+
dshRes.pause();
|
|
626
|
+
const resumeWhenDrained = () => {
|
|
627
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
628
|
+
if (ws.bufferedAmount < WS_SEND_HIGH_WATER / 2) {
|
|
629
|
+
dshRes.resume();
|
|
630
|
+
} else {
|
|
631
|
+
setTimeout(resumeWhenDrained, 50);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
setTimeout(resumeWhenDrained, 50);
|
|
635
|
+
}
|
|
636
|
+
send(ws, { type: 'http:data', streamId, data: b64(chunk) });
|
|
637
|
+
});
|
|
638
|
+
dshRes.on('end', () => {
|
|
639
|
+
send(ws, { type: 'http:end-response', streamId });
|
|
640
|
+
streams.delete(streamId);
|
|
641
|
+
});
|
|
642
|
+
dshRes.on('error', () => {
|
|
643
|
+
send(ws, { type: 'error', streamId, message: 'Local dsh response error' });
|
|
644
|
+
streams.delete(streamId);
|
|
645
|
+
});
|
|
467
646
|
});
|
|
468
|
-
|
|
469
|
-
|
|
647
|
+
|
|
648
|
+
dshReq.on('error', (err) => {
|
|
649
|
+
log.error?.(`[agent] HTTP Error: ${err.message}`);
|
|
650
|
+
send(ws, { type: 'error', streamId, message: err.message });
|
|
470
651
|
streams.delete(streamId);
|
|
471
652
|
});
|
|
472
|
-
});
|
|
473
653
|
|
|
474
|
-
|
|
475
|
-
log.error?.(`[agent] HTTP Error: ${err.message}`);
|
|
476
|
-
send(ws, { type: 'error', streamId, message: err.message });
|
|
477
|
-
streams.delete(streamId);
|
|
478
|
-
});
|
|
654
|
+
streams.set(streamId, { dshReq, dshRes: null, chunks: [] });
|
|
479
655
|
|
|
480
|
-
|
|
656
|
+
// Body-less requests are complete the moment they are created: end them
|
|
657
|
+
// now so a 401-retry (whose http:end frame was already consumed by the
|
|
658
|
+
// first attempt) still transmits. The later http:end frame, when it
|
|
659
|
+
// arrives, calls dshReq.end() again — a safe no-op. Body-bearing
|
|
660
|
+
// requests keep waiting for http:body/http:end frames as before.
|
|
661
|
+
if (method === 'GET' || method === 'HEAD') dshReq.end();
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
if ((webToken || authCookie) && !authCookie) {
|
|
665
|
+
// A token is known but the eager mint has not landed yet (startup
|
|
666
|
+
// race): mint first, then issue. Body frames arriving during the mint
|
|
667
|
+
// window are dropped (no streams entry yet) — only POSTs racing the
|
|
668
|
+
// very first mint are affected; the eager mint on token acquisition
|
|
669
|
+
// makes this path practically unreachable.
|
|
670
|
+
mintAuthCookie().then((c) => issue(c));
|
|
671
|
+
} else {
|
|
672
|
+
issue(authCookie || undefined);
|
|
673
|
+
}
|
|
481
674
|
}
|
|
482
675
|
|
|
483
676
|
function handleWsOpen(ws, msg) {
|
|
@@ -493,6 +686,8 @@ export function startAgent(opts) {
|
|
|
493
686
|
},
|
|
494
687
|
rejectUnauthorized: false,
|
|
495
688
|
};
|
|
689
|
+
// dsh >= 0.1.2 gates the WS upgrade behind the same browser cookie.
|
|
690
|
+
if (authCookie) options.headers.cookie = authCookie;
|
|
496
691
|
|
|
497
692
|
const dshWs = new WebSocket(dshWsUrl, undefined, options);
|
|
498
693
|
const pending = [];
|
|
@@ -551,6 +746,8 @@ export function startAgent(opts) {
|
|
|
551
746
|
connect();
|
|
552
747
|
|
|
553
748
|
return {
|
|
749
|
+
/** Provide the local dsh web launch token (dsh >= 0.1.2 auth mode). */
|
|
750
|
+
setWebToken,
|
|
554
751
|
stop() {
|
|
555
752
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
556
753
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
package/lib/index.js
CHANGED
|
@@ -16,26 +16,54 @@ export function apply(ctx, config) {
|
|
|
16
16
|
const relayUrl = config.relayUrl || process.env.DSH_RELAY_URL || 'wss://relay.example.com/agent';
|
|
17
17
|
const autoConnect = config.autoConnect !== false;
|
|
18
18
|
// Local dsh instance this plugin proxies (config-overridable for tests/dev)
|
|
19
|
-
const dshHost = config.dshHost || process.env.DSH_HOST ||
|
|
20
|
-
const dshPort = parseInt(config.dshPort || process.env.DSH_PORT ||
|
|
19
|
+
const dshHost = config.dshHost || process.env.DSH_HOST || DSH_HOST;
|
|
20
|
+
const dshPort = parseInt(config.dshPort || process.env.DSH_PORT || String(DSH_PORT), 10);
|
|
21
21
|
|
|
22
22
|
console.log('[dsh-remote] Plugin loaded, token:', token ? '***' : 'none');
|
|
23
23
|
|
|
24
24
|
if (!token) {
|
|
25
25
|
console.warn('[dsh-remote] No token configured. Please set token in settings.');
|
|
26
|
-
return;
|
|
26
|
+
return { setWebToken() {}, stop() {} };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (!autoConnect) {
|
|
30
30
|
console.log('[dsh-remote] Auto-connect disabled.');
|
|
31
|
-
return;
|
|
31
|
+
return { setWebToken() {}, stop() {} };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Start the agent connection (wss enforcement lives in core.js)
|
|
35
|
-
startAgent({
|
|
35
|
+
const agent = startAgent({
|
|
36
36
|
token,
|
|
37
37
|
relayUrl,
|
|
38
38
|
dshHost,
|
|
39
39
|
dshPort,
|
|
40
40
|
});
|
|
41
|
+
|
|
42
|
+
// dsh >= 0.1.2 web auth: the web UI is gated behind a per-process one-time
|
|
43
|
+
// launch token (the @Remote gateway era replaced the plain ApiProxy surface
|
|
44
|
+
// and added BrowserAuth on the shared /api FetchHandler). Our agent runs
|
|
45
|
+
// inside the host process, so it asks the connection service for the
|
|
46
|
+
// authenticated URL and mints a browser cookie for all proxied traffic.
|
|
47
|
+
// dsh <= 0.1.1 has no 'connection' service: the guarded inject below never
|
|
48
|
+
// fires and the plugin keeps its 0.1.5 behavior — one build, both hosts.
|
|
49
|
+
try {
|
|
50
|
+
if (ctx && typeof ctx.inject === 'function') {
|
|
51
|
+
ctx.inject(['connection'], (connectionCtx) => {
|
|
52
|
+
try {
|
|
53
|
+
const conn = connectionCtx && connectionCtx.connection;
|
|
54
|
+
if (!conn || typeof conn.authenticatedUrl !== 'function') return;
|
|
55
|
+
const url = conn.authenticatedUrl(`http://${dshHost}:${dshPort}/`);
|
|
56
|
+
const webToken = new URL(url).searchParams.get('token');
|
|
57
|
+
if (webToken) agent.setWebToken(webToken);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.warn('[dsh-remote] web launch token acquisition failed:', err && err.message);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Non-cordis ctx (smoke tests) or an older host without the service
|
|
65
|
+
// registry shape: legacy mode, no web auth injection.
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return agent;
|
|
41
69
|
}
|
package/package.json
CHANGED