@vincentt-xr/cli 0.19.0 → 0.21.0
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/MIGRATION.md +25 -0
- package/README.md +3 -2
- package/dist/cli/index.js +95 -13
- package/dist/cli/usage-text.js +3 -2
- package/dist/domain/api.js +16 -12
- package/dist/domain/command.js +9 -0
- package/dist/dx/command.js +3 -0
- package/dist/dx/consent.js +10 -14
- package/dist/dx/reportIo.js +6 -4
- package/dist/dx/send.js +6 -4
- package/dist/lib/backend.js +5 -10
- package/dist/lib/exitCodes.d.ts +6 -0
- package/dist/lib/exitCodes.js +6 -0
- package/dist/lib/terminalText.d.ts +6 -0
- package/dist/lib/terminalText.js +10 -0
- package/dist/lib/whoami.js +3 -0
- package/dist/library/assetsCommand.js +4 -0
- package/dist/library/client.js +6 -8
- package/dist/login/authBackend.d.ts +27 -2
- package/dist/login/authBackend.js +86 -17
- package/dist/login/loginCommand.d.ts +35 -3
- package/dist/login/loginCommand.js +81 -5
- package/dist/login/machineLabel.d.ts +46 -0
- package/dist/login/machineLabel.js +119 -0
- package/dist/login/pkceLogin.d.ts +17 -1
- package/dist/login/pkceLogin.js +30 -5
- package/dist/platform/releaseFetch.js +36 -69
- package/dist/preview/api.js +9 -12
- package/dist/preview/command.d.ts +3 -2
- package/dist/preview/command.js +8 -5
- package/dist/preview/copy.d.ts +8 -1
- package/dist/preview/copy.js +17 -4
- package/dist/preview/proxy.js +5 -39
- package/dist/preview/runner.d.ts +25 -9
- package/dist/preview/runner.js +42 -15
- package/dist/publish/api.js +14 -14
- package/dist/publish/command.d.ts +3 -0
- package/dist/publish/command.js +71 -7
- package/dist/publish/copy.d.ts +9 -0
- package/dist/publish/copy.js +83 -14
- package/dist/publish/keyScan.d.ts +25 -0
- package/dist/publish/keyScan.js +88 -0
- package/dist/publish/manifest.d.ts +51 -19
- package/dist/publish/manifest.js +246 -63
- package/dist/publish/upload.d.ts +8 -0
- package/dist/publish/upload.js +15 -0
- package/dist/publish/weight.d.ts +23 -0
- package/dist/publish/weight.js +113 -0
- package/dist/shared/authError.d.ts +1 -1
- package/dist/shared/authError.js +3 -1
- package/dist/shared/credentialNames.d.ts +17 -0
- package/dist/shared/credentialNames.js +72 -0
- package/dist/shared/http.d.ts +34 -0
- package/dist/shared/http.js +98 -2
- package/dist/shared/upgradeError.d.ts +62 -0
- package/dist/shared/upgradeError.js +215 -0
- package/dist/tunnel/caps.d.ts +19 -0
- package/dist/tunnel/caps.js +52 -0
- package/dist/tunnel/cbor.d.ts +39 -12
- package/dist/tunnel/cbor.js +84 -10
- package/dist/tunnel/client.d.ts +48 -2
- package/dist/tunnel/client.js +157 -44
- package/dist/tunnel/frame.d.ts +1 -1
- package/dist/tunnel/frame.js +8 -4
- package/dist/tunnel/index.d.ts +3 -2
- package/dist/tunnel/index.js +3 -2
- package/package.json +4 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability negotiation, the client's half (f3 §2.2.2). The client offers on
|
|
3
|
+
* every dial; the edge answers the agreed set on the 101, or omits the header
|
|
4
|
+
* when it agreed nothing. The agreed set fixes this connection's decode grammar.
|
|
5
|
+
*/
|
|
6
|
+
import { BASE_CAPS, TUNNEL_CAPS } from "./cbor.js";
|
|
7
|
+
/** Lower-case: Node lower-cases response header names. */
|
|
8
|
+
export const CAPS_HEADER = "x-vincentt-tunnel-caps";
|
|
9
|
+
/** Every element this client understands, offered on every dial. */
|
|
10
|
+
export const OFFERED_CAPS = TUNNEL_CAPS;
|
|
11
|
+
export const OFFERED_CAPS_VALUE = OFFERED_CAPS.join(", ");
|
|
12
|
+
const TOKEN_RE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
13
|
+
const MAX_TOKENS = 8;
|
|
14
|
+
const MAX_BYTES = 256;
|
|
15
|
+
/**
|
|
16
|
+
* The agreed set from a 101, or null for a protocol violation: the header
|
|
17
|
+
* duplicated, an array, ungrammatical, or naming a token this client did not
|
|
18
|
+
* offer. Absent is the base protocol, never a violation. `rawHeaders` is
|
|
19
|
+
* consulted because Node joins a duplicated header into one comma list, which
|
|
20
|
+
* would otherwise read as a grammatical value.
|
|
21
|
+
*/
|
|
22
|
+
export function parseAgreedCaps(value, rawHeaders, offered = OFFERED_CAPS) {
|
|
23
|
+
if (rawHeaders !== undefined) {
|
|
24
|
+
let seen = 0;
|
|
25
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
26
|
+
if (rawHeaders[i]?.toLowerCase() === CAPS_HEADER)
|
|
27
|
+
seen++;
|
|
28
|
+
}
|
|
29
|
+
if (seen > 1)
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return BASE_CAPS;
|
|
34
|
+
if (typeof value !== "string")
|
|
35
|
+
return null;
|
|
36
|
+
if (Buffer.byteLength(value, "utf8") > MAX_BYTES)
|
|
37
|
+
return null;
|
|
38
|
+
const tokens = value.split(",").map((t) => t.replace(/^ +| +$/g, ""));
|
|
39
|
+
if (tokens.length > MAX_TOKENS)
|
|
40
|
+
return null;
|
|
41
|
+
const agreed = new Set();
|
|
42
|
+
for (const t of tokens) {
|
|
43
|
+
if (!TOKEN_RE.test(t))
|
|
44
|
+
return null;
|
|
45
|
+
if (!offered.includes(t))
|
|
46
|
+
return null;
|
|
47
|
+
if (agreed.has(t))
|
|
48
|
+
return null;
|
|
49
|
+
agreed.add(t);
|
|
50
|
+
}
|
|
51
|
+
return agreed;
|
|
52
|
+
}
|
package/dist/tunnel/cbor.d.ts
CHANGED
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
* themselves, but because the encoder we own never emits them, so accepting
|
|
16
16
|
* them would only widen the input this parser has to be correct about.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* The optional protocol elements a connection may agree on the upgrade (f3
|
|
20
|
+
* §2.2.2). Each changes one disjoint grammar element.
|
|
21
|
+
*/
|
|
22
|
+
export declare const TUNNEL_CAPS: readonly ["open-body", "control-authz-expired"];
|
|
23
|
+
export type TunnelCap = (typeof TUNNEL_CAPS)[number];
|
|
24
|
+
export type CapSet = ReadonlySet<TunnelCap>;
|
|
25
|
+
/** The agreed-nothing grammar: exactly the pre-negotiation protocol. */
|
|
26
|
+
export declare const BASE_CAPS: CapSet;
|
|
18
27
|
/**
|
|
19
28
|
* VIEWER_LABEL_RE is the SAME regex the edge assigns against, and this client
|
|
20
29
|
* RE-VALIDATES rather than trusting the edge. That is closure (c) of the three
|
|
@@ -39,6 +48,11 @@ export interface OpenPayload {
|
|
|
39
48
|
method: string;
|
|
40
49
|
path: string;
|
|
41
50
|
headers: [string, string][];
|
|
51
|
+
/**
|
|
52
|
+
* Whether a request body follows. Present on every non-upgrade OPEN iff the
|
|
53
|
+
* connection agreed `open-body`; never present with `upgrade`.
|
|
54
|
+
*/
|
|
55
|
+
body?: boolean;
|
|
42
56
|
upgrade?: string;
|
|
43
57
|
}
|
|
44
58
|
export interface HeadPayload {
|
|
@@ -46,26 +60,39 @@ export interface HeadPayload {
|
|
|
46
60
|
headers: [string, string][];
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
|
-
* `viewer_gone` is the ONE non-terminal kind: the
|
|
50
|
-
* is ending, this one means a viewer's connection closed while
|
|
51
|
-
* continues. It is also the only kind carrying a second key.
|
|
63
|
+
* A closed enum of five. `viewer_gone` is the ONE non-terminal kind: the others
|
|
64
|
+
* mean the session is ending, this one means a viewer's connection closed while
|
|
65
|
+
* the session continues. It is also the only kind carrying a second key.
|
|
66
|
+
* `authz_expired` exists only on a connection that agreed
|
|
67
|
+
* `control-authz-expired`.
|
|
52
68
|
*/
|
|
53
|
-
export type ControlKind = "revoke" | "expiring" | "superseded" | "viewer_gone";
|
|
69
|
+
export type ControlKind = "revoke" | "expiring" | "superseded" | "authz_expired" | "viewer_gone";
|
|
54
70
|
/** A decoded CONTROL payload. `viewerLabel` is present only on `viewer_gone`. */
|
|
55
71
|
export interface ControlPayload {
|
|
56
72
|
kind: ControlKind;
|
|
57
73
|
viewerLabel?: string;
|
|
58
74
|
}
|
|
59
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Decode an OPEN under the connection's agreed set. `body` on a connection that
|
|
77
|
+
* did not agree `open-body`, its absence on a non-upgrade OPEN that did, and
|
|
78
|
+
* `body` beside `upgrade` are all fatal: only the edge writes that key.
|
|
79
|
+
*/
|
|
80
|
+
export declare function decodeOpen(b: Uint8Array, caps?: CapSet): OpenPayload;
|
|
60
81
|
export declare function decodeHead(b: Uint8Array): HeadPayload;
|
|
61
82
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
83
|
+
* Decode a CONTROL under the connection's agreed set. EVERY failure is fatal:
|
|
84
|
+
* CONTROL rides stream 0, so there is no stream to reset, and a CONTROL the
|
|
85
|
+
* client cannot read is the edge misbehaving. The map arity is asserted
|
|
86
|
+
* AGAINST THE KIND, so a payload claiming `revoke` with a trailing label is
|
|
87
|
+
* refused rather than quietly accepted with the extra key ignored.
|
|
88
|
+
*/
|
|
89
|
+
export declare function decodeControl(b: Uint8Array, caps?: CapSet): ControlPayload;
|
|
90
|
+
/**
|
|
91
|
+
* Canonical key order: viewerLabel, method, path, headers, [body], [upgrade].
|
|
92
|
+
* The client never sends an OPEN; this is the mirror of the edge's encoder for
|
|
93
|
+
* the fixture round trip, and it refuses an element outside `caps` the same way.
|
|
65
94
|
*/
|
|
66
|
-
export declare function
|
|
67
|
-
/** Canonical key order: viewerLabel, method, path, headers, [upgrade]. */
|
|
68
|
-
export declare function encodeOpen(p: OpenPayload): Uint8Array;
|
|
95
|
+
export declare function encodeOpen(p: OpenPayload, caps?: CapSet): Uint8Array;
|
|
69
96
|
/** Canonical key order: status, headers. */
|
|
70
97
|
export declare function encodeHead(p: HeadPayload): Uint8Array;
|
|
71
|
-
export declare function encodeControl(kind: ControlKind, viewerLabel?: string): Uint8Array;
|
|
98
|
+
export declare function encodeControl(kind: ControlKind, viewerLabel?: string, caps?: CapSet): Uint8Array;
|
package/dist/tunnel/cbor.js
CHANGED
|
@@ -18,10 +18,25 @@
|
|
|
18
18
|
import { MAX_CBOR_DEPTH, MAX_HEADERS, MAX_HEADER_BYTES, MAX_OPEN_PAYLOAD, MAX_PATH_LEN, ProtocolError, } from "./frame.js";
|
|
19
19
|
const MAX_METHOD_LEN = 16;
|
|
20
20
|
const MAX_VIEWER_LABEL_LEN = 11;
|
|
21
|
-
/**
|
|
21
|
+
/** A CBOR shape error is STREAM-level: the stream dies, the tunnel SURVIVES. */
|
|
22
22
|
function fail(code) {
|
|
23
23
|
throw new ProtocolError(code, false);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* An element outside the connection's agreed set, or a violation of what the
|
|
27
|
+
* agreement promises, closes the TUNNEL: only the edge writes these keys, so a
|
|
28
|
+
* wrong one is the edge misbehaving, not a viewer.
|
|
29
|
+
*/
|
|
30
|
+
function fatal(code) {
|
|
31
|
+
throw new ProtocolError(code, true);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The optional protocol elements a connection may agree on the upgrade (f3
|
|
35
|
+
* §2.2.2). Each changes one disjoint grammar element.
|
|
36
|
+
*/
|
|
37
|
+
export const TUNNEL_CAPS = ["open-body", "control-authz-expired"];
|
|
38
|
+
/** The agreed-nothing grammar: exactly the pre-negotiation protocol. */
|
|
39
|
+
export const BASE_CAPS = new Set();
|
|
25
40
|
/**
|
|
26
41
|
* VIEWER_LABEL_RE is the SAME regex the edge assigns against, and this client
|
|
27
42
|
* RE-VALIDATES rather than trusting the edge. That is closure (c) of the three
|
|
@@ -61,8 +76,13 @@ const CONTROL_ARITY = new Map([
|
|
|
61
76
|
["revoke", 1],
|
|
62
77
|
["expiring", 1],
|
|
63
78
|
["superseded", 1],
|
|
79
|
+
["authz_expired", 1],
|
|
64
80
|
["viewer_gone", 2],
|
|
65
81
|
]);
|
|
82
|
+
/** The kinds a connection may receive only when it agreed the named element. */
|
|
83
|
+
const NEGOTIATED_KIND = new Map([
|
|
84
|
+
["authz_expired", "control-authz-expired"],
|
|
85
|
+
]);
|
|
66
86
|
// ---- decoding ---------------------------------------------------------------
|
|
67
87
|
class Reader {
|
|
68
88
|
b;
|
|
@@ -140,6 +160,13 @@ class Reader {
|
|
|
140
160
|
fail("cbor_malformed");
|
|
141
161
|
return arg;
|
|
142
162
|
}
|
|
163
|
+
/** CBOR simple values 20 (false) and 21 (true); nothing else. */
|
|
164
|
+
bool() {
|
|
165
|
+
const { major, arg } = this.head();
|
|
166
|
+
if (major !== 7 || (arg !== 20 && arg !== 21))
|
|
167
|
+
fail("cbor_malformed");
|
|
168
|
+
return arg === 21;
|
|
169
|
+
}
|
|
143
170
|
/**
|
|
144
171
|
* headerPairs reads a map of text->text, enforcing MAX_HEADERS and
|
|
145
172
|
* MAX_HEADER_BYTES as it goes. The COUNT bound is checked against the
|
|
@@ -173,7 +200,12 @@ class Reader {
|
|
|
173
200
|
}
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Decode an OPEN under the connection's agreed set. `body` on a connection that
|
|
205
|
+
* did not agree `open-body`, its absence on a non-upgrade OPEN that did, and
|
|
206
|
+
* `body` beside `upgrade` are all fatal: only the edge writes that key.
|
|
207
|
+
*/
|
|
208
|
+
export function decodeOpen(b, caps = BASE_CAPS) {
|
|
177
209
|
if (b.length > MAX_OPEN_PAYLOAD)
|
|
178
210
|
fail("open_payload_too_large");
|
|
179
211
|
const r = new Reader(b);
|
|
@@ -184,6 +216,7 @@ export function decodeOpen(b) {
|
|
|
184
216
|
let method;
|
|
185
217
|
let path;
|
|
186
218
|
let headers = [];
|
|
219
|
+
let body;
|
|
187
220
|
let upgrade;
|
|
188
221
|
for (let k = 0; k < arg; k++) {
|
|
189
222
|
const key = r.text(32);
|
|
@@ -210,6 +243,11 @@ export function decodeOpen(b) {
|
|
|
210
243
|
case "headers":
|
|
211
244
|
headers = r.headerPairs();
|
|
212
245
|
break;
|
|
246
|
+
case "body":
|
|
247
|
+
if (!caps.has("open-body"))
|
|
248
|
+
fatal("not_negotiated");
|
|
249
|
+
body = r.bool();
|
|
250
|
+
break;
|
|
213
251
|
case "upgrade":
|
|
214
252
|
upgrade = r.text(32);
|
|
215
253
|
break;
|
|
@@ -227,7 +265,15 @@ export function decodeOpen(b) {
|
|
|
227
265
|
// frame.
|
|
228
266
|
if (r.i !== b.length)
|
|
229
267
|
fail("cbor_malformed");
|
|
268
|
+
if (caps.has("open-body")) {
|
|
269
|
+
if (upgrade !== undefined && body !== undefined)
|
|
270
|
+
fatal("open_body_with_upgrade");
|
|
271
|
+
if (upgrade === undefined && body === undefined)
|
|
272
|
+
fatal("open_body_missing");
|
|
273
|
+
}
|
|
230
274
|
const out = { viewerLabel, method, path, headers };
|
|
275
|
+
if (body !== undefined)
|
|
276
|
+
out.body = body;
|
|
231
277
|
if (upgrade !== undefined)
|
|
232
278
|
out.upgrade = upgrade;
|
|
233
279
|
return out;
|
|
@@ -267,11 +313,23 @@ export function decodeHead(b) {
|
|
|
267
313
|
return { status, headers };
|
|
268
314
|
}
|
|
269
315
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
316
|
+
* Decode a CONTROL under the connection's agreed set. EVERY failure is fatal:
|
|
317
|
+
* CONTROL rides stream 0, so there is no stream to reset, and a CONTROL the
|
|
318
|
+
* client cannot read is the edge misbehaving. The map arity is asserted
|
|
319
|
+
* AGAINST THE KIND, so a payload claiming `revoke` with a trailing label is
|
|
320
|
+
* refused rather than quietly accepted with the extra key ignored.
|
|
273
321
|
*/
|
|
274
|
-
export function decodeControl(b) {
|
|
322
|
+
export function decodeControl(b, caps = BASE_CAPS) {
|
|
323
|
+
try {
|
|
324
|
+
return decodeControlShape(b, caps);
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
if (err instanceof ProtocolError && !err.fatal)
|
|
328
|
+
fatal(err.code);
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function decodeControlShape(b, caps) {
|
|
275
333
|
if (b.length > MAX_OPEN_PAYLOAD)
|
|
276
334
|
fail("open_payload_too_large");
|
|
277
335
|
const r = new Reader(b);
|
|
@@ -281,6 +339,9 @@ export function decodeControl(b) {
|
|
|
281
339
|
if (r.text(32) !== "kind")
|
|
282
340
|
fail("cbor_malformed");
|
|
283
341
|
const v = r.text(32);
|
|
342
|
+
const needs = NEGOTIATED_KIND.get(v);
|
|
343
|
+
if (needs !== undefined && !caps.has(needs))
|
|
344
|
+
fatal("not_negotiated");
|
|
284
345
|
const want = CONTROL_ARITY.get(v);
|
|
285
346
|
if (want === undefined || arg !== want)
|
|
286
347
|
fail("cbor_malformed");
|
|
@@ -347,20 +408,30 @@ function headerMap(h) {
|
|
|
347
408
|
out = out.concat(text(k), text(v));
|
|
348
409
|
return out;
|
|
349
410
|
}
|
|
350
|
-
/**
|
|
351
|
-
|
|
411
|
+
/**
|
|
412
|
+
* Canonical key order: viewerLabel, method, path, headers, [body], [upgrade].
|
|
413
|
+
* The client never sends an OPEN; this is the mirror of the edge's encoder for
|
|
414
|
+
* the fixture round trip, and it refuses an element outside `caps` the same way.
|
|
415
|
+
*/
|
|
416
|
+
export function encodeOpen(p, caps = BASE_CAPS) {
|
|
352
417
|
if (parseViewerLabel(p.viewerLabel) === null)
|
|
353
418
|
fail("cbor_viewer");
|
|
354
419
|
if (p.path.length > MAX_PATH_LEN)
|
|
355
420
|
fail("cbor_path_len");
|
|
356
421
|
if (p.headers.length > MAX_HEADERS)
|
|
357
422
|
fail("cbor_headers");
|
|
358
|
-
|
|
423
|
+
if (p.body !== undefined && !caps.has("open-body"))
|
|
424
|
+
fatal("not_negotiated");
|
|
425
|
+
if (p.body !== undefined && p.upgrade)
|
|
426
|
+
fatal("open_body_with_upgrade");
|
|
427
|
+
const n = 4 + (p.body !== undefined ? 1 : 0) + (p.upgrade ? 1 : 0);
|
|
359
428
|
let out = head(5, n);
|
|
360
429
|
out = out.concat(text("viewerLabel"), text(p.viewerLabel));
|
|
361
430
|
out = out.concat(text("method"), text(p.method));
|
|
362
431
|
out = out.concat(text("path"), text(p.path));
|
|
363
432
|
out = out.concat(text("headers"), headerMap(p.headers));
|
|
433
|
+
if (p.body !== undefined)
|
|
434
|
+
out = out.concat(text("body"), [p.body ? 0xf5 : 0xf4]);
|
|
364
435
|
if (p.upgrade)
|
|
365
436
|
out = out.concat(text("upgrade"), text(p.upgrade));
|
|
366
437
|
const b = new Uint8Array(out);
|
|
@@ -382,10 +453,13 @@ export function encodeHead(p) {
|
|
|
382
453
|
fail("open_payload_too_large");
|
|
383
454
|
return b;
|
|
384
455
|
}
|
|
385
|
-
export function encodeControl(kind, viewerLabel) {
|
|
456
|
+
export function encodeControl(kind, viewerLabel, caps = BASE_CAPS) {
|
|
386
457
|
const arity = CONTROL_ARITY.get(kind);
|
|
387
458
|
if (arity === undefined)
|
|
388
459
|
fail("cbor_malformed");
|
|
460
|
+
const needs = NEGOTIATED_KIND.get(kind);
|
|
461
|
+
if (needs !== undefined && !caps.has(needs))
|
|
462
|
+
fatal("not_negotiated");
|
|
389
463
|
if (kind === "viewer_gone") {
|
|
390
464
|
if (viewerLabel === undefined || parseViewerLabel(viewerLabel) === null) {
|
|
391
465
|
fail("cbor_viewer");
|
package/dist/tunnel/client.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { WebSocket } from "ws";
|
|
16
16
|
import { encodeCredit } from "./frame.js";
|
|
17
|
-
import { type ControlKind, type UAClass } from "./cbor.js";
|
|
17
|
+
import { type CapSet, type ControlKind, type OpenPayload, type UAClass } from "./cbor.js";
|
|
18
18
|
import { type ForwardTarget } from "./forwardTarget.js";
|
|
19
19
|
/**
|
|
20
20
|
* VIEWER_STAMP_HEADER carries the VALIDATED label to the creator's own local
|
|
@@ -59,6 +59,15 @@ export declare const RESUME_HEADER = "x-vincentt-resume";
|
|
|
59
59
|
export declare const SESSION_HEADER = "x-vincentt-session";
|
|
60
60
|
/** The Authorization header, named once so the never-send-both rule cites it. */
|
|
61
61
|
export declare const AUTHORIZATION_HEADER = "authorization";
|
|
62
|
+
/**
|
|
63
|
+
* The version refusal on a dial: `410` with these headers (f3 §2.2.3). 410 is
|
|
64
|
+
* in a released client's stop set; only a client that reads the reason header
|
|
65
|
+
* knows it means "upgrade".
|
|
66
|
+
*/
|
|
67
|
+
export declare const REFUSAL_HEADER = "x-vincentt-refusal";
|
|
68
|
+
export declare const CLI_MINIMUM_HEADER = "x-vincentt-cli-minimum";
|
|
69
|
+
export declare const CLI_DECLARED_HEADER = "x-vincentt-cli-declared";
|
|
70
|
+
export declare const UPGRADE_REQUIRED_REFUSAL = "cli_upgrade_required";
|
|
62
71
|
/**
|
|
63
72
|
* RESUME_TOKEN_LEN is base64url(32 random bytes), unpadded. It mirrors
|
|
64
73
|
* `resumeTokenLen` in the Go edge so the two ends of this wire state the same
|
|
@@ -113,8 +122,17 @@ export interface TunnelClientOptions {
|
|
|
113
122
|
* client nothing, so a phone refreshing read as an arriving crowd.
|
|
114
123
|
*/
|
|
115
124
|
readonly onViewerGone?: (label: string) => void;
|
|
116
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* onControl carries revoke/expiring/superseded/authz_expired so the CLI prints
|
|
127
|
+
* the right ending. `authz_expired` arrives only on a connection that agreed
|
|
128
|
+
* it, and the client has already stopped redialling when it fires.
|
|
129
|
+
*/
|
|
117
130
|
readonly onControl?: (kind: ControlKind) => void;
|
|
131
|
+
/**
|
|
132
|
+
* The edge refused the dial for this CLI's version. Raw header values: the
|
|
133
|
+
* caller validates them. The client makes no further dial after this.
|
|
134
|
+
*/
|
|
135
|
+
readonly onRefused?: (reason: "upgrade_required", minimum: string | undefined, declared: string | undefined) => void;
|
|
118
136
|
/** onOriginError is the creator-facing detail the 502 page must never carry. */
|
|
119
137
|
readonly onOriginError?: (label: string, code: string) => void;
|
|
120
138
|
readonly onStateChange?: (state: TunnelState) => void;
|
|
@@ -143,6 +161,27 @@ export interface TunnelClientOptions {
|
|
|
143
161
|
readonly connect?: (url: string, headers: Record<string, string>) => WebSocket;
|
|
144
162
|
}
|
|
145
163
|
export type TunnelState = "connecting" | "live" | "reconnecting" | "closed";
|
|
164
|
+
/**
|
|
165
|
+
* What to do with a request's body. `reset` is a disagreement a viewer can
|
|
166
|
+
* cause, so it ends that stream and never the tunnel.
|
|
167
|
+
*/
|
|
168
|
+
export type BodyPlan = {
|
|
169
|
+
kind: "none";
|
|
170
|
+
} | {
|
|
171
|
+
kind: "declared";
|
|
172
|
+
length: number;
|
|
173
|
+
} | {
|
|
174
|
+
kind: "chunked";
|
|
175
|
+
} | {
|
|
176
|
+
kind: "reset";
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* With `open-body` agreed, the edge's `body` decides and a content-length must
|
|
180
|
+
* agree with it (the edge writes at most one, positive). Without it, the base
|
|
181
|
+
* rule: a positive content-length streams, anything else ends at OPEN. An
|
|
182
|
+
* upgrade has no body whatever it declares.
|
|
183
|
+
*/
|
|
184
|
+
export declare function bodyPlan(open: OpenPayload, caps: CapSet): BodyPlan;
|
|
146
185
|
export declare class TunnelClient {
|
|
147
186
|
private readonly opts;
|
|
148
187
|
private ws;
|
|
@@ -155,6 +194,8 @@ export declare class TunnelClient {
|
|
|
155
194
|
* is a credential in a log.
|
|
156
195
|
*/
|
|
157
196
|
private resumeToken;
|
|
197
|
+
/** This connection's agreed set. Fixed at its 101, reset on every dial. */
|
|
198
|
+
private caps;
|
|
158
199
|
private attempt;
|
|
159
200
|
private closed;
|
|
160
201
|
private state;
|
|
@@ -190,6 +231,11 @@ export declare class TunnelClient {
|
|
|
190
231
|
private dialHeaders;
|
|
191
232
|
private open;
|
|
192
233
|
private onClosed;
|
|
234
|
+
/**
|
|
235
|
+
* Close this connection for a violation the edge alone could cause. The
|
|
236
|
+
* ordinary close path then decides whether to redial.
|
|
237
|
+
*/
|
|
238
|
+
private protocolViolation;
|
|
193
239
|
private startPing;
|
|
194
240
|
private stopPing;
|
|
195
241
|
private send;
|