@linxin666/dsh-remote-web-ui 0.3.12 → 0.3.13
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 +24 -4
- package/README.zh.md +24 -4
- package/lib/client.js +77 -8
- package/lib/client.js.map +1 -1
- package/lib/index.js +409 -44
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +1 -0
- package/lib/types/client/RemotePanel.d.ts +3 -1
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +1 -1
- package/lib/types/client/RemoteSettingsCard.d.ts +16 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +6 -2
- package/lib/types/client/locales.d.ts +12 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +16 -4
- package/lib/types/client/pair-api.d.ts +10 -0
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +28 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +138 -23
- package/lib/types/pairing.d.ts +17 -0
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +13 -0
- package/lib/types/relay-registry.d.ts +97 -0
- package/lib/types/relay-registry.d.ts.map +1 -0
- package/lib/types/relay-registry.js +191 -0
- package/lib/types/tunnel-plan.d.ts +36 -0
- package/lib/types/tunnel-plan.d.ts.map +1 -0
- package/lib/types/tunnel-plan.js +43 -0
- package/lib/types/tunnel.d.ts +66 -13
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +93 -21
- package/package.json +1 -1
- package/src/client/RemoteEntry.tsx +2 -1
- package/src/client/RemotePanel.tsx +11 -0
- package/src/client/RemoteSettingsCard.tsx +43 -1
- package/src/client/locales.ts +16 -4
- package/src/client/pair-api.ts +11 -0
- package/src/client/settings-card.module.css +14 -8
- package/src/index.ts +153 -25
- package/src/pairing.ts +30 -0
- package/src/relay-registry.ts +232 -0
- package/src/tunnel-plan.ts +51 -0
- package/src/tunnel.ts +120 -24
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { z as z$1 } from "zod";
|
|
|
11
11
|
import http, { request } from "node:http";
|
|
12
12
|
import { connect } from "node:net";
|
|
13
13
|
import { spawn, spawnSync } from "node:child_process";
|
|
14
|
+
import { EventEmitter } from "node:events";
|
|
14
15
|
import { Tunnel, bin, install } from "cloudflared";
|
|
15
16
|
//#region src/pairing.ts
|
|
16
17
|
/**
|
|
@@ -86,6 +87,7 @@ var PairingService = class {
|
|
|
86
87
|
publicBase;
|
|
87
88
|
/** Auto-tunnel status, while the auto-tunnel feature is active. */
|
|
88
89
|
tunnelStatus;
|
|
90
|
+
relayStatus;
|
|
89
91
|
posture;
|
|
90
92
|
/** True when lastSeenAt changed since the last persist (flushed on sweep). */
|
|
91
93
|
dirty = false;
|
|
@@ -201,6 +203,11 @@ var PairingService = class {
|
|
|
201
203
|
this.tunnelStatus = status;
|
|
202
204
|
this.notify();
|
|
203
205
|
}
|
|
206
|
+
/** Set or clear the relay-registry status frame (undefined when not in play). */
|
|
207
|
+
setRelayStatus(status) {
|
|
208
|
+
this.relayStatus = status;
|
|
209
|
+
this.notify();
|
|
210
|
+
}
|
|
204
211
|
/** Set the latest /api posture probe result (see posture.ts). */
|
|
205
212
|
setPosture(snapshot) {
|
|
206
213
|
this.posture = snapshot;
|
|
@@ -349,6 +356,7 @@ var PairingService = class {
|
|
|
349
356
|
lanAddresses: [...this.lanBases.keys()],
|
|
350
357
|
...this.publicBase !== void 0 ? { publicUrl: this.publicBase } : {},
|
|
351
358
|
...this.tunnelStatus !== void 0 ? { tunnel: this.tunnelStatus } : {},
|
|
359
|
+
...this.relayStatus !== void 0 ? { relay: this.relayStatus } : {},
|
|
352
360
|
...this.posture !== void 0 ? { posture: this.posture } : {},
|
|
353
361
|
...token !== void 0 ? {
|
|
354
362
|
tokenId: token.record.id,
|
|
@@ -430,7 +438,7 @@ var PairingService = class {
|
|
|
430
438
|
};
|
|
431
439
|
/** Structural equality over the snapshot's wire fields. */
|
|
432
440
|
function snapshotsEqual(a, b) {
|
|
433
|
-
return a.phase === b.phase && a.lanAvailable === b.lanAvailable && sameStrings(a.lanAddresses, b.lanAddresses) && a.publicUrl === b.publicUrl && tunnelEqual(a.tunnel, b.tunnel) && a.tokenId === b.tokenId && a.tokenExpiresAt === b.tokenExpiresAt && a.deviceCount === b.deviceCount && a.onlineCount === b.onlineCount && devicesEqual(a.devices, b.devices);
|
|
441
|
+
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);
|
|
434
442
|
}
|
|
435
443
|
/** Per-device roster equality (order is pairing time). */
|
|
436
444
|
function devicesEqual(a, b) {
|
|
@@ -443,6 +451,10 @@ function devicesEqual(a, b) {
|
|
|
443
451
|
function tunnelEqual(a, b) {
|
|
444
452
|
return a === b || a !== void 0 && b !== void 0 && a.state === b.state && a.url === b.url && a.error === b.error;
|
|
445
453
|
}
|
|
454
|
+
/** Relay frame equality (same shape as the tunnel frame). */
|
|
455
|
+
function relayEqual(a, b) {
|
|
456
|
+
return a === b || a !== void 0 && b !== void 0 && a.state === b.state && a.url === b.url && a.error === b.error;
|
|
457
|
+
}
|
|
446
458
|
/** Element-wise string list equality (interface order is meaningful). */
|
|
447
459
|
function sameStrings(a, b) {
|
|
448
460
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
@@ -2308,6 +2320,224 @@ function writeLanBind(host, port, profile, home = dshHome()) {
|
|
|
2308
2320
|
renameSync(temp, file);
|
|
2309
2321
|
}
|
|
2310
2322
|
//#endregion
|
|
2323
|
+
//#region src/tunnel-plan.ts
|
|
2324
|
+
/**
|
|
2325
|
+
* Pure planner for the settings-sync tunnel branch: given the resolved
|
|
2326
|
+
* settings slice, decide which tunnel mode runs and with what target. Kept
|
|
2327
|
+
* free of cordis types so the precedence matrix is unit-testable without a
|
|
2328
|
+
* host context.
|
|
2329
|
+
*/
|
|
2330
|
+
/** Whether a configured public base is a parseable http(s) URL with a host. */
|
|
2331
|
+
function isHttpUrl(value) {
|
|
2332
|
+
try {
|
|
2333
|
+
const url = new URL(value);
|
|
2334
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname !== "";
|
|
2335
|
+
} catch {
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Decide the tunnel mode and target from one resolved settings value.
|
|
2341
|
+
* Precedence: the quick tunnel wins when `autoTunnel` is on (anything else
|
|
2342
|
+
* configured is reported as ignored); the named tunnel runs only with a
|
|
2343
|
+
* token AND a valid public hostname (the token does not carry the hostname,
|
|
2344
|
+
* so the QR base and the fence trust would have no source without it);
|
|
2345
|
+
* otherwise no tunnel and the manual public base applies.
|
|
2346
|
+
* @param value - the resolved settings slice that drives the tunnel.
|
|
2347
|
+
* @param port - the local webServer port the tunnel forwards to.
|
|
2348
|
+
*/
|
|
2349
|
+
function tunnelPlanOf(value, port) {
|
|
2350
|
+
if (value.autoTunnel === true) {
|
|
2351
|
+
const ignored = [];
|
|
2352
|
+
if (value.tunnelToken !== void 0 && value.tunnelToken !== "") ignored.push("tunnelToken");
|
|
2353
|
+
if (value.publicBaseUrl !== void 0 && value.publicBaseUrl !== "") ignored.push("publicBaseUrl");
|
|
2354
|
+
return {
|
|
2355
|
+
mode: "quick",
|
|
2356
|
+
targetUrl: `http://127.0.0.1:${String(port)}`,
|
|
2357
|
+
ignored
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
if (value.tunnelToken !== void 0 && value.tunnelToken !== "") {
|
|
2361
|
+
if (value.publicBaseUrl !== void 0 && isHttpUrl(value.publicBaseUrl)) return {
|
|
2362
|
+
mode: "named",
|
|
2363
|
+
token: value.tunnelToken,
|
|
2364
|
+
publicUrl: value.publicBaseUrl
|
|
2365
|
+
};
|
|
2366
|
+
return { mode: "off" };
|
|
2367
|
+
}
|
|
2368
|
+
return { mode: "off" };
|
|
2369
|
+
}
|
|
2370
|
+
const RELAY_UNREGISTER_URL = "https://dsh-market.com/api/relay/unregister";
|
|
2371
|
+
/**
|
|
2372
|
+
* The public origin template: one stable single-label subdomain per
|
|
2373
|
+
* registration id. Single label on purpose — Universal SSL covers exactly
|
|
2374
|
+
* one subdomain level, so a two-level hostname would get no certificate.
|
|
2375
|
+
*/
|
|
2376
|
+
const RELAY_BASE_SUFFIX = ".dsh-market.com";
|
|
2377
|
+
const RELAY_ID_RE = /^[a-z0-9]{16}$/;
|
|
2378
|
+
const RELAY_SECRET_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
2379
|
+
/** Registry storage layout: one file per profile under the relay directory. */
|
|
2380
|
+
function relayIdentityFile(profile, home = dshHome()) {
|
|
2381
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) throw new Error(`relay registry: profile ${JSON.stringify(profile)} is not a safe path segment`);
|
|
2382
|
+
return join(home, "remote-web-ui-registry", `${profile}.json`);
|
|
2383
|
+
}
|
|
2384
|
+
/** Generate one identity pair (id: 8-byte hex slug; secret: 32-byte base64url). */
|
|
2385
|
+
function generateRelayIdentity(random = randomBytes) {
|
|
2386
|
+
return {
|
|
2387
|
+
id: random(8).toString("hex"),
|
|
2388
|
+
secret: random(32).toString("base64url"),
|
|
2389
|
+
fresh: true
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Load (or lazily mint) the per-profile relay identity. The file is written
|
|
2394
|
+
* owner-only on first mint; a corrupt file is reminted (the phone then
|
|
2395
|
+
* re-pairs once — the same cost as losing the devices file).
|
|
2396
|
+
*/
|
|
2397
|
+
function loadRelayIdentity(profile, home = dshHome(), random = randomBytes) {
|
|
2398
|
+
const file = relayIdentityFile(profile, home);
|
|
2399
|
+
try {
|
|
2400
|
+
if (existsSync(file)) {
|
|
2401
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
2402
|
+
if (typeof parsed.id === "string" && RELAY_ID_RE.test(parsed.id) && typeof parsed.secret === "string" && RELAY_SECRET_RE.test(parsed.secret)) return {
|
|
2403
|
+
id: parsed.id,
|
|
2404
|
+
secret: parsed.secret
|
|
2405
|
+
};
|
|
2406
|
+
console.warn(`relay registry: ${file} is malformed — reminting the identity (paired devices must scan the QR once more)`);
|
|
2407
|
+
}
|
|
2408
|
+
} catch (error) {
|
|
2409
|
+
console.warn(`relay registry: could not read ${file} (${error instanceof Error ? error.message : String(error)}) — reminting`);
|
|
2410
|
+
}
|
|
2411
|
+
const identity = generateRelayIdentity(random);
|
|
2412
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
2413
|
+
writeFileSync(file, JSON.stringify({
|
|
2414
|
+
id: identity.id,
|
|
2415
|
+
secret: identity.secret
|
|
2416
|
+
}, null, 2) + "\n", { mode: 384 });
|
|
2417
|
+
return identity;
|
|
2418
|
+
}
|
|
2419
|
+
/** Build the registration request body for one identity + tunnel target. */
|
|
2420
|
+
function registrationBody(identity, target) {
|
|
2421
|
+
return JSON.stringify({
|
|
2422
|
+
id: identity.id,
|
|
2423
|
+
secret: identity.secret,
|
|
2424
|
+
new_secret: identity.fresh === true ? identity.secret : void 0,
|
|
2425
|
+
target
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
/** Best-effort removal of the mapping (used on disable; errors are swallowed). */
|
|
2429
|
+
async function unregisterRelay(identity, endpoint = RELAY_UNREGISTER_URL, fetchFn = fetch) {
|
|
2430
|
+
await fetchFn(endpoint, {
|
|
2431
|
+
method: "POST",
|
|
2432
|
+
headers: { "content-type": "application/json" },
|
|
2433
|
+
body: JSON.stringify({
|
|
2434
|
+
id: identity.id,
|
|
2435
|
+
secret: identity.secret
|
|
2436
|
+
})
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* The stable relay base for one identity — the origin the QR and the phone
|
|
2441
|
+
* bookmark use while (or after) the registration is accepted.
|
|
2442
|
+
*/
|
|
2443
|
+
function relayBaseOf(identity) {
|
|
2444
|
+
return `https://${identity.id}${RELAY_BASE_SUFFIX}`;
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Registration loop with capped exponential backoff. Re-registration happens
|
|
2448
|
+
* on every tunnel start (the quick URL can change after a crash-restart);
|
|
2449
|
+
* failures retry with backoff and surface the last error to the card.
|
|
2450
|
+
*/
|
|
2451
|
+
var RelayRegistrar = class {
|
|
2452
|
+
identity;
|
|
2453
|
+
setState;
|
|
2454
|
+
options;
|
|
2455
|
+
timer;
|
|
2456
|
+
attempts = 0;
|
|
2457
|
+
lastError;
|
|
2458
|
+
disposed = false;
|
|
2459
|
+
constructor(identity, setState, options = {}) {
|
|
2460
|
+
this.identity = identity;
|
|
2461
|
+
this.setState = setState;
|
|
2462
|
+
this.options = options;
|
|
2463
|
+
}
|
|
2464
|
+
/** The stable relay base for the QR while registered. */
|
|
2465
|
+
get baseUrl() {
|
|
2466
|
+
return relayBaseOf(this.identity);
|
|
2467
|
+
}
|
|
2468
|
+
/** The last registration failure detail (undefined while healthy). */
|
|
2469
|
+
get error() {
|
|
2470
|
+
return this.lastError;
|
|
2471
|
+
}
|
|
2472
|
+
/** Push one tunnel target; resolves the stable base once accepted. */
|
|
2473
|
+
async announce(target) {
|
|
2474
|
+
if (this.disposed) return void 0;
|
|
2475
|
+
if (this.timer !== void 0) {
|
|
2476
|
+
clearTimeout(this.timer);
|
|
2477
|
+
this.timer = void 0;
|
|
2478
|
+
}
|
|
2479
|
+
this.setState({ state: "registering" });
|
|
2480
|
+
try {
|
|
2481
|
+
const response = await (this.options.fetchFn ?? fetch)(this.options.registerUrl ?? "https://dsh-market.com/api/relay/register", {
|
|
2482
|
+
method: "PUT",
|
|
2483
|
+
headers: { "content-type": "application/json" },
|
|
2484
|
+
body: registrationBody(this.identity, target)
|
|
2485
|
+
});
|
|
2486
|
+
if (!response.ok) {
|
|
2487
|
+
const detail = await response.json().catch(() => void 0);
|
|
2488
|
+
const error = detail && typeof detail.error === "string" ? detail.error : `HTTP ${String(response.status)}`;
|
|
2489
|
+
throw new Error(error);
|
|
2490
|
+
}
|
|
2491
|
+
this.identity.fresh = false;
|
|
2492
|
+
this.attempts = 0;
|
|
2493
|
+
this.lastError = void 0;
|
|
2494
|
+
this.setState({
|
|
2495
|
+
state: "running",
|
|
2496
|
+
url: this.baseUrl
|
|
2497
|
+
});
|
|
2498
|
+
return this.baseUrl;
|
|
2499
|
+
} catch (error) {
|
|
2500
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
2501
|
+
if (this.lastError === "invalid-params" && this.identity.fresh !== true) this.identity.fresh = true;
|
|
2502
|
+
this.setState({
|
|
2503
|
+
state: "failed",
|
|
2504
|
+
error: this.lastError
|
|
2505
|
+
});
|
|
2506
|
+
this.scheduleRetry(target);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
/** Best-effort removal of the registry row (the relay toggle turned off). */
|
|
2511
|
+
async unregister() {
|
|
2512
|
+
await unregisterRelay(this.identity, this.options.unregisterUrl, this.options.fetchFn);
|
|
2513
|
+
}
|
|
2514
|
+
scheduleRetry(target) {
|
|
2515
|
+
if (this.disposed) return;
|
|
2516
|
+
const base = this.options.baseDelayMs ?? 5e3;
|
|
2517
|
+
const max = this.options.maxDelayMs ?? 6e4;
|
|
2518
|
+
const delay = Math.min(base * 2 ** this.attempts, max);
|
|
2519
|
+
this.attempts += 1;
|
|
2520
|
+
const timerFn = this.options.timerFn ?? ((fn, ms) => {
|
|
2521
|
+
const timer = setTimeout(fn, ms);
|
|
2522
|
+
timer.unref?.();
|
|
2523
|
+
return timer;
|
|
2524
|
+
});
|
|
2525
|
+
this.timer = timerFn(() => {
|
|
2526
|
+
this.timer = void 0;
|
|
2527
|
+
this.announce(target);
|
|
2528
|
+
}, delay);
|
|
2529
|
+
}
|
|
2530
|
+
/** Stop retrying and clear the card state (tunnel stopped or disabled). */
|
|
2531
|
+
dispose() {
|
|
2532
|
+
this.disposed = true;
|
|
2533
|
+
if (this.timer !== void 0) {
|
|
2534
|
+
clearTimeout(this.timer);
|
|
2535
|
+
this.timer = void 0;
|
|
2536
|
+
}
|
|
2537
|
+
this.setState({ state: "off" });
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
//#endregion
|
|
2311
2541
|
//#region src/lan-bind-plan.ts
|
|
2312
2542
|
/**
|
|
2313
2543
|
* The bind host the managed block should pin: an explicit CLI --host wins,
|
|
@@ -2406,11 +2636,14 @@ function createInnerAuth(launchUrl, fetchImpl = fetch) {
|
|
|
2406
2636
|
//#endregion
|
|
2407
2637
|
//#region src/tunnel.ts
|
|
2408
2638
|
/**
|
|
2409
|
-
* Auto-tunnel manager: spawns a Cloudflare
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2639
|
+
* Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
|
|
2640
|
+
* npm package — its postinstall downloads the platform binary, so no
|
|
2641
|
+
* user-side tooling is involved — surfaces the public URL, and restarts the
|
|
2642
|
+
* process after unexpected exits with exponential backoff. Two modes: the
|
|
2643
|
+
* accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
|
|
2644
|
+
* per start) and an account named tunnel (`cloudflared tunnel run --token`,
|
|
2645
|
+
* fixed public hostname — the mode that lets a paired phone keep its
|
|
2646
|
+
* bookmark and pairing cookie across restarts).
|
|
2414
2647
|
*
|
|
2415
2648
|
* The cloudflared package's Tunnel is a thin spawn wrapper; this manager
|
|
2416
2649
|
* owns the lifecycle policy (binary readiness, URL timeout, restart
|
|
@@ -2418,17 +2651,75 @@ function createInnerAuth(launchUrl, fetchImpl = fetch) {
|
|
|
2418
2651
|
* timers — are injectable so the whole lifecycle is unit-testable without
|
|
2419
2652
|
* a real binary or network.
|
|
2420
2653
|
*/
|
|
2654
|
+
/** Compare two targets for the start idempotence check. */
|
|
2655
|
+
function sameTarget(left, right) {
|
|
2656
|
+
if (left === void 0) return false;
|
|
2657
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Wrap a named-tunnel process so it fits the quick-tunnel handle shape: the
|
|
2661
|
+
* fixed public URL is reported through the same `url` event once the first
|
|
2662
|
+
* edge connection registers (cloudflared fires `connected` per connection,
|
|
2663
|
+
* so only the first is taken), and exit/error pass through. The manager's
|
|
2664
|
+
* URL timeout, crash-restart backoff, and stop semantics then stay fully
|
|
2665
|
+
* mode-agnostic.
|
|
2666
|
+
* @param inner - the running named-tunnel process.
|
|
2667
|
+
* @param publicUrl - the fixed public hostname ingress maps to this server.
|
|
2668
|
+
* @returns a handle emitting `url` once the tunnel is reachable.
|
|
2669
|
+
*/
|
|
2670
|
+
function namedTunnelHandle(inner, publicUrl) {
|
|
2671
|
+
const handle = new EventEmitter();
|
|
2672
|
+
let registered = false;
|
|
2673
|
+
const onConnected = () => {
|
|
2674
|
+
if (registered) return;
|
|
2675
|
+
registered = true;
|
|
2676
|
+
handle.emit("url", publicUrl);
|
|
2677
|
+
};
|
|
2678
|
+
const onExit = (code, signal) => {
|
|
2679
|
+
handle.emit("exit", code, signal);
|
|
2680
|
+
};
|
|
2681
|
+
const onError = (value) => {
|
|
2682
|
+
handle.emit("error", value);
|
|
2683
|
+
};
|
|
2684
|
+
inner.on("connected", onConnected);
|
|
2685
|
+
inner.on("exit", onExit);
|
|
2686
|
+
inner.on("error", onError);
|
|
2687
|
+
handle.stop = () => {
|
|
2688
|
+
inner.off("connected", onConnected);
|
|
2689
|
+
inner.off("exit", onExit);
|
|
2690
|
+
inner.off("error", onError);
|
|
2691
|
+
return inner.stop();
|
|
2692
|
+
};
|
|
2693
|
+
return handle;
|
|
2694
|
+
}
|
|
2421
2695
|
/** Default binary readiness: download the platform binary on first use. */
|
|
2422
2696
|
async function defaultEnsureBinary() {
|
|
2423
2697
|
if (existsSync(bin)) return;
|
|
2424
2698
|
await install(bin);
|
|
2425
2699
|
}
|
|
2426
|
-
/**
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2700
|
+
/** The flags every tunnel mode shares (see the quick factory comment). */
|
|
2701
|
+
const SHARED_TUNNEL_FLAGS = {
|
|
2702
|
+
"--no-autoupdate": true,
|
|
2703
|
+
"--protocol": "http2"
|
|
2704
|
+
};
|
|
2705
|
+
/**
|
|
2706
|
+
* Spawn flags for one quick target. `originHostHeader` stamps the Host the
|
|
2707
|
+
* local webserver sees: the plugin's phone-facing fence trusts the configured
|
|
2708
|
+
* public host (the stable relay origin, Host-bound) and the harness
|
|
2709
|
+
* browser-auth binds cookies to the Host authority, while the Workers relay
|
|
2710
|
+
* cannot control the origin-side Host at all (fetch forces it to the URL
|
|
2711
|
+
* authority) — so the connector must restamp it with the stable origin.
|
|
2712
|
+
*/
|
|
2713
|
+
function quickTunnelFlags(originHostHeader) {
|
|
2714
|
+
return originHostHeader === void 0 ? { ...SHARED_TUNNEL_FLAGS } : {
|
|
2715
|
+
...SHARED_TUNNEL_FLAGS,
|
|
2716
|
+
"--http-host-header": originHostHeader
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
/** Default factory: the cloudflared package's quick and named tunnels. */
|
|
2720
|
+
function defaultFactory(target) {
|
|
2721
|
+
if (target.kind === "quick") return Tunnel.quick(target.targetUrl, quickTunnelFlags(target.originHostHeader));
|
|
2722
|
+
return namedTunnelHandle(Tunnel.withToken(target.token, { ...SHARED_TUNNEL_FLAGS }), target.publicUrl);
|
|
2432
2723
|
}
|
|
2433
2724
|
/** Node timers. */
|
|
2434
2725
|
const nodeTimer = {
|
|
@@ -2449,7 +2740,7 @@ var TunnelManager = class {
|
|
|
2449
2740
|
phase = "stopped";
|
|
2450
2741
|
url;
|
|
2451
2742
|
error;
|
|
2452
|
-
|
|
2743
|
+
target;
|
|
2453
2744
|
handle;
|
|
2454
2745
|
urlTimer;
|
|
2455
2746
|
restartTimer;
|
|
@@ -2478,16 +2769,24 @@ var TunnelManager = class {
|
|
|
2478
2769
|
};
|
|
2479
2770
|
}
|
|
2480
2771
|
/**
|
|
2481
|
-
* Start (or keep) a
|
|
2482
|
-
*
|
|
2483
|
-
*
|
|
2484
|
-
*
|
|
2772
|
+
* Start (or keep) a tunnel toward `target`. Restarting with a different
|
|
2773
|
+
* target tears the old tunnel down first; restarting with the same target
|
|
2774
|
+
* while running is a no-op. A string is the quick mode's local target URL.
|
|
2775
|
+
*
|
|
2776
|
+
* In quick mode the URL surfaces when cloudflared mints the ephemeral
|
|
2777
|
+
* trycloudflare hostname; in named mode the fixed `publicUrl` surfaces
|
|
2778
|
+
* once the first edge connection registers (see {@link namedTunnelHandle}).
|
|
2779
|
+
* @param target - what to run (a string means quick toward that local URL).
|
|
2485
2780
|
*/
|
|
2486
|
-
start(
|
|
2487
|
-
|
|
2781
|
+
start(target) {
|
|
2782
|
+
const resolved = typeof target === "string" ? {
|
|
2783
|
+
kind: "quick",
|
|
2784
|
+
targetUrl: target
|
|
2785
|
+
} : target;
|
|
2786
|
+
if (sameTarget(this.target, resolved) && (this.phase === "starting" || this.phase === "running")) return;
|
|
2488
2787
|
this.teardown();
|
|
2489
2788
|
this.stopping = false;
|
|
2490
|
-
this.
|
|
2789
|
+
this.target = resolved;
|
|
2491
2790
|
this.attempts = 0;
|
|
2492
2791
|
this.generation += 1;
|
|
2493
2792
|
this.attempt();
|
|
@@ -2496,7 +2795,7 @@ var TunnelManager = class {
|
|
|
2496
2795
|
stop() {
|
|
2497
2796
|
this.teardown();
|
|
2498
2797
|
this.stopping = false;
|
|
2499
|
-
this.
|
|
2798
|
+
this.target = void 0;
|
|
2500
2799
|
this.setPhase("stopped");
|
|
2501
2800
|
}
|
|
2502
2801
|
/** Alias of {@link stop} for plugin-effect disposal. */
|
|
@@ -2518,15 +2817,15 @@ var TunnelManager = class {
|
|
|
2518
2817
|
};
|
|
2519
2818
|
}
|
|
2520
2819
|
attempt() {
|
|
2521
|
-
if (this.stopping || this.
|
|
2820
|
+
if (this.stopping || this.target === void 0) return;
|
|
2522
2821
|
const gen = this.generation;
|
|
2523
2822
|
this.setPhase("starting");
|
|
2524
2823
|
this.handle = void 0;
|
|
2525
2824
|
this.url = void 0;
|
|
2526
2825
|
this.error = void 0;
|
|
2527
2826
|
this.ensureBinary().then(() => {
|
|
2528
|
-
if (this.stopping || this.
|
|
2529
|
-
const handle = this.factory(this.
|
|
2827
|
+
if (this.stopping || this.target === void 0 || gen !== this.generation) return;
|
|
2828
|
+
const handle = this.factory(this.target);
|
|
2530
2829
|
this.handle = handle;
|
|
2531
2830
|
this.urlTimer = this.timer.setTimeout(() => {
|
|
2532
2831
|
this.fail("timed out waiting for the tunnel URL");
|
|
@@ -2544,7 +2843,7 @@ var TunnelManager = class {
|
|
|
2544
2843
|
this.error = value instanceof Error ? value.message : String(value);
|
|
2545
2844
|
});
|
|
2546
2845
|
}).catch((value) => {
|
|
2547
|
-
if (this.stopping || this.
|
|
2846
|
+
if (this.stopping || this.target === void 0 || gen !== this.generation) return;
|
|
2548
2847
|
const message = value instanceof Error ? value.message : String(value);
|
|
2549
2848
|
this.fail(`could not obtain the cloudflared binary: ${message}`);
|
|
2550
2849
|
});
|
|
@@ -3418,6 +3717,8 @@ const Config = z.object({
|
|
|
3418
3717
|
publicBaseUrl: z.string(),
|
|
3419
3718
|
devicesFile: z.string(),
|
|
3420
3719
|
autoTunnel: z.boolean().default(false),
|
|
3720
|
+
tunnelToken: z.string().role("secret"),
|
|
3721
|
+
relay: z.boolean().default(true),
|
|
3421
3722
|
lanBind: z.boolean(),
|
|
3422
3723
|
profile: z.string().pattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
|
|
3423
3724
|
enabled: z.boolean().default(true)
|
|
@@ -3455,6 +3756,8 @@ const DEFAULTS = {
|
|
|
3455
3756
|
publicBaseUrl: void 0,
|
|
3456
3757
|
devicesFile: defaultDevicesFile(),
|
|
3457
3758
|
autoTunnel: false,
|
|
3759
|
+
tunnelToken: void 0,
|
|
3760
|
+
relay: true,
|
|
3458
3761
|
lanBind: void 0,
|
|
3459
3762
|
profile: process.env.DSH_PROFILE ?? "web",
|
|
3460
3763
|
enabled: true
|
|
@@ -3476,6 +3779,8 @@ function applyImpl(ctx, config) {
|
|
|
3476
3779
|
publicBaseUrl: config?.publicBaseUrl,
|
|
3477
3780
|
devicesFile: config?.devicesFile ?? DEFAULTS.devicesFile,
|
|
3478
3781
|
autoTunnel: config?.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
3782
|
+
tunnelToken: config?.tunnelToken,
|
|
3783
|
+
relay: config?.relay ?? DEFAULTS.relay,
|
|
3479
3784
|
lanBind: config?.lanBind,
|
|
3480
3785
|
profile: config?.profile ?? process.env.DSH_PROFILE ?? DEFAULTS.profile,
|
|
3481
3786
|
enabled: config?.enabled ?? DEFAULTS.enabled
|
|
@@ -3493,6 +3798,8 @@ function applyImpl(ctx, config) {
|
|
|
3493
3798
|
publicBaseUrl: value.publicBaseUrl,
|
|
3494
3799
|
devicesFile: value.devicesFile ?? DEFAULTS.devicesFile,
|
|
3495
3800
|
autoTunnel: value.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
3801
|
+
tunnelToken: value.tunnelToken,
|
|
3802
|
+
relay: value.relay ?? DEFAULTS.relay,
|
|
3496
3803
|
lanBind: value.lanBind,
|
|
3497
3804
|
profile: value.profile ?? process.env.DSH_PROFILE ?? DEFAULTS.profile,
|
|
3498
3805
|
enabled: value.enabled ?? DEFAULTS.enabled
|
|
@@ -3500,21 +3807,64 @@ function applyImpl(ctx, config) {
|
|
|
3500
3807
|
};
|
|
3501
3808
|
const service = new PairingService(pairingConfigOf(resolved));
|
|
3502
3809
|
const tunnel = new TunnelManager();
|
|
3503
|
-
let
|
|
3810
|
+
let relayRegistrar;
|
|
3811
|
+
let relayUrl;
|
|
3812
|
+
let rawTunnelUrl;
|
|
3813
|
+
/** The tunnel target the registrar last announced (dedupes sync re-runs). */
|
|
3814
|
+
let relayAnnouncedFor;
|
|
3815
|
+
const setPublicBase = () => {
|
|
3816
|
+
service.setPublicBaseUrl(relayUrl ?? rawTunnelUrl);
|
|
3817
|
+
};
|
|
3818
|
+
const disposeRelayRegistrar = (unregister = false) => {
|
|
3819
|
+
const registrar = relayRegistrar;
|
|
3820
|
+
relayRegistrar = void 0;
|
|
3821
|
+
relayUrl = void 0;
|
|
3822
|
+
relayAnnouncedFor = void 0;
|
|
3823
|
+
if (registrar === void 0) return;
|
|
3824
|
+
if (unregister) registrar.unregister().catch(() => void 0);
|
|
3825
|
+
registrar.dispose();
|
|
3826
|
+
};
|
|
3827
|
+
const ensureRelayRegistrar = () => {
|
|
3828
|
+
if (resolve().relay === false) return void 0;
|
|
3829
|
+
if (relayRegistrar === void 0) try {
|
|
3830
|
+
relayRegistrar = new RelayRegistrar(loadRelayIdentity(resolve().profile), (state) => {
|
|
3831
|
+
service.setRelayStatus(state.state === "off" ? void 0 : state);
|
|
3832
|
+
if (state.state === "running") relayUrl = state.url;
|
|
3833
|
+
else if (state.state === "off") relayUrl = void 0;
|
|
3834
|
+
else if (state.state === "failed") console.warn(`remote-web-ui: relay registration failed (${state.error}) — the stable origin may serve its offline page until the retry lands`);
|
|
3835
|
+
setPublicBase();
|
|
3836
|
+
});
|
|
3837
|
+
} catch (error) {
|
|
3838
|
+
console.warn(`remote-web-ui: relay registry unavailable (${error instanceof Error ? error.message : String(error)}) — the quick URL is the QR base`);
|
|
3839
|
+
}
|
|
3840
|
+
return relayRegistrar;
|
|
3841
|
+
};
|
|
3842
|
+
const announceRelay = (registrar, target) => {
|
|
3843
|
+
if (relayAnnouncedFor === target) return;
|
|
3844
|
+
relayAnnouncedFor = target;
|
|
3845
|
+
registrar.announce(target);
|
|
3846
|
+
};
|
|
3847
|
+
let tunnelMode = resolved.autoTunnel ? "quick" : "off";
|
|
3504
3848
|
tunnel.onPhase((info) => {
|
|
3505
|
-
if (
|
|
3849
|
+
if (tunnelMode === "off") return;
|
|
3506
3850
|
if (info.phase === "running" && info.url !== void 0) {
|
|
3507
|
-
|
|
3851
|
+
rawTunnelUrl = info.url;
|
|
3852
|
+
setPublicBase();
|
|
3508
3853
|
service.setTunnelStatus({
|
|
3509
3854
|
state: "running",
|
|
3510
3855
|
url: info.url
|
|
3511
3856
|
});
|
|
3857
|
+
const registrar = tunnelMode === "quick" ? ensureRelayRegistrar() : void 0;
|
|
3858
|
+
if (registrar !== void 0) announceRelay(registrar, info.url);
|
|
3512
3859
|
runPostureProbe();
|
|
3513
3860
|
} else if (info.phase === "starting") {
|
|
3514
|
-
|
|
3861
|
+
rawTunnelUrl = void 0;
|
|
3862
|
+
relayUrl = void 0;
|
|
3863
|
+
setPublicBase();
|
|
3515
3864
|
service.setTunnelStatus({ state: "starting" });
|
|
3516
3865
|
} else if (info.phase === "failed") {
|
|
3517
|
-
|
|
3866
|
+
rawTunnelUrl = void 0;
|
|
3867
|
+
setPublicBase();
|
|
3518
3868
|
service.setTunnelStatus(info.error === void 0 ? { state: "failed" } : {
|
|
3519
3869
|
state: "failed",
|
|
3520
3870
|
error: info.error
|
|
@@ -3522,6 +3872,7 @@ function applyImpl(ctx, config) {
|
|
|
3522
3872
|
}
|
|
3523
3873
|
});
|
|
3524
3874
|
ctx.effect(() => () => {
|
|
3875
|
+
disposeRelayRegistrar();
|
|
3525
3876
|
tunnel.dispose();
|
|
3526
3877
|
}, "remote-web-ui: auto tunnel");
|
|
3527
3878
|
if ((ctx.webServer.host === "0.0.0.0" || ctx.webServer.host === "127.0.0.1") && Number.isFinite(ctx.webServer.port)) {
|
|
@@ -3762,12 +4113,35 @@ function applyImpl(ctx, config) {
|
|
|
3762
4113
|
else console.error("remote-web-ui: the host firewall rule could not be updated (admin rights required on managed platforms)");
|
|
3763
4114
|
}
|
|
3764
4115
|
}
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
4116
|
+
const plan = tunnelPlanOf(value, ctx.webServer.port);
|
|
4117
|
+
tunnelMode = plan.mode;
|
|
4118
|
+
if (plan.mode !== "quick") {
|
|
4119
|
+
disposeRelayRegistrar();
|
|
4120
|
+
if (plan.mode !== "named") setPublicBase();
|
|
4121
|
+
} else if (value.relay === false) {
|
|
4122
|
+
disposeRelayRegistrar(true);
|
|
4123
|
+
setPublicBase();
|
|
4124
|
+
} else if (rawTunnelUrl !== void 0) {
|
|
4125
|
+
const registrar = ensureRelayRegistrar();
|
|
4126
|
+
if (registrar !== void 0) announceRelay(registrar, rawTunnelUrl);
|
|
4127
|
+
}
|
|
4128
|
+
if (plan.mode === "quick") {
|
|
4129
|
+
for (const ignored of plan.ignored) console.warn(`remote-web-ui: autoTunnel is on — ignoring the configured ${ignored}`);
|
|
4130
|
+
const registrar = ensureRelayRegistrar();
|
|
4131
|
+
const originHostHeader = registrar === void 0 ? void 0 : new URL(registrar.baseUrl).host;
|
|
4132
|
+
tunnel.start(originHostHeader === void 0 ? plan.targetUrl : {
|
|
4133
|
+
kind: "quick",
|
|
4134
|
+
targetUrl: plan.targetUrl,
|
|
4135
|
+
originHostHeader
|
|
4136
|
+
});
|
|
4137
|
+
} else if (plan.mode === "named") tunnel.start({
|
|
4138
|
+
kind: "named",
|
|
4139
|
+
token: plan.token,
|
|
4140
|
+
publicUrl: plan.publicUrl
|
|
4141
|
+
});
|
|
4142
|
+
else {
|
|
3770
4143
|
tunnel.stop();
|
|
4144
|
+
if (value.tunnelToken !== void 0 && value.tunnelToken !== "") console.warn("remote-web-ui: tunnelToken is set but publicBaseUrl is missing or not a valid URL — fill the fixed public hostname of the named tunnel (e.g. https://dsh.example.com) to run it");
|
|
3771
4145
|
if (value.publicBaseUrl !== void 0 && !isHttpUrl(value.publicBaseUrl)) {
|
|
3772
4146
|
console.warn(`remote-web-ui: ignoring malformed publicBaseUrl ${JSON.stringify(value.publicBaseUrl)} (expected https://host[:port])`);
|
|
3773
4147
|
service.setPublicBaseUrl(void 0);
|
|
@@ -3827,14 +4201,5 @@ function applyImpl(ctx, config) {
|
|
|
3827
4201
|
});
|
|
3828
4202
|
sync();
|
|
3829
4203
|
}
|
|
3830
|
-
/** Whether a configured public base is a parseable http(s) URL with a host. */
|
|
3831
|
-
function isHttpUrl(value) {
|
|
3832
|
-
try {
|
|
3833
|
-
const url = new URL(value);
|
|
3834
|
-
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname !== "";
|
|
3835
|
-
} catch {
|
|
3836
|
-
return false;
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
4204
|
//#endregion
|
|
3840
4205
|
export { Config, REMOTE_WEB_UI_SETTINGS_NAMESPACE, apply, defaultDevicesFile, inject, name, pairingConfigOf };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAQnE,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG;IACrD,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;
|
|
1
|
+
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAQnE,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG;IACrD,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AA2BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BA2LxD"}
|
|
@@ -36,6 +36,7 @@ function mergeFrame(state, frame) {
|
|
|
36
36
|
onlineCount: frame.onlineCount,
|
|
37
37
|
devices: frame.devices ?? [],
|
|
38
38
|
...(frame.tunnel !== undefined ? { tunnel: frame.tunnel } : {}),
|
|
39
|
+
...(frame.relay !== undefined ? { relay: frame.relay } : {}),
|
|
39
40
|
...(frame.posture !== undefined ? { posture: frame.posture } : {}),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { PairingPhase } from '../pairing.ts';
|
|
3
|
-
import { type DeviceFrame, type PostureFrame, type TunnelStatusFrame } from './pair-api.ts';
|
|
3
|
+
import { type DeviceFrame, type PostureFrame, type RelayStatusFrame, type TunnelStatusFrame } from './pair-api.ts';
|
|
4
4
|
/** The panel's view state, owned by the entry component. */
|
|
5
5
|
export type PanelState = {
|
|
6
6
|
kind: 'lan-required';
|
|
@@ -29,6 +29,8 @@ export type PanelState = {
|
|
|
29
29
|
publicBaseUrl?: string;
|
|
30
30
|
/** Auto-tunnel status, while the auto-tunnel feature is active. */
|
|
31
31
|
tunnel?: TunnelStatusFrame;
|
|
32
|
+
/** Relay-registry status, while the stable-origin relay is in play. */
|
|
33
|
+
relay?: RelayStatusFrame;
|
|
32
34
|
/** Latest /api posture probe, once a round has completed. */
|
|
33
35
|
posture?: PostureFrame;
|
|
34
36
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AAItB,4DAA4D;AAC5D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,YAAY,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,yEAAyE;IACzE,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAEL,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,IAAI,IAAI,CAAA;IACf,MAAM,IAAI,IAAI,CAAA;IACd,SAAS,IAAI,IAAI,CAAA;IACjB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sDAAsD;IACtD,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,oEAAoE;IACpE,YAAY,IAAI,IAAI,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAgBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AAItB,4DAA4D;AAC5D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,YAAY,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,yEAAyE;IACzE,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,uEAAuE;IACvE,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAEL,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,IAAI,IAAI,CAAA;IACf,MAAM,IAAI,IAAI,CAAA;IACd,SAAS,IAAI,IAAI,CAAA;IACjB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sDAAsD;IACtD,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,oEAAoE;IACpE,YAAY,IAAI,IAAI,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAgBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAuK5I"}
|