@linxin666/dsh-remote-web-ui 0.4.3 → 0.4.4
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 +4 -4
- package/README.zh.md +4 -4
- package/lib/client.js +125 -6
- package/lib/client.js.map +1 -1
- package/lib/index.js +73 -5
- package/lib/types/client/mobile-adapt.d.ts.map +1 -1
- package/lib/types/client/mobile-adapt.js +142 -8
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +46 -12
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +43 -0
- package/lib/types/remote-api.d.ts +13 -1
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +14 -1
- package/package.json +19 -19
- package/src/client/mobile-adapt.ts +144 -5
- package/src/index.ts +46 -12
- package/src/pairing.ts +44 -1
- package/src/remote-api.ts +14 -1
package/lib/index.js
CHANGED
|
@@ -479,7 +479,7 @@ var PairingService = class {
|
|
|
479
479
|
};
|
|
480
480
|
/** Structural equality over the snapshot's wire fields. */
|
|
481
481
|
function snapshotsEqual(a, b) {
|
|
482
|
-
return a.phase === b.phase && a.lanAvailable === b.lanAvailable && sameStrings(a.lanAddresses, b.lanAddresses) && a.publicUrl === b.publicUrl && tunnelEqual(a.tunnel, b.tunnel) && relayEqual(a.relay, b.relay) && a.tokenId === b.tokenId && a.tokenExpiresAt === b.tokenExpiresAt && a.deviceCount === b.deviceCount && a.onlineCount === b.onlineCount && devicesEqual(a.devices, b.devices);
|
|
482
|
+
return a.phase === b.phase && a.lanAvailable === b.lanAvailable && sameStrings(a.lanAddresses, b.lanAddresses) && a.publicUrl === b.publicUrl && tunnelEqual(a.tunnel, b.tunnel) && relayEqual(a.relay, b.relay) && postureEqual(a.posture, b.posture) && a.tokenId === b.tokenId && a.tokenExpiresAt === b.tokenExpiresAt && a.deviceCount === b.deviceCount && a.onlineCount === b.onlineCount && devicesEqual(a.devices, b.devices);
|
|
483
483
|
}
|
|
484
484
|
/** Per-device roster equality (order is pairing time). */
|
|
485
485
|
function devicesEqual(a, b) {
|
|
@@ -496,6 +496,42 @@ function tunnelEqual(a, b) {
|
|
|
496
496
|
function relayEqual(a, b) {
|
|
497
497
|
return a === b || a !== void 0 && b !== void 0 && a.state === b.state && a.url === b.url && a.error === b.error;
|
|
498
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Posture equality. A probe round can change this frame while every other field
|
|
501
|
+
* stays put — the common case, since the probe re-runs on a settings change and
|
|
502
|
+
* on a tunnel reaching running, and a quick-tunnel restart churns the probed
|
|
503
|
+
* host set. Leaving the frame out of the comparison suppressed those emits, so
|
|
504
|
+
* the loopback SSE stream and the desktop panel could sit on a stale "exposed"
|
|
505
|
+
* (or stale "clean") verdict for as long as the phase and roster stayed put.
|
|
506
|
+
*/
|
|
507
|
+
function postureEqual(a, b) {
|
|
508
|
+
if (a === b) return true;
|
|
509
|
+
if (!isPostureFrame(a) || !isPostureFrame(b)) return false;
|
|
510
|
+
return a.checkedAt === b.checkedAt && hostsEqual(a.hosts, b.hosts);
|
|
511
|
+
}
|
|
512
|
+
/** Whether a value has the shape a posture frame is compared on. */
|
|
513
|
+
function isPostureFrame(value) {
|
|
514
|
+
return typeof value === "object" && value !== null;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Per-host posture equality (a round's verdicts, in probe order). Tolerates a
|
|
518
|
+
* frame that is not the expected shape: this runs inside the emit path, outside
|
|
519
|
+
* the listener error containment, so a malformed snapshot handed in by another
|
|
520
|
+
* host-fiber caller must degrade to "changed" instead of throwing out of the
|
|
521
|
+
* setter that produced it.
|
|
522
|
+
*/
|
|
523
|
+
function hostsEqual(a, b) {
|
|
524
|
+
if (a === b) return true;
|
|
525
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
526
|
+
return a.length === b.length && a.every((host, index) => {
|
|
527
|
+
const other = b[index];
|
|
528
|
+
return isPostureHost(host) && isPostureHost(other) && host.host === other.host && host.exposed === other.exposed;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
/** Whether a value has the shape one probed host is compared on. */
|
|
532
|
+
function isPostureHost(value) {
|
|
533
|
+
return typeof value === "object" && value !== null;
|
|
534
|
+
}
|
|
499
535
|
/** Element-wise string list equality (interface order is meaningful). */
|
|
500
536
|
function sameStrings(a, b) {
|
|
501
537
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
@@ -2184,7 +2220,19 @@ function makeRemoteApiRoutes(deps) {
|
|
|
2184
2220
|
}];
|
|
2185
2221
|
}
|
|
2186
2222
|
/**
|
|
2187
|
-
* Map one outer upgrade URL onto the loopback path
|
|
2223
|
+
* Map one outer upgrade URL onto the loopback path, dropping the cookieless
|
|
2224
|
+
* device credential on the way in.
|
|
2225
|
+
*
|
|
2226
|
+
* The outer handshake carries the device id as `?device=<id>` because WebSocket
|
|
2227
|
+
* handshakes cannot carry headers from the Web API. That id is a live session
|
|
2228
|
+
* credential — it opens the full host API through this same channel — and the
|
|
2229
|
+
* inner leg is a request the rest of the loopback origin sees (on a WebSocket
|
|
2230
|
+
* leg, a raw handshake request line written to a loopback socket), so forwarding
|
|
2231
|
+
* it handed a working credential to every other observer of the local port for
|
|
2232
|
+
* no benefit: the gate above has already authenticated the caller, and no
|
|
2233
|
+
* upstream route reads the parameter (the gateway mux matches on path alone).
|
|
2234
|
+
* The rest of the query rides through unchanged, so a caller's own parameters
|
|
2235
|
+
* still reach the upstream.
|
|
2188
2236
|
*/
|
|
2189
2237
|
function upgradeInnerPath(reqUrl, fallbackPath) {
|
|
2190
2238
|
if (reqUrl === void 0 || reqUrl === "") return fallbackPath;
|
|
@@ -2196,6 +2244,7 @@ function upgradeInnerPath(reqUrl, fallbackPath) {
|
|
|
2196
2244
|
}
|
|
2197
2245
|
const inner = innerPathOf(url.pathname);
|
|
2198
2246
|
if (inner === void 0) return fallbackPath;
|
|
2247
|
+
url.searchParams.delete(REMOTE_DEVICE_QUERY);
|
|
2199
2248
|
return `${inner}${url.search}`;
|
|
2200
2249
|
}
|
|
2201
2250
|
/**
|
|
@@ -4341,9 +4390,7 @@ function applyImpl(ctx, config) {
|
|
|
4341
4390
|
console.log(`remote-web-ui: the paired Web GUI is reachable on LAN at ${urls}`);
|
|
4342
4391
|
}
|
|
4343
4392
|
if (ctx.webServer.host === "0.0.0.0") console.warn("remote-web-ui: LAN-exposed bind — pairing gates the /remote channel; direct /api stays under the harness fence + browser auth (stop() does not revoke an already-redeemed browser credential)");
|
|
4344
|
-
const
|
|
4345
|
-
const value = resolve();
|
|
4346
|
-
service.config = pairingConfigOf(value);
|
|
4393
|
+
const applyLanBindWork = (value) => {
|
|
4347
4394
|
if (value.lanBind !== void 0) {
|
|
4348
4395
|
const startup = ctx.get("webStartup");
|
|
4349
4396
|
const desiredHost = desiredBindHost(value.lanBind === true, startup?.host);
|
|
@@ -4369,6 +4416,27 @@ function applyImpl(ctx, config) {
|
|
|
4369
4416
|
else console.error("remote-web-ui: the host firewall rule could not be updated (admin rights required on managed platforms)");
|
|
4370
4417
|
}
|
|
4371
4418
|
}
|
|
4419
|
+
};
|
|
4420
|
+
let lanBindWorkPending = false;
|
|
4421
|
+
const scheduleLanBindWork = () => {
|
|
4422
|
+
if (lanBindWorkPending) return;
|
|
4423
|
+
lanBindWorkPending = true;
|
|
4424
|
+
setImmediate(() => {
|
|
4425
|
+
lanBindWorkPending = false;
|
|
4426
|
+
try {
|
|
4427
|
+
applyLanBindWork(resolve());
|
|
4428
|
+
} catch (error) {
|
|
4429
|
+
console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4430
|
+
}
|
|
4431
|
+
});
|
|
4432
|
+
};
|
|
4433
|
+
ctx.effect(() => () => {
|
|
4434
|
+
lanBindWorkPending = false;
|
|
4435
|
+
}, "remote-web-ui: lan-bind work");
|
|
4436
|
+
const sync = () => {
|
|
4437
|
+
const value = resolve();
|
|
4438
|
+
service.config = pairingConfigOf(value);
|
|
4439
|
+
scheduleLanBindWork();
|
|
4372
4440
|
const plan = tunnelPlanOf(value, ctx.webServer.port);
|
|
4373
4441
|
tunnelMode = plan.mode;
|
|
4374
4442
|
publicBase.setMode(plan.mode);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AA8RD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAqhCvC"}
|
|
@@ -30,6 +30,16 @@ const ADAPT_CSS_ID = 'dsh-remote-web-ui/mobile-adapt.css';
|
|
|
30
30
|
const ACTIVE_CLASS = 'dsh-remote-portrait';
|
|
31
31
|
/** Body class while the collapsed rail is hidden behind the whale. */
|
|
32
32
|
const RAIL_HIDDEN_CLASS = 'dsh-remote-rail-hidden';
|
|
33
|
+
/**
|
|
34
|
+
* The official sidebar column root. Also the scope of the row sweep: every row
|
|
35
|
+
* the drag suppression covers is a descendant of it, so the document-wide scan
|
|
36
|
+
* it replaces only ever returned these.
|
|
37
|
+
*/
|
|
38
|
+
const SIDEBAR_SELECTOR = '[class*="_sidebarCol"]';
|
|
39
|
+
/** The official draggable session/project rows (see disableRowDrag). */
|
|
40
|
+
const ROW_SELECTOR = '[class*="_sessionRow"], [class*="_projectRow"]';
|
|
41
|
+
/** The injected stylesheet, addressed through the same cached lookup. */
|
|
42
|
+
const STYLE_SELECTOR = `style[data-plugin-css="${ADAPT_CSS_ID}"]`;
|
|
33
43
|
/** Whale button id. */
|
|
34
44
|
const WHALE_ID = 'dshRemoteWhale';
|
|
35
45
|
/** Compact picker: synthesized model button id. */
|
|
@@ -319,6 +329,8 @@ export function startMobileAdapt() {
|
|
|
319
329
|
let whaleObserver = null;
|
|
320
330
|
/** Header subtree observer: marks the geometry measurement dirty on re-render. */
|
|
321
331
|
let headerObserver = null;
|
|
332
|
+
/** Document child-list observer: invalidates the cached-absent selector set. */
|
|
333
|
+
let domObserver = null;
|
|
322
334
|
let observedHeader = null;
|
|
323
335
|
/** Whether the seated-actions geometry needs re-measuring (see alignActionsText). */
|
|
324
336
|
let headerGeometryDirty = true;
|
|
@@ -340,18 +352,29 @@ export function startMobileAdapt() {
|
|
|
340
352
|
* rail compaction) while the body class stays.
|
|
341
353
|
*/
|
|
342
354
|
function ensureAdaptStyle() {
|
|
343
|
-
if (nodeOf(
|
|
355
|
+
if (nodeOf(STYLE_SELECTOR) !== null)
|
|
344
356
|
return;
|
|
345
357
|
const tag = document.createElement('style');
|
|
346
358
|
tag.dataset.plugin = 'remote-web-ui';
|
|
347
359
|
tag.dataset.pluginCss = ADAPT_CSS_ID;
|
|
348
360
|
tag.textContent = ADAPT_CSS.join('');
|
|
349
361
|
document.head.appendChild(tag);
|
|
362
|
+
// Seed the lookup cache: the tag is created here, outside nodeOf, so the
|
|
363
|
+
// negative cache could otherwise report it missing for one more tick and
|
|
364
|
+
// stack a duplicate stylesheet.
|
|
365
|
+
nodeCache.set(STYLE_SELECTOR, tag);
|
|
366
|
+
nodeMissCache.delete(STYLE_SELECTOR);
|
|
350
367
|
}
|
|
351
368
|
function apply() {
|
|
352
369
|
if (active)
|
|
353
370
|
return;
|
|
354
371
|
active = true;
|
|
372
|
+
// First statement of the activation, before any lookup: nothing observed the
|
|
373
|
+
// document while the layer was reverted (the observer is created below), so
|
|
374
|
+
// no "absent" verdict recorded earlier may be served now. Clearing here
|
|
375
|
+
// rather than beside the observer also covers the lookups this path runs
|
|
376
|
+
// before that point.
|
|
377
|
+
forgetAbsentSelectors();
|
|
355
378
|
document.body.classList.add(ACTIVE_CLASS);
|
|
356
379
|
// A details panel opened before the viewport rotated into portrait (or
|
|
357
380
|
// restored across reloads) would sit behind the display:none above;
|
|
@@ -371,6 +394,7 @@ export function startMobileAdapt() {
|
|
|
371
394
|
meta.setAttribute('content', `${savedViewportContent}, viewport-fit=cover`);
|
|
372
395
|
}
|
|
373
396
|
ensureWhale();
|
|
397
|
+
ensureDomObserver();
|
|
374
398
|
syncWhale();
|
|
375
399
|
// React builds nodes with attributes before inserting them, so
|
|
376
400
|
// attribute observers miss the initial collapsed state — a light
|
|
@@ -404,6 +428,45 @@ export function startMobileAdapt() {
|
|
|
404
428
|
whaleObserver.disconnect();
|
|
405
429
|
whaleObserver = null;
|
|
406
430
|
}
|
|
431
|
+
if (domObserver !== null) {
|
|
432
|
+
domObserver.disconnect();
|
|
433
|
+
domObserver = null;
|
|
434
|
+
}
|
|
435
|
+
// Nothing observes the document from here on; a verdict recorded now could
|
|
436
|
+
// not be trusted after the next apply().
|
|
437
|
+
forgetAbsentSelectors();
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Observe the document for the two kinds of change that can make a cached-absent
|
|
441
|
+
* target discoverable again (see nodeOf): node insertion/removal, and the
|
|
442
|
+
* class / compat-stamp attributes the cached selectors match on.
|
|
443
|
+
*
|
|
444
|
+
* Body class writes are excluded by target: the layer toggles three body
|
|
445
|
+
* classes every tick, and counting those would invalidate the negative cache
|
|
446
|
+
* on every tick and restore the very scan this cache removes. They cannot
|
|
447
|
+
* create a target either — every cached selector matches an element other
|
|
448
|
+
* than <body>.
|
|
449
|
+
*
|
|
450
|
+
* The record queue is drained on a microtask, so this stays off the layout
|
|
451
|
+
* path; a chat turn's insertions are a handful per second, not per element.
|
|
452
|
+
*/
|
|
453
|
+
function ensureDomObserver() {
|
|
454
|
+
if (domObserver !== null || typeof MutationObserver === 'undefined' || !document.body)
|
|
455
|
+
return;
|
|
456
|
+
domObserver = new MutationObserver((records) => {
|
|
457
|
+
for (const record of records) {
|
|
458
|
+
if (record.type === 'childList' || record.target !== document.body) {
|
|
459
|
+
noteDomChange();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
domObserver.observe(document.body, {
|
|
465
|
+
childList: true,
|
|
466
|
+
subtree: true,
|
|
467
|
+
attributes: true,
|
|
468
|
+
attributeFilter: ['class', 'data-dsh-frame'],
|
|
469
|
+
});
|
|
407
470
|
}
|
|
408
471
|
/** Start/stop the 600ms sync tick; a no-op when already in the asked state. */
|
|
409
472
|
function setWhaleTimer(on) {
|
|
@@ -701,7 +764,7 @@ export function startMobileAdapt() {
|
|
|
701
764
|
function disableRowDrag() {
|
|
702
765
|
if (!active)
|
|
703
766
|
return;
|
|
704
|
-
const rows =
|
|
767
|
+
const rows = sidebarRows();
|
|
705
768
|
for (const row of rows) {
|
|
706
769
|
if (row.getAttribute('draggable') !== 'false') {
|
|
707
770
|
if (!dragOverridden.has(row))
|
|
@@ -732,17 +795,88 @@ export function startMobileAdapt() {
|
|
|
732
795
|
* node on a major re-render, which the isConnected guard detects.
|
|
733
796
|
*/
|
|
734
797
|
const nodeCache = new Map();
|
|
798
|
+
/**
|
|
799
|
+
* Selectors known to be absent as of {@link domGeneration}. A miss is the
|
|
800
|
+
* common case, not the exception: the layer holds no overlay of its own, and
|
|
801
|
+
* several official surfaces it looks for (tabs row, tools row, sidebar rows)
|
|
802
|
+
* legitimately do not exist at once. Re-walking a conversation-sized document
|
|
803
|
+
* for the same absent selector on every 600ms tick was waste that almost
|
|
804
|
+
* always resolved to null: five document-wide lookups per tick on the measured
|
|
805
|
+
* fixture, zero after this cache. That is a lookup count — the wall-clock
|
|
806
|
+
* saving is real while the mounted content is quiet and shrinks to the
|
|
807
|
+
* row-scope saving when a turn streams (see the Agent Note for the harness and
|
|
808
|
+
* the before/after numbers).
|
|
809
|
+
*
|
|
810
|
+
* A miss is trusted only while the observed content has not changed, so a
|
|
811
|
+
* surface that appears is still found on the very next tick — the same
|
|
812
|
+
* discovery latency the unconditional probe had. The cache is never aged by a
|
|
813
|
+
* clock: it is cleared by {@link noteDomChange} while the observer is live and
|
|
814
|
+
* by {@link forgetAbsentSelectors} across the windows where it is not (apply
|
|
815
|
+
* and revert). One blind spot is deliberate and load-bearing: the observer
|
|
816
|
+
* watches <body>, so a head-resident target is not covered by that signal —
|
|
817
|
+
* the one such target is seeded into the cache where it is created (see
|
|
818
|
+
* ensureAdaptStyle).
|
|
819
|
+
*/
|
|
820
|
+
const nodeMissCache = new Set();
|
|
821
|
+
/**
|
|
822
|
+
* Bumped whenever a cached target may have appeared or disappeared. A surface
|
|
823
|
+
* qualifies on a class token or on the aggregate compat stamp
|
|
824
|
+
* (`data-dsh-frame`), so both insertions/removals AND those two attributes
|
|
825
|
+
* can change the answer. Nothing the layer writes per tick lands here: its own
|
|
826
|
+
* class writes are all on <body>, and its row/transform/label writes touch
|
|
827
|
+
* other attributes entirely (see ensureDomObserver).
|
|
828
|
+
*/
|
|
829
|
+
let domGeneration = 0;
|
|
830
|
+
/** The generation the negative cache was recorded against. */
|
|
831
|
+
let missGeneration = -1;
|
|
832
|
+
function noteDomChange() {
|
|
833
|
+
domGeneration += 1;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Drop every cached "absent" verdict. Called on apply and revert: while the
|
|
837
|
+
* observer is disconnected the layer is blind to insertions, so a verdict
|
|
838
|
+
* recorded before the gap could otherwise outlive the change that falsified
|
|
839
|
+
* it and be served without a probe. The positive cache needs no equivalent —
|
|
840
|
+
* its `isConnected` guard already re-resolves a replaced node.
|
|
841
|
+
*/
|
|
842
|
+
function forgetAbsentSelectors() {
|
|
843
|
+
nodeMissCache.clear();
|
|
844
|
+
domGeneration += 1;
|
|
845
|
+
}
|
|
735
846
|
function nodeOf(selector) {
|
|
736
847
|
const cached = nodeCache.get(selector);
|
|
737
|
-
if (cached !== undefined
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
848
|
+
if (cached !== undefined) {
|
|
849
|
+
// A node React replaced is no longer connected; drop it and fall through
|
|
850
|
+
// so the lookup below can find its replacement.
|
|
851
|
+
if (cached.isConnected)
|
|
852
|
+
return cached;
|
|
741
853
|
nodeCache.delete(selector);
|
|
742
|
-
|
|
743
|
-
|
|
854
|
+
}
|
|
855
|
+
if (missGeneration !== domGeneration) {
|
|
856
|
+
nodeMissCache.clear();
|
|
857
|
+
missGeneration = domGeneration;
|
|
858
|
+
}
|
|
859
|
+
if (nodeMissCache.has(selector))
|
|
860
|
+
return null;
|
|
861
|
+
const found = document.querySelector(selector);
|
|
862
|
+
if (found === null) {
|
|
863
|
+
nodeMissCache.add(selector);
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
nodeCache.set(selector, found);
|
|
744
867
|
return found;
|
|
745
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* The session/project rows the drag suppression covers, scoped to the cached
|
|
871
|
+
* sidebar root. Each row carries the sidebar column class as an ancestor, so
|
|
872
|
+
* the document-wide scan this replaces only ever returned rows from here.
|
|
873
|
+
*/
|
|
874
|
+
function sidebarRows() {
|
|
875
|
+
const sidebar = nodeOf(SIDEBAR_SELECTOR);
|
|
876
|
+
if (sidebar === null)
|
|
877
|
+
return [];
|
|
878
|
+
return Array.from(sidebar.querySelectorAll(ROW_SELECTOR));
|
|
879
|
+
}
|
|
746
880
|
/** The official application frame, through the same cached lookup. */
|
|
747
881
|
function frameEl() {
|
|
748
882
|
const frame = nodeOf(APP_FRAME_SELECTOR);
|
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;AAIH,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,0BAA0B,CAAA;AAOxC,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAsBzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;;;WAOG;QACH,wBAAwB,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,CAAA;QACrE;;;;;;;;;;;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;;;;;;;GAOG;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;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAgBjB,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC,GAAG;IACvI,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;GAGG;AACH,UAAU,SAAS,CAAC,CAAC;IACnB,uEAAuE;IACvE,GAAG,IAAI,CAAC,CAAA;CACT;AAED,+FAA+F;AAC/F,KAAK,WAAW,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAEtC,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAChC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACpC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAChC,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAChC,oBAAoB,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC3C,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAA;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC5B,OAAO,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;CAC/B;AAyBD;;;;GAIG;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;AAmCD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/types/index.js
CHANGED
|
@@ -536,18 +536,22 @@ function applyImpl(ctx, config) {
|
|
|
536
536
|
if (ctx.webServer.host === '0.0.0.0') {
|
|
537
537
|
console.warn('remote-web-ui: LAN-exposed bind — pairing gates the /remote channel; direct /api stays under the harness fence + browser auth (stop() does not revoke an already-redeemed browser credential)');
|
|
538
538
|
}
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
539
|
+
// LAN bind toggle: only write the managed patch block once the user has
|
|
540
|
+
// flipped it (undefined = untouched, never write). Desired host/port come
|
|
541
|
+
// from the CLI flags when given (flags win), else from the toggle and
|
|
542
|
+
// the currently bound port. The block takes effect on the next start, so
|
|
543
|
+
// the re-assert at every boot keeps it in sync with both the toggle and
|
|
544
|
+
// the flags.
|
|
545
|
+
//
|
|
546
|
+
// This work must leave the caller's async context first: a settings save
|
|
547
|
+
// runs inside the Host's hmr.runExclusive, and cordis.patch.yml is exactly
|
|
548
|
+
// the file the HMR config watcher refreshes from. A write issued on that
|
|
549
|
+
// same context makes the watcher's refresh re-enter runExclusive, which
|
|
550
|
+
// rejects with "HMR transactions cannot be nested" and fails the user's
|
|
551
|
+
// save (issue #1751). setImmediate starts a fresh AsyncLocalStorage store,
|
|
552
|
+
// so the assertion runs outside the transaction; it is idempotent, so the
|
|
553
|
+
// coalesced follow-up sync() re-reading the same block writes nothing.
|
|
554
|
+
const applyLanBindWork = (value) => {
|
|
551
555
|
if (value.lanBind !== undefined) {
|
|
552
556
|
const startup = ctx.get('webStartup');
|
|
553
557
|
const desiredHost = desiredBindHost(value.lanBind === true, startup?.host);
|
|
@@ -589,6 +593,36 @@ function applyImpl(ctx, config) {
|
|
|
589
593
|
}
|
|
590
594
|
}
|
|
591
595
|
}
|
|
596
|
+
};
|
|
597
|
+
// The LAN bind assertion never blocks the caller: one pending work item is
|
|
598
|
+
// enough, and it always re-reads the config through `resolve()` at run time
|
|
599
|
+
// so a toggle flipped twice before the tick lands on the last value.
|
|
600
|
+
let lanBindWorkPending = false;
|
|
601
|
+
const scheduleLanBindWork = () => {
|
|
602
|
+
if (lanBindWorkPending)
|
|
603
|
+
return;
|
|
604
|
+
lanBindWorkPending = true;
|
|
605
|
+
setImmediate(() => {
|
|
606
|
+
lanBindWorkPending = false;
|
|
607
|
+
try {
|
|
608
|
+
applyLanBindWork(resolve());
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
// A deferred assertion must never surface as an unhandled rejection:
|
|
612
|
+
// the settings save that triggered it already answered, and the card
|
|
613
|
+
// reads the live block state back on its own poll.
|
|
614
|
+
console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
};
|
|
618
|
+
ctx.effect(() => () => { lanBindWorkPending = false; }, 'remote-web-ui: lan-bind work');
|
|
619
|
+
// Apply the effective configuration to the running surfaces once per
|
|
620
|
+
// activation: the service tunables, the LAN-bind block/firewall, the tunnel
|
|
621
|
+
// plan, the pairing routes, the presence sweep, and the posture probe.
|
|
622
|
+
const sync = () => {
|
|
623
|
+
const value = resolve();
|
|
624
|
+
service.config = pairingConfigOf(value);
|
|
625
|
+
scheduleLanBindWork();
|
|
592
626
|
// The plugin-managed tunnels own the public base while one runs: the URL
|
|
593
627
|
// lands in the service through the tunnel's phase listener (the minted
|
|
594
628
|
// quick URL, or the named tunnel's fixed public hostname). The manual
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
|
package/lib/types/pairing.js
CHANGED
|
@@ -509,6 +509,7 @@ function snapshotsEqual(a, b) {
|
|
|
509
509
|
&& a.publicUrl === b.publicUrl
|
|
510
510
|
&& tunnelEqual(a.tunnel, b.tunnel)
|
|
511
511
|
&& relayEqual(a.relay, b.relay)
|
|
512
|
+
&& postureEqual(a.posture, b.posture)
|
|
512
513
|
&& a.tokenId === b.tokenId
|
|
513
514
|
&& a.tokenExpiresAt === b.tokenExpiresAt
|
|
514
515
|
&& a.deviceCount === b.deviceCount
|
|
@@ -537,6 +538,48 @@ function relayEqual(a, b) {
|
|
|
537
538
|
return a === b || (a !== undefined && b !== undefined
|
|
538
539
|
&& a.state === b.state && a.url === b.url && a.error === b.error);
|
|
539
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Posture equality. A probe round can change this frame while every other field
|
|
543
|
+
* stays put — the common case, since the probe re-runs on a settings change and
|
|
544
|
+
* on a tunnel reaching running, and a quick-tunnel restart churns the probed
|
|
545
|
+
* host set. Leaving the frame out of the comparison suppressed those emits, so
|
|
546
|
+
* the loopback SSE stream and the desktop panel could sit on a stale "exposed"
|
|
547
|
+
* (or stale "clean") verdict for as long as the phase and roster stayed put.
|
|
548
|
+
*/
|
|
549
|
+
function postureEqual(a, b) {
|
|
550
|
+
if (a === b)
|
|
551
|
+
return true;
|
|
552
|
+
// A frame that is not an object at all (a hand-built context can pass
|
|
553
|
+
// anything) counts as changed instead of throwing out of the setter.
|
|
554
|
+
if (!isPostureFrame(a) || !isPostureFrame(b))
|
|
555
|
+
return false;
|
|
556
|
+
return a.checkedAt === b.checkedAt && hostsEqual(a.hosts, b.hosts);
|
|
557
|
+
}
|
|
558
|
+
/** Whether a value has the shape a posture frame is compared on. */
|
|
559
|
+
function isPostureFrame(value) {
|
|
560
|
+
return typeof value === 'object' && value !== null;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Per-host posture equality (a round's verdicts, in probe order). Tolerates a
|
|
564
|
+
* frame that is not the expected shape: this runs inside the emit path, outside
|
|
565
|
+
* the listener error containment, so a malformed snapshot handed in by another
|
|
566
|
+
* host-fiber caller must degrade to "changed" instead of throwing out of the
|
|
567
|
+
* setter that produced it.
|
|
568
|
+
*/
|
|
569
|
+
function hostsEqual(a, b) {
|
|
570
|
+
if (a === b)
|
|
571
|
+
return true;
|
|
572
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
573
|
+
return false;
|
|
574
|
+
return a.length === b.length && a.every((host, index) => {
|
|
575
|
+
const other = b[index];
|
|
576
|
+
return isPostureHost(host) && isPostureHost(other) && host.host === other.host && host.exposed === other.exposed;
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
/** Whether a value has the shape one probed host is compared on. */
|
|
580
|
+
function isPostureHost(value) {
|
|
581
|
+
return typeof value === 'object' && value !== null;
|
|
582
|
+
}
|
|
540
583
|
/** Element-wise string list equality (interface order is meaningful). */
|
|
541
584
|
function sameStrings(a, b) {
|
|
542
585
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
@@ -70,7 +70,19 @@ export declare function pairedDeviceIdOf(req: IncomingMessage, service: PairingS
|
|
|
70
70
|
*/
|
|
71
71
|
export declare function makeRemoteApiRoutes(deps: RemoteApiDeps): WebRoute[];
|
|
72
72
|
/**
|
|
73
|
-
* Map one outer upgrade URL onto the loopback path
|
|
73
|
+
* Map one outer upgrade URL onto the loopback path, dropping the cookieless
|
|
74
|
+
* device credential on the way in.
|
|
75
|
+
*
|
|
76
|
+
* The outer handshake carries the device id as `?device=<id>` because WebSocket
|
|
77
|
+
* handshakes cannot carry headers from the Web API. That id is a live session
|
|
78
|
+
* credential — it opens the full host API through this same channel — and the
|
|
79
|
+
* inner leg is a request the rest of the loopback origin sees (on a WebSocket
|
|
80
|
+
* leg, a raw handshake request line written to a loopback socket), so forwarding
|
|
81
|
+
* it handed a working credential to every other observer of the local port for
|
|
82
|
+
* no benefit: the gate above has already authenticated the caller, and no
|
|
83
|
+
* upstream route reads the parameter (the gateway mux matches on path alone).
|
|
84
|
+
* The rest of the query rides through unchanged, so a caller's own parameters
|
|
85
|
+
* still reach the upstream.
|
|
74
86
|
*/
|
|
75
87
|
export declare function upgradeInnerPath(reqUrl: string | undefined, fallbackPath: string): string;
|
|
76
88
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAUhD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CAwCnE;AAED
|
|
1
|
+
{"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAUhD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CAwCnE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAYzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,EAAE,CAyCjF"}
|
package/lib/types/remote-api.js
CHANGED
|
@@ -137,7 +137,19 @@ export function makeRemoteApiRoutes(deps) {
|
|
|
137
137
|
return [{ kind: 'prefix', path: REMOTE_PREFIX, handler }];
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
|
-
* Map one outer upgrade URL onto the loopback path
|
|
140
|
+
* Map one outer upgrade URL onto the loopback path, dropping the cookieless
|
|
141
|
+
* device credential on the way in.
|
|
142
|
+
*
|
|
143
|
+
* The outer handshake carries the device id as `?device=<id>` because WebSocket
|
|
144
|
+
* handshakes cannot carry headers from the Web API. That id is a live session
|
|
145
|
+
* credential — it opens the full host API through this same channel — and the
|
|
146
|
+
* inner leg is a request the rest of the loopback origin sees (on a WebSocket
|
|
147
|
+
* leg, a raw handshake request line written to a loopback socket), so forwarding
|
|
148
|
+
* it handed a working credential to every other observer of the local port for
|
|
149
|
+
* no benefit: the gate above has already authenticated the caller, and no
|
|
150
|
+
* upstream route reads the parameter (the gateway mux matches on path alone).
|
|
151
|
+
* The rest of the query rides through unchanged, so a caller's own parameters
|
|
152
|
+
* still reach the upstream.
|
|
141
153
|
*/
|
|
142
154
|
export function upgradeInnerPath(reqUrl, fallbackPath) {
|
|
143
155
|
if (reqUrl === undefined || reqUrl === '')
|
|
@@ -152,6 +164,7 @@ export function upgradeInnerPath(reqUrl, fallbackPath) {
|
|
|
152
164
|
const inner = innerPathOf(url.pathname);
|
|
153
165
|
if (inner === undefined)
|
|
154
166
|
return fallbackPath;
|
|
167
|
+
url.searchParams.delete(REMOTE_DEVICE_QUERY);
|
|
155
168
|
return `${inner}${url.search}`;
|
|
156
169
|
}
|
|
157
170
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-remote-web-ui",
|
|
3
3
|
"description": "Scan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and revocable device sessions, with a LAN bind toggle, optional Cloudflare tunnel, and one-click family self-update",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"license": "Apache-2.0",
|
|
39
39
|
"dsh": {
|
|
40
40
|
"engines": {
|
|
41
|
-
"dsh": ">=0.
|
|
41
|
+
"dsh": ">=0.2.0-rc.1"
|
|
42
42
|
},
|
|
43
43
|
"bundle": {
|
|
44
44
|
"patch": "./cordis.patch.yml"
|
|
@@ -61,28 +61,28 @@
|
|
|
61
61
|
"zod": "^4.4.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@deepseek-ai/dsh": ">=0.
|
|
64
|
+
"@deepseek-ai/dsh": ">=0.2.0-rc.1",
|
|
65
65
|
"react": "^18.2.0",
|
|
66
66
|
"react-dom": "^18.2.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
70
|
-
"@deepseek-ai/dsh-agent": "^0.
|
|
71
|
-
"@deepseek-ai/dsh-api-remotes": "^0.
|
|
72
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.
|
|
73
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.
|
|
74
|
-
"@deepseek-ai/dsh-client-connection": "^0.
|
|
75
|
-
"@deepseek-ai/dsh-client-locale": "^0.
|
|
76
|
-
"@deepseek-ai/dsh-client-store": "^0.
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.
|
|
79
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.
|
|
80
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.
|
|
81
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.
|
|
82
|
-
"@deepseek-ai/dsh-commands": "^0.
|
|
83
|
-
"@deepseek-ai/dsh-host-webserver": "^0.
|
|
84
|
-
"@deepseek-ai/dsh-invariants": "^0.
|
|
85
|
-
"@deepseek-ai/dsh-settings": "^0.
|
|
70
|
+
"@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-api-remotes": "^0.2.0-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.2.0-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-connection": "^0.2.0-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-store": "^0.2.0-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-invariants": "^0.2.0-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-settings": "^0.2.0-rc.1",
|
|
86
86
|
"@testing-library/dom": "^10.4.1",
|
|
87
87
|
"@testing-library/react": "^16.3.2",
|
|
88
88
|
"@types/node": "^22.20.0",
|