@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
|
package/lib/types/pairing.js
CHANGED
|
@@ -74,6 +74,7 @@ export class PairingService {
|
|
|
74
74
|
publicBase;
|
|
75
75
|
/** Auto-tunnel status, while the auto-tunnel feature is active. */
|
|
76
76
|
tunnelStatus;
|
|
77
|
+
relayStatus;
|
|
77
78
|
posture;
|
|
78
79
|
/** True when lastSeenAt changed since the last persist (flushed on sweep). */
|
|
79
80
|
dirty = false;
|
|
@@ -205,6 +206,11 @@ export class PairingService {
|
|
|
205
206
|
this.tunnelStatus = status;
|
|
206
207
|
this.notify();
|
|
207
208
|
}
|
|
209
|
+
/** Set or clear the relay-registry status frame (undefined when not in play). */
|
|
210
|
+
setRelayStatus(status) {
|
|
211
|
+
this.relayStatus = status;
|
|
212
|
+
this.notify();
|
|
213
|
+
}
|
|
208
214
|
/** Set the latest /api posture probe result (see posture.ts). */
|
|
209
215
|
setPosture(snapshot) {
|
|
210
216
|
this.posture = snapshot;
|
|
@@ -361,6 +367,7 @@ export class PairingService {
|
|
|
361
367
|
lanAddresses: [...this.lanBases.keys()],
|
|
362
368
|
...(this.publicBase !== undefined ? { publicUrl: this.publicBase } : {}),
|
|
363
369
|
...(this.tunnelStatus !== undefined ? { tunnel: this.tunnelStatus } : {}),
|
|
370
|
+
...(this.relayStatus !== undefined ? { relay: this.relayStatus } : {}),
|
|
364
371
|
...(this.posture !== undefined ? { posture: this.posture } : {}),
|
|
365
372
|
...(token !== undefined ? { tokenId: token.record.id, tokenExpiresAt: token.record.expiresAt } : {}),
|
|
366
373
|
deviceCount: this.devices.size,
|
|
@@ -454,6 +461,7 @@ function snapshotsEqual(a, b) {
|
|
|
454
461
|
&& sameStrings(a.lanAddresses, b.lanAddresses)
|
|
455
462
|
&& a.publicUrl === b.publicUrl
|
|
456
463
|
&& tunnelEqual(a.tunnel, b.tunnel)
|
|
464
|
+
&& relayEqual(a.relay, b.relay)
|
|
457
465
|
&& a.tokenId === b.tokenId
|
|
458
466
|
&& a.tokenExpiresAt === b.tokenExpiresAt
|
|
459
467
|
&& a.deviceCount === b.deviceCount
|
|
@@ -477,6 +485,11 @@ function tunnelEqual(a, b) {
|
|
|
477
485
|
return a === b || (a !== undefined && b !== undefined
|
|
478
486
|
&& a.state === b.state && a.url === b.url && a.error === b.error);
|
|
479
487
|
}
|
|
488
|
+
/** Relay frame equality (same shape as the tunnel frame). */
|
|
489
|
+
function relayEqual(a, b) {
|
|
490
|
+
return a === b || (a !== undefined && b !== undefined
|
|
491
|
+
&& a.state === b.state && a.url === b.url && a.error === b.error);
|
|
492
|
+
}
|
|
480
493
|
/** Element-wise string list equality (interface order is meaningful). */
|
|
481
494
|
function sameStrings(a, b) {
|
|
482
495
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay registry client: a stable `https://<id>.dsh-market.com` origin in
|
|
3
|
+
* front of the plugin-managed quick tunnel, so a paired phone keeps one
|
|
4
|
+
* bookmark and one cookie context across `dsh web` restarts.
|
|
5
|
+
*
|
|
6
|
+
* The plugin mints an id + secret on first run and persists them per profile
|
|
7
|
+
* under `$DSH_HOME/remote-web-ui-registry/`; every tunnel (re)start
|
|
8
|
+
* re-registers the current quick URL with the market worker. Registration is
|
|
9
|
+
* best-effort: a registry outage never blocks the tunnel — the QR just falls
|
|
10
|
+
* back to the raw quick URL for that session (the pre-relay behavior).
|
|
11
|
+
*/
|
|
12
|
+
/** The market worker's registration endpoints (dsh-market.com edge API). */
|
|
13
|
+
export declare const RELAY_REGISTER_URL = "https://dsh-market.com/api/relay/register";
|
|
14
|
+
export declare const RELAY_UNREGISTER_URL = "https://dsh-market.com/api/relay/unregister";
|
|
15
|
+
/**
|
|
16
|
+
* The public origin template: one stable single-label subdomain per
|
|
17
|
+
* registration id. Single label on purpose — Universal SSL covers exactly
|
|
18
|
+
* one subdomain level, so a two-level hostname would get no certificate.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RELAY_BASE_SUFFIX = ".dsh-market.com";
|
|
21
|
+
export declare const RELAY_ID_RE: RegExp;
|
|
22
|
+
export declare const RELAY_SECRET_RE: RegExp;
|
|
23
|
+
/** Persisted per-profile relay identity. */
|
|
24
|
+
export interface RelayIdentity {
|
|
25
|
+
id: string;
|
|
26
|
+
secret: string;
|
|
27
|
+
/**
|
|
28
|
+
* True only for an identity minted in this process that has never been
|
|
29
|
+
* accepted by the registry: the first register carries `new_secret` so
|
|
30
|
+
* the endpoint may create the row; refreshes authenticate with `secret`
|
|
31
|
+
* alone and must not re-claim an id that already exists.
|
|
32
|
+
*/
|
|
33
|
+
fresh?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The observable relay lifecycle the settings card renders. */
|
|
36
|
+
export type RelayState = {
|
|
37
|
+
state: 'off';
|
|
38
|
+
} | {
|
|
39
|
+
state: 'registering';
|
|
40
|
+
} | {
|
|
41
|
+
state: 'running';
|
|
42
|
+
url: string;
|
|
43
|
+
} | {
|
|
44
|
+
state: 'failed';
|
|
45
|
+
error: string;
|
|
46
|
+
};
|
|
47
|
+
/** Registry storage layout: one file per profile under the relay directory. */
|
|
48
|
+
export declare function relayIdentityFile(profile: string, home?: string): string;
|
|
49
|
+
/** Generate one identity pair (id: 8-byte hex slug; secret: 32-byte base64url). */
|
|
50
|
+
export declare function generateRelayIdentity(random?: (size: number) => Buffer): RelayIdentity;
|
|
51
|
+
/**
|
|
52
|
+
* Load (or lazily mint) the per-profile relay identity. The file is written
|
|
53
|
+
* owner-only on first mint; a corrupt file is reminted (the phone then
|
|
54
|
+
* re-pairs once — the same cost as losing the devices file).
|
|
55
|
+
*/
|
|
56
|
+
export declare function loadRelayIdentity(profile: string, home?: string, random?: (size: number) => Buffer): RelayIdentity;
|
|
57
|
+
/** Best-effort removal of the mapping (used on disable; errors are swallowed). */
|
|
58
|
+
export declare function unregisterRelay(identity: RelayIdentity, endpoint?: string, fetchFn?: typeof fetch): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* The stable relay base for one identity — the origin the QR and the phone
|
|
61
|
+
* bookmark use while (or after) the registration is accepted.
|
|
62
|
+
*/
|
|
63
|
+
export declare function relayBaseOf(identity: RelayIdentity): string;
|
|
64
|
+
/**
|
|
65
|
+
* Registration loop with capped exponential backoff. Re-registration happens
|
|
66
|
+
* on every tunnel start (the quick URL can change after a crash-restart);
|
|
67
|
+
* failures retry with backoff and surface the last error to the card.
|
|
68
|
+
*/
|
|
69
|
+
export declare class RelayRegistrar {
|
|
70
|
+
private readonly identity;
|
|
71
|
+
private readonly setState;
|
|
72
|
+
private readonly options;
|
|
73
|
+
private timer;
|
|
74
|
+
private attempts;
|
|
75
|
+
private lastError;
|
|
76
|
+
private disposed;
|
|
77
|
+
constructor(identity: RelayIdentity, setState: (state: RelayState) => void, options?: {
|
|
78
|
+
fetchFn?: typeof fetch;
|
|
79
|
+
registerUrl?: string;
|
|
80
|
+
unregisterUrl?: string;
|
|
81
|
+
baseDelayMs?: number;
|
|
82
|
+
maxDelayMs?: number;
|
|
83
|
+
timerFn?: (fn: () => void, ms: number) => NodeJS.Timeout;
|
|
84
|
+
});
|
|
85
|
+
/** The stable relay base for the QR while registered. */
|
|
86
|
+
get baseUrl(): string;
|
|
87
|
+
/** The last registration failure detail (undefined while healthy). */
|
|
88
|
+
get error(): string | undefined;
|
|
89
|
+
/** Push one tunnel target; resolves the stable base once accepted. */
|
|
90
|
+
announce(target: string): Promise<string | undefined>;
|
|
91
|
+
/** Best-effort removal of the registry row (the relay toggle turned off). */
|
|
92
|
+
unregister(): Promise<void>;
|
|
93
|
+
private scheduleRetry;
|
|
94
|
+
/** Stop retrying and clear the card state (tunnel stopped or disabled). */
|
|
95
|
+
dispose(): void;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=relay-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-registry.d.ts","sourceRoot":"","sources":["../../src/relay-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,8CAA8C,CAAA;AAC7E,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AACjF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAA;AAElD,eAAO,MAAM,WAAW,QAAmB,CAAA;AAC3C,eAAO,MAAM,eAAe,QAAwB,CAAA;AAEpD,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,gEAAgE;AAChE,MAAM,MAAM,UAAU,GAClB;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAChB;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GACxB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAKnF;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,MAAM,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAoB,GAAG,aAAa,CAMnG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,EAAE,MAAM,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAoB,GAAG,aAAa,CAmB1I;AAYD,kFAAkF;AAClF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,aAAa,EACvB,QAAQ,GAAE,MAA6B,EACvC,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAE3D;AAED;;;;GAIG;AACH,qBAAa,cAAc;IAOvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,QAAQ,CAAQ;gBAGL,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACrC,OAAO,GAAE;QACxB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAA;KACpD;IAGR,yDAAyD;IACzD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,sEAAsE;IACtE,IAAI,KAAK,IAAI,MAAM,GAAG,SAAS,CAE9B;IAED,sEAAsE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyC3D,6EAA6E;IACvE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,OAAO,CAAC,aAAa;IAiBrB,2EAA2E;IAC3E,OAAO,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay registry client: a stable `https://<id>.dsh-market.com` origin in
|
|
3
|
+
* front of the plugin-managed quick tunnel, so a paired phone keeps one
|
|
4
|
+
* bookmark and one cookie context across `dsh web` restarts.
|
|
5
|
+
*
|
|
6
|
+
* The plugin mints an id + secret on first run and persists them per profile
|
|
7
|
+
* under `$DSH_HOME/remote-web-ui-registry/`; every tunnel (re)start
|
|
8
|
+
* re-registers the current quick URL with the market worker. Registration is
|
|
9
|
+
* best-effort: a registry outage never blocks the tunnel — the QR just falls
|
|
10
|
+
* back to the raw quick URL for that session (the pre-relay behavior).
|
|
11
|
+
*/
|
|
12
|
+
import { randomBytes } from 'node:crypto';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { dshHome } from "./dsh-home.js";
|
|
16
|
+
/** The market worker's registration endpoints (dsh-market.com edge API). */
|
|
17
|
+
export const RELAY_REGISTER_URL = 'https://dsh-market.com/api/relay/register';
|
|
18
|
+
export const RELAY_UNREGISTER_URL = 'https://dsh-market.com/api/relay/unregister';
|
|
19
|
+
/**
|
|
20
|
+
* The public origin template: one stable single-label subdomain per
|
|
21
|
+
* registration id. Single label on purpose — Universal SSL covers exactly
|
|
22
|
+
* one subdomain level, so a two-level hostname would get no certificate.
|
|
23
|
+
*/
|
|
24
|
+
export const RELAY_BASE_SUFFIX = '.dsh-market.com';
|
|
25
|
+
export const RELAY_ID_RE = /^[a-z0-9]{16}$/;
|
|
26
|
+
export const RELAY_SECRET_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
27
|
+
/** Registry storage layout: one file per profile under the relay directory. */
|
|
28
|
+
export function relayIdentityFile(profile, home = dshHome()) {
|
|
29
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {
|
|
30
|
+
throw new Error(`relay registry: profile ${JSON.stringify(profile)} is not a safe path segment`);
|
|
31
|
+
}
|
|
32
|
+
return join(home, 'remote-web-ui-registry', `${profile}.json`);
|
|
33
|
+
}
|
|
34
|
+
/** Generate one identity pair (id: 8-byte hex slug; secret: 32-byte base64url). */
|
|
35
|
+
export function generateRelayIdentity(random = randomBytes) {
|
|
36
|
+
return {
|
|
37
|
+
id: random(8).toString('hex'),
|
|
38
|
+
secret: random(32).toString('base64url'),
|
|
39
|
+
fresh: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load (or lazily mint) the per-profile relay identity. The file is written
|
|
44
|
+
* owner-only on first mint; a corrupt file is reminted (the phone then
|
|
45
|
+
* re-pairs once — the same cost as losing the devices file).
|
|
46
|
+
*/
|
|
47
|
+
export function loadRelayIdentity(profile, home = dshHome(), random = randomBytes) {
|
|
48
|
+
const file = relayIdentityFile(profile, home);
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(file)) {
|
|
51
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
52
|
+
if (typeof parsed.id === 'string' && RELAY_ID_RE.test(parsed.id)
|
|
53
|
+
&& typeof parsed.secret === 'string' && RELAY_SECRET_RE.test(parsed.secret)) {
|
|
54
|
+
// A persisted identity has certainly been registered before.
|
|
55
|
+
return { id: parsed.id, secret: parsed.secret };
|
|
56
|
+
}
|
|
57
|
+
console.warn(`relay registry: ${file} is malformed — reminting the identity (paired devices must scan the QR once more)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn(`relay registry: could not read ${file} (${error instanceof Error ? error.message : String(error)}) — reminting`);
|
|
62
|
+
}
|
|
63
|
+
const identity = generateRelayIdentity(random);
|
|
64
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
65
|
+
writeFileSync(file, JSON.stringify({ id: identity.id, secret: identity.secret }, null, 2) + '\n', { mode: 0o600 });
|
|
66
|
+
return identity;
|
|
67
|
+
}
|
|
68
|
+
/** Build the registration request body for one identity + tunnel target. */
|
|
69
|
+
function registrationBody(identity, target) {
|
|
70
|
+
return JSON.stringify({
|
|
71
|
+
id: identity.id,
|
|
72
|
+
secret: identity.secret,
|
|
73
|
+
new_secret: identity.fresh === true ? identity.secret : undefined,
|
|
74
|
+
target,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Best-effort removal of the mapping (used on disable; errors are swallowed). */
|
|
78
|
+
export async function unregisterRelay(identity, endpoint = RELAY_UNREGISTER_URL, fetchFn = fetch) {
|
|
79
|
+
await fetchFn(endpoint, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'content-type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({ id: identity.id, secret: identity.secret }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The stable relay base for one identity — the origin the QR and the phone
|
|
87
|
+
* bookmark use while (or after) the registration is accepted.
|
|
88
|
+
*/
|
|
89
|
+
export function relayBaseOf(identity) {
|
|
90
|
+
return `https://${identity.id}${RELAY_BASE_SUFFIX}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Registration loop with capped exponential backoff. Re-registration happens
|
|
94
|
+
* on every tunnel start (the quick URL can change after a crash-restart);
|
|
95
|
+
* failures retry with backoff and surface the last error to the card.
|
|
96
|
+
*/
|
|
97
|
+
export class RelayRegistrar {
|
|
98
|
+
identity;
|
|
99
|
+
setState;
|
|
100
|
+
options;
|
|
101
|
+
timer;
|
|
102
|
+
attempts = 0;
|
|
103
|
+
lastError;
|
|
104
|
+
disposed = false;
|
|
105
|
+
constructor(identity, setState, options = {}) {
|
|
106
|
+
this.identity = identity;
|
|
107
|
+
this.setState = setState;
|
|
108
|
+
this.options = options;
|
|
109
|
+
}
|
|
110
|
+
/** The stable relay base for the QR while registered. */
|
|
111
|
+
get baseUrl() {
|
|
112
|
+
return relayBaseOf(this.identity);
|
|
113
|
+
}
|
|
114
|
+
/** The last registration failure detail (undefined while healthy). */
|
|
115
|
+
get error() {
|
|
116
|
+
return this.lastError;
|
|
117
|
+
}
|
|
118
|
+
/** Push one tunnel target; resolves the stable base once accepted. */
|
|
119
|
+
async announce(target) {
|
|
120
|
+
// A disposed registrar stays disposed: a pending retry timer must not
|
|
121
|
+
// resurrect it (re-enabling creates a fresh registrar instead).
|
|
122
|
+
if (this.disposed)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (this.timer !== undefined) {
|
|
125
|
+
clearTimeout(this.timer);
|
|
126
|
+
this.timer = undefined;
|
|
127
|
+
}
|
|
128
|
+
this.setState({ state: 'registering' });
|
|
129
|
+
try {
|
|
130
|
+
const fetchFn = this.options.fetchFn ?? fetch;
|
|
131
|
+
const response = await fetchFn(this.options.registerUrl ?? RELAY_REGISTER_URL, {
|
|
132
|
+
method: 'PUT',
|
|
133
|
+
headers: { 'content-type': 'application/json' },
|
|
134
|
+
body: registrationBody(this.identity, target),
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const detail = await response.json().catch(() => undefined);
|
|
138
|
+
const error = detail && typeof detail.error === 'string' ? detail.error : `HTTP ${String(response.status)}`;
|
|
139
|
+
throw new Error(error);
|
|
140
|
+
}
|
|
141
|
+
// The row exists now: never claim a fresh id twice.
|
|
142
|
+
this.identity.fresh = false;
|
|
143
|
+
this.attempts = 0;
|
|
144
|
+
this.lastError = undefined;
|
|
145
|
+
this.setState({ state: 'running', url: this.baseUrl });
|
|
146
|
+
return this.baseUrl;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
150
|
+
if (this.lastError === 'invalid-params' && this.identity.fresh !== true) {
|
|
151
|
+
// The registry lost the row (a D1 restore or manual wipe): re-claim
|
|
152
|
+
// the id with a fresh claim on the next retry. A 403 auth-failed
|
|
153
|
+
// means the id is owned by another secret and stays failed.
|
|
154
|
+
this.identity.fresh = true;
|
|
155
|
+
}
|
|
156
|
+
this.setState({ state: 'failed', error: this.lastError });
|
|
157
|
+
this.scheduleRetry(target);
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Best-effort removal of the registry row (the relay toggle turned off). */
|
|
162
|
+
async unregister() {
|
|
163
|
+
await unregisterRelay(this.identity, this.options.unregisterUrl, this.options.fetchFn);
|
|
164
|
+
}
|
|
165
|
+
scheduleRetry(target) {
|
|
166
|
+
if (this.disposed)
|
|
167
|
+
return;
|
|
168
|
+
const base = this.options.baseDelayMs ?? 5_000;
|
|
169
|
+
const max = this.options.maxDelayMs ?? 60_000;
|
|
170
|
+
const delay = Math.min(base * 2 ** this.attempts, max);
|
|
171
|
+
this.attempts += 1;
|
|
172
|
+
const timerFn = this.options.timerFn ?? ((fn, ms) => {
|
|
173
|
+
const timer = setTimeout(fn, ms);
|
|
174
|
+
timer.unref?.();
|
|
175
|
+
return timer;
|
|
176
|
+
});
|
|
177
|
+
this.timer = timerFn(() => {
|
|
178
|
+
this.timer = undefined;
|
|
179
|
+
void this.announce(target);
|
|
180
|
+
}, delay);
|
|
181
|
+
}
|
|
182
|
+
/** Stop retrying and clear the card state (tunnel stopped or disabled). */
|
|
183
|
+
dispose() {
|
|
184
|
+
this.disposed = true;
|
|
185
|
+
if (this.timer !== undefined) {
|
|
186
|
+
clearTimeout(this.timer);
|
|
187
|
+
this.timer = undefined;
|
|
188
|
+
}
|
|
189
|
+
this.setState({ state: 'off' });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure planner for the settings-sync tunnel branch: given the resolved
|
|
3
|
+
* settings slice, decide which tunnel mode runs and with what target. Kept
|
|
4
|
+
* free of cordis types so the precedence matrix is unit-testable without a
|
|
5
|
+
* host context.
|
|
6
|
+
*/
|
|
7
|
+
/** Whether a configured public base is a parseable http(s) URL with a host. */
|
|
8
|
+
export declare function isHttpUrl(value: string): boolean;
|
|
9
|
+
/** How the settings sync should drive the tunnel for one resolved value. */
|
|
10
|
+
export type TunnelPlan = {
|
|
11
|
+
mode: 'off';
|
|
12
|
+
} | {
|
|
13
|
+
mode: 'quick';
|
|
14
|
+
targetUrl: string;
|
|
15
|
+
ignored: Array<'tunnelToken' | 'publicBaseUrl'>;
|
|
16
|
+
} | {
|
|
17
|
+
mode: 'named';
|
|
18
|
+
token: string;
|
|
19
|
+
publicUrl: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decide the tunnel mode and target from one resolved settings value.
|
|
23
|
+
* Precedence: the quick tunnel wins when `autoTunnel` is on (anything else
|
|
24
|
+
* configured is reported as ignored); the named tunnel runs only with a
|
|
25
|
+
* token AND a valid public hostname (the token does not carry the hostname,
|
|
26
|
+
* so the QR base and the fence trust would have no source without it);
|
|
27
|
+
* otherwise no tunnel and the manual public base applies.
|
|
28
|
+
* @param value - the resolved settings slice that drives the tunnel.
|
|
29
|
+
* @param port - the local webServer port the tunnel forwards to.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tunnelPlanOf(value: {
|
|
32
|
+
autoTunnel?: boolean;
|
|
33
|
+
tunnelToken?: string;
|
|
34
|
+
publicBaseUrl?: string;
|
|
35
|
+
}, port: number): TunnelPlan;
|
|
36
|
+
//# sourceMappingURL=tunnel-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-plan.d.ts","sourceRoot":"","sources":["../../src/tunnel-plan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOhD;AAED,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GACf;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC,aAAa,GAAG,eAAe,CAAC,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,IAAI,EAAE,MAAM,GACX,UAAU,CAcZ"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure planner for the settings-sync tunnel branch: given the resolved
|
|
3
|
+
* settings slice, decide which tunnel mode runs and with what target. Kept
|
|
4
|
+
* free of cordis types so the precedence matrix is unit-testable without a
|
|
5
|
+
* host context.
|
|
6
|
+
*/
|
|
7
|
+
/** Whether a configured public base is a parseable http(s) URL with a host. */
|
|
8
|
+
export function isHttpUrl(value) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && url.hostname !== '';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Decide the tunnel mode and target from one resolved settings value.
|
|
19
|
+
* Precedence: the quick tunnel wins when `autoTunnel` is on (anything else
|
|
20
|
+
* configured is reported as ignored); the named tunnel runs only with a
|
|
21
|
+
* token AND a valid public hostname (the token does not carry the hostname,
|
|
22
|
+
* so the QR base and the fence trust would have no source without it);
|
|
23
|
+
* otherwise no tunnel and the manual public base applies.
|
|
24
|
+
* @param value - the resolved settings slice that drives the tunnel.
|
|
25
|
+
* @param port - the local webServer port the tunnel forwards to.
|
|
26
|
+
*/
|
|
27
|
+
export function tunnelPlanOf(value, port) {
|
|
28
|
+
if (value.autoTunnel === true) {
|
|
29
|
+
const ignored = [];
|
|
30
|
+
if (value.tunnelToken !== undefined && value.tunnelToken !== '')
|
|
31
|
+
ignored.push('tunnelToken');
|
|
32
|
+
if (value.publicBaseUrl !== undefined && value.publicBaseUrl !== '')
|
|
33
|
+
ignored.push('publicBaseUrl');
|
|
34
|
+
return { mode: 'quick', targetUrl: `http://127.0.0.1:${String(port)}`, ignored };
|
|
35
|
+
}
|
|
36
|
+
if (value.tunnelToken !== undefined && value.tunnelToken !== '') {
|
|
37
|
+
if (value.publicBaseUrl !== undefined && isHttpUrl(value.publicBaseUrl)) {
|
|
38
|
+
return { mode: 'named', token: value.tunnelToken, publicUrl: value.publicBaseUrl };
|
|
39
|
+
}
|
|
40
|
+
return { mode: 'off' };
|
|
41
|
+
}
|
|
42
|
+
return { mode: 'off' };
|
|
43
|
+
}
|
package/lib/types/tunnel.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auto-tunnel manager: spawns a Cloudflare
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Auto-tunnel manager: spawns a Cloudflare tunnel through the `cloudflared`
|
|
3
|
+
* npm package — its postinstall downloads the platform binary, so no
|
|
4
|
+
* user-side tooling is involved — surfaces the public URL, and restarts the
|
|
5
|
+
* process after unexpected exits with exponential backoff. Two modes: the
|
|
6
|
+
* accountless quick tunnel (`https://xxx.trycloudflare.com`, hostname minted
|
|
7
|
+
* per start) and an account named tunnel (`cloudflared tunnel run --token`,
|
|
8
|
+
* fixed public hostname — the mode that lets a paired phone keep its
|
|
9
|
+
* bookmark and pairing cookie across restarts).
|
|
7
10
|
*
|
|
8
11
|
* The cloudflared package's Tunnel is a thin spawn wrapper; this manager
|
|
9
12
|
* owns the lifecycle policy (binary readiness, URL timeout, restart
|
|
@@ -13,6 +16,42 @@
|
|
|
13
16
|
*/
|
|
14
17
|
/** The observable tunnel lifecycle the settings/panel surfaces render. */
|
|
15
18
|
export type TunnelPhase = 'stopped' | 'starting' | 'running' | 'failed';
|
|
19
|
+
/**
|
|
20
|
+
* What the manager should run. `quick` is the accountless trycloudflare
|
|
21
|
+
* tunnel whose hostname is minted per start (and dies with the process);
|
|
22
|
+
* `named` is an account tunnel whose public hostname is fixed — the point
|
|
23
|
+
* of the named mode is that a paired phone keeps its bookmark and its
|
|
24
|
+
* pairing cookie across restarts.
|
|
25
|
+
*/
|
|
26
|
+
export type TunnelTarget = {
|
|
27
|
+
kind: 'quick';
|
|
28
|
+
targetUrl: string;
|
|
29
|
+
originHostHeader?: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'named';
|
|
32
|
+
token: string;
|
|
33
|
+
publicUrl: string;
|
|
34
|
+
};
|
|
35
|
+
/** The event face the named adapter wraps (a cloudflared package Tunnel). */
|
|
36
|
+
interface NamedTunnelProcess {
|
|
37
|
+
on(event: 'connected', listener: () => void): unknown;
|
|
38
|
+
on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;
|
|
39
|
+
on(event: 'error', listener: (value: Error) => void): unknown;
|
|
40
|
+
off(event: string, listener: (...args: any[]) => void): unknown;
|
|
41
|
+
stop(): boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wrap a named-tunnel process so it fits the quick-tunnel handle shape: the
|
|
45
|
+
* fixed public URL is reported through the same `url` event once the first
|
|
46
|
+
* edge connection registers (cloudflared fires `connected` per connection,
|
|
47
|
+
* so only the first is taken), and exit/error pass through. The manager's
|
|
48
|
+
* URL timeout, crash-restart backoff, and stop semantics then stay fully
|
|
49
|
+
* mode-agnostic.
|
|
50
|
+
* @param inner - the running named-tunnel process.
|
|
51
|
+
* @param publicUrl - the fixed public hostname ingress maps to this server.
|
|
52
|
+
* @returns a handle emitting `url` once the tunnel is reachable.
|
|
53
|
+
*/
|
|
54
|
+
export declare function namedTunnelHandle(inner: NamedTunnelProcess, publicUrl: string): TunnelHandle;
|
|
16
55
|
/** One tunnel status frame. */
|
|
17
56
|
export interface TunnelInfo {
|
|
18
57
|
phase: TunnelPhase;
|
|
@@ -29,8 +68,8 @@ export interface TunnelHandle {
|
|
|
29
68
|
}
|
|
30
69
|
/** Injectable seams (defaults are the real cloudflared package + node timers). */
|
|
31
70
|
export interface TunnelManagerOptions {
|
|
32
|
-
/** Spawn one
|
|
33
|
-
factory?: (
|
|
71
|
+
/** Spawn one tunnel process for the target (quick or named). */
|
|
72
|
+
factory?: (target: TunnelTarget) => TunnelHandle;
|
|
34
73
|
/** Make sure the cloudflared binary exists, downloading it when absent. */
|
|
35
74
|
ensureBinary?: () => Promise<void>;
|
|
36
75
|
/** Wait up to this long for the tunnel URL before failing the attempt. */
|
|
@@ -45,6 +84,15 @@ export interface TunnelManagerOptions {
|
|
|
45
84
|
clearTimeout(t: unknown): void;
|
|
46
85
|
};
|
|
47
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Spawn flags for one quick target. `originHostHeader` stamps the Host the
|
|
89
|
+
* local webserver sees: the plugin's phone-facing fence trusts the configured
|
|
90
|
+
* public host (the stable relay origin, Host-bound) and the harness
|
|
91
|
+
* browser-auth binds cookies to the Host authority, while the Workers relay
|
|
92
|
+
* cannot control the origin-side Host at all (fetch forces it to the URL
|
|
93
|
+
* authority) — so the connector must restamp it with the stable origin.
|
|
94
|
+
*/
|
|
95
|
+
export declare function quickTunnelFlags(originHostHeader?: string): Record<string, string | boolean>;
|
|
48
96
|
/**
|
|
49
97
|
* Own the lifecycle of one auto-tunnel: start/stop, URL surfacing, and
|
|
50
98
|
* crash-restart backoff.
|
|
@@ -59,7 +107,7 @@ export declare class TunnelManager {
|
|
|
59
107
|
private phase;
|
|
60
108
|
private url;
|
|
61
109
|
private error;
|
|
62
|
-
private
|
|
110
|
+
private target;
|
|
63
111
|
private handle;
|
|
64
112
|
private urlTimer;
|
|
65
113
|
private restartTimer;
|
|
@@ -75,12 +123,16 @@ export declare class TunnelManager {
|
|
|
75
123
|
/** The current status frame. */
|
|
76
124
|
get info(): TunnelInfo;
|
|
77
125
|
/**
|
|
78
|
-
* Start (or keep) a
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
126
|
+
* Start (or keep) a tunnel toward `target`. Restarting with a different
|
|
127
|
+
* target tears the old tunnel down first; restarting with the same target
|
|
128
|
+
* while running is a no-op. A string is the quick mode's local target URL.
|
|
129
|
+
*
|
|
130
|
+
* In quick mode the URL surfaces when cloudflared mints the ephemeral
|
|
131
|
+
* trycloudflare hostname; in named mode the fixed `publicUrl` surfaces
|
|
132
|
+
* once the first edge connection registers (see {@link namedTunnelHandle}).
|
|
133
|
+
* @param target - what to run (a string means quick toward that local URL).
|
|
82
134
|
*/
|
|
83
|
-
start(
|
|
135
|
+
start(target: TunnelTarget | string): void;
|
|
84
136
|
/** Stop the tunnel for good: no restarts, no state. */
|
|
85
137
|
stop(): void;
|
|
86
138
|
/** Alias of {@link stop} for plugin-effect disposal. */
|
|
@@ -97,4 +149,5 @@ export declare class TunnelManager {
|
|
|
97
149
|
private teardown;
|
|
98
150
|
private setPhase;
|
|
99
151
|
}
|
|
152
|
+
export {};
|
|
100
153
|
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
|