@cortexkit/antigravity-auth-core 2.2.1 → 3.0.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/README.md +24 -2
- package/dist/abort-forwarding.d.ts +3 -0
- package/dist/abort-forwarding.d.ts.map +1 -0
- package/dist/abort-forwarding.js +19 -0
- package/dist/abort-forwarding.js.map +1 -0
- package/dist/account-identity.d.ts +52 -0
- package/dist/account-identity.d.ts.map +1 -0
- package/dist/account-identity.js +66 -0
- package/dist/account-identity.js.map +1 -0
- package/dist/account-manager.d.ts +228 -43
- package/dist/account-manager.d.ts.map +1 -1
- package/dist/account-manager.js +942 -903
- package/dist/account-manager.js.map +1 -1
- package/dist/account-migration.d.ts +309 -0
- package/dist/account-migration.d.ts.map +1 -0
- package/dist/account-migration.js +2282 -0
- package/dist/account-migration.js.map +1 -0
- package/dist/account-repository-codecs.d.ts +82 -0
- package/dist/account-repository-codecs.d.ts.map +1 -0
- package/dist/account-repository-codecs.js +609 -0
- package/dist/account-repository-codecs.js.map +1 -0
- package/dist/account-repository-types.d.ts +831 -0
- package/dist/account-repository-types.d.ts.map +1 -0
- package/dist/account-repository-types.js +137 -0
- package/dist/account-repository-types.js.map +1 -0
- package/dist/account-repository.d.ts +442 -0
- package/dist/account-repository.d.ts.map +1 -0
- package/dist/account-repository.js +2253 -0
- package/dist/account-repository.js.map +1 -0
- package/dist/account-selector.d.ts +470 -0
- package/dist/account-selector.d.ts.map +1 -0
- package/dist/account-selector.js +1383 -0
- package/dist/account-selector.js.map +1 -0
- package/dist/account-storage.d.ts.map +1 -1
- package/dist/account-storage.js +5 -0
- package/dist/account-storage.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +21 -35
- package/dist/agy-transport.js.map +1 -1
- package/dist/antigravity-command-menu.d.ts +171 -0
- package/dist/antigravity-command-menu.d.ts.map +1 -0
- package/dist/antigravity-command-menu.js +523 -0
- package/dist/antigravity-command-menu.js.map +1 -0
- package/dist/atomic-write.d.ts +18 -1
- package/dist/atomic-write.d.ts.map +1 -1
- package/dist/atomic-write.js +15 -3
- package/dist/atomic-write.js.map +1 -1
- package/dist/common-auth-embedded/NOTICE.txt +21 -0
- package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
- package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
- package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
- package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
- package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
- package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
- package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
- package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
- package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
- package/dist/common-auth-embedded/auth-menu/index.js +12 -0
- package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
- package/dist/common-auth-embedded/auth-menu/login.js +83 -0
- package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
- package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
- package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
- package/dist/common-auth-embedded/auth-menu/select.js +159 -0
- package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
- package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
- package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
- package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
- package/dist/common-auth-embedded/claustrum/custody.js +354 -0
- package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
- package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
- package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
- package/dist/common-auth-embedded/claustrum/errors.js +8 -0
- package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
- package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
- package/dist/common-auth-embedded/claustrum/index.js +22 -0
- package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
- package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
- package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
- package/dist/common-auth-embedded/claustrum/roster.js +536 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
- package/dist/common-auth-embedded/commands/builtins.js +534 -0
- package/dist/common-auth-embedded/commands/index.d.ts +10 -0
- package/dist/common-auth-embedded/commands/index.js +5 -0
- package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
- package/dist/common-auth-embedded/commands/menu.js +300 -0
- package/dist/common-auth-embedded/commands/model.d.ts +238 -0
- package/dist/common-auth-embedded/commands/model.js +16 -0
- package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
- package/dist/common-auth-embedded/commands/pi.js +206 -0
- package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
- package/dist/common-auth-embedded/commands/seam.js +301 -0
- package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
- package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
- package/dist/common-auth-embedded/fs/index.d.ts +7 -0
- package/dist/common-auth-embedded/fs/index.js +6 -0
- package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
- package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
- package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/with-lock.js +48 -0
- package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
- package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
- package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
- package/dist/common-auth-embedded/logger/engine.js +246 -0
- package/dist/common-auth-embedded/logger/index.d.ts +6 -0
- package/dist/common-auth-embedded/logger/index.js +3 -0
- package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
- package/dist/common-auth-embedded/logger/redact.js +83 -0
- package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
- package/dist/common-auth-embedded/pi-slot/index.js +2 -0
- package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
- package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
- package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
- package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
- package/dist/common-auth-embedded/quota/codec.js +12 -0
- package/dist/common-auth-embedded/quota/format.d.ts +46 -0
- package/dist/common-auth-embedded/quota/format.js +138 -0
- package/dist/common-auth-embedded/quota/index.d.ts +10 -0
- package/dist/common-auth-embedded/quota/index.js +5 -0
- package/dist/common-auth-embedded/quota/map.d.ts +63 -0
- package/dist/common-auth-embedded/quota/map.js +105 -0
- package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
- package/dist/common-auth-embedded/quota/merge.js +162 -0
- package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
- package/dist/common-auth-embedded/quota/projection.js +134 -0
- package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
- package/dist/common-auth-embedded/routing/admission.js +142 -0
- package/dist/common-auth-embedded/routing/index.d.ts +8 -0
- package/dist/common-auth-embedded/routing/index.js +4 -0
- package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
- package/dist/common-auth-embedded/routing/ordered.js +58 -0
- package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
- package/dist/common-auth-embedded/routing/pins.js +34 -0
- package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
- package/dist/common-auth-embedded/routing/sticky.js +339 -0
- package/dist/common-auth-embedded/source-output.json +1090 -0
- package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
- package/dist/common-auth-embedded/store/attribution.js +60 -0
- package/dist/common-auth-embedded/store/errors.d.ts +55 -0
- package/dist/common-auth-embedded/store/errors.js +38 -0
- package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
- package/dist/common-auth-embedded/store/hooks.js +34 -0
- package/dist/common-auth-embedded/store/identity.d.ts +39 -0
- package/dist/common-auth-embedded/store/identity.js +79 -0
- package/dist/common-auth-embedded/store/index.d.ts +21 -0
- package/dist/common-auth-embedded/store/index.js +7 -0
- package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
- package/dist/common-auth-embedded/store/mutate.js +500 -0
- package/dist/common-auth-embedded/store/pool.d.ts +196 -0
- package/dist/common-auth-embedded/store/pool.js +120 -0
- package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
- package/dist/common-auth-embedded/store/provider-state.js +226 -0
- package/dist/common-auth-embedded/store/publication.d.ts +47 -0
- package/dist/common-auth-embedded/store/publication.js +304 -0
- package/dist/common-auth-embedded/store/pull.d.ts +36 -0
- package/dist/common-auth-embedded/store/pull.js +143 -0
- package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
- package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
- package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
- package/dist/common-auth-embedded/store/refresh.js +225 -0
- package/dist/common-auth-embedded/store/reserved.js +13 -0
- package/dist/common-auth-embedded/store/rows.d.ts +313 -0
- package/dist/common-auth-embedded/store/rows.js +1258 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
- package/dist/common-auth-embedded/store/runtime.js +68 -0
- package/dist/common-auth-embedded/store/schema.d.ts +443 -0
- package/dist/common-auth-embedded/store/schema.js +754 -0
- package/dist/common-auth-embedded/store/settings.d.ts +63 -0
- package/dist/common-auth-embedded/store/settings.js +120 -0
- package/dist/common-auth-embedded/store/torn.d.ts +78 -0
- package/dist/common-auth-embedded/store/torn.js +294 -0
- package/dist/common-auth-embedded/util/any-signal.js +42 -0
- package/dist/common-auth-runtime.d.ts +73 -0
- package/dist/common-auth-runtime.d.ts.map +1 -0
- package/dist/common-auth-runtime.js +65 -0
- package/dist/common-auth-runtime.js.map +1 -0
- package/dist/credential-redaction.d.ts +13 -0
- package/dist/credential-redaction.d.ts.map +1 -0
- package/dist/credential-redaction.js +230 -0
- package/dist/credential-redaction.js.map +1 -0
- package/dist/fetch-timeout.d.ts +4 -2
- package/dist/fetch-timeout.d.ts.map +1 -1
- package/dist/fetch-timeout.js +121 -14
- package/dist/fetch-timeout.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/quota-manager.d.ts.map +1 -1
- package/dist/quota-manager.js +35 -10
- package/dist/quota-manager.js.map +1 -1
- package/dist/quota-presentation.d.ts +10 -0
- package/dist/quota-presentation.d.ts.map +1 -0
- package/dist/quota-presentation.js +60 -0
- package/dist/quota-presentation.js.map +1 -0
- package/dist/quota-types.d.ts +2 -0
- package/dist/quota-types.d.ts.map +1 -1
- package/dist/rotation.d.ts +8 -0
- package/dist/rotation.d.ts.map +1 -1
- package/dist/rotation.js +24 -0
- package/dist/rotation.js.map +1 -1
- package/dist/transform/claude.d.ts +7 -3
- package/dist/transform/claude.d.ts.map +1 -1
- package/dist/transform/claude.js +6 -2
- package/dist/transform/claude.js.map +1 -1
- package/dist/transform/types.d.ts +1 -1
- package/dist/transform/types.d.ts.map +1 -1
- package/dist/vault-account-source.d.ts +183 -0
- package/dist/vault-account-source.d.ts.map +1 -0
- package/dist/vault-account-source.js +296 -0
- package/dist/vault-account-source.js.map +1 -0
- package/dist/vault-enrollment-authority.d.ts +16 -0
- package/dist/vault-enrollment-authority.d.ts.map +1 -0
- package/dist/vault-enrollment-authority.js +19 -0
- package/dist/vault-enrollment-authority.js.map +1 -0
- package/dist/vault-family.d.ts +11 -0
- package/dist/vault-family.d.ts.map +1 -0
- package/dist/vault-family.js +24 -0
- package/dist/vault-family.js.map +1 -0
- package/dist/vault-provider-state.d.ts +162 -0
- package/dist/vault-provider-state.d.ts.map +1 -0
- package/dist/vault-provider-state.js +422 -0
- package/dist/vault-provider-state.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { ANSI, parseKeys, truncateAnsi } from './ansi.js';
|
|
2
|
+
import { isInteractive } from './terminal.js';
|
|
3
|
+
/** How long a lone Escape byte waits for the rest of an arrow-key sequence. */
|
|
4
|
+
const ESCAPE_TIMEOUT_MS = 50;
|
|
5
|
+
function colorCode(color) {
|
|
6
|
+
if (color === 'red')
|
|
7
|
+
return ANSI.red;
|
|
8
|
+
if (color === 'cyan')
|
|
9
|
+
return ANSI.cyan;
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render a bounded, keyboard-only terminal selector without a prompt
|
|
14
|
+
* dependency. Resolves the chosen value, or null on Escape, Ctrl-C, a signal,
|
|
15
|
+
* or a terminal that refuses raw mode.
|
|
16
|
+
*/
|
|
17
|
+
export async function select(terminal, items, options) {
|
|
18
|
+
if (!isInteractive(terminal))
|
|
19
|
+
throw new Error('Interactive select requires a TTY terminal');
|
|
20
|
+
if (items.length === 0)
|
|
21
|
+
throw new Error('No menu items provided');
|
|
22
|
+
const { input: stdin, output: stdout, signals } = terminal;
|
|
23
|
+
let cursor = 0;
|
|
24
|
+
let escapeTimeout = null;
|
|
25
|
+
let cleaned = false;
|
|
26
|
+
let renderedLines = 0;
|
|
27
|
+
const render = () => {
|
|
28
|
+
const columns = stdout.columns ?? 80;
|
|
29
|
+
const rows = stdout.rows ?? 24;
|
|
30
|
+
const previousLines = renderedLines;
|
|
31
|
+
if (options.clearScreen) {
|
|
32
|
+
stdout.write(ANSI.clearScreen + ANSI.moveTo(1, 1));
|
|
33
|
+
}
|
|
34
|
+
else if (previousLines > 0) {
|
|
35
|
+
stdout.write(ANSI.up(previousLines));
|
|
36
|
+
}
|
|
37
|
+
let lines = 0;
|
|
38
|
+
const writeLine = (line) => {
|
|
39
|
+
stdout.write(`${ANSI.clearLine}${line}\n`);
|
|
40
|
+
lines += 1;
|
|
41
|
+
};
|
|
42
|
+
const extraLines = options.lines ?? [];
|
|
43
|
+
const headerLines = 1 +
|
|
44
|
+
(options.subtitle ? 3 : 0) +
|
|
45
|
+
extraLines.length +
|
|
46
|
+
(extraLines.length ? 1 : 0);
|
|
47
|
+
const maxVisible = Math.max(1, Math.min(items.length, rows - headerLines - 2 - 1));
|
|
48
|
+
const windowStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), Math.max(0, items.length - maxVisible)));
|
|
49
|
+
const visibleItems = items.slice(windowStart, windowStart + maxVisible);
|
|
50
|
+
writeLine(`${ANSI.dim}┌ ${ANSI.reset}${truncateAnsi(options.message, Math.max(1, columns - 4))}`);
|
|
51
|
+
if (options.subtitle) {
|
|
52
|
+
writeLine(`${ANSI.dim}│${ANSI.reset}`);
|
|
53
|
+
writeLine(`${ANSI.cyan}◆${ANSI.reset} ${truncateAnsi(options.subtitle, Math.max(1, columns - 4))}`);
|
|
54
|
+
writeLine('');
|
|
55
|
+
}
|
|
56
|
+
for (const line of extraLines) {
|
|
57
|
+
writeLine(`${ANSI.cyan}│${ANSI.reset} ${truncateAnsi(line, Math.max(1, columns - 4))}`);
|
|
58
|
+
}
|
|
59
|
+
if (extraLines.length)
|
|
60
|
+
writeLine(`${ANSI.cyan}│${ANSI.reset}`);
|
|
61
|
+
for (let offset = 0; offset < visibleItems.length; offset++) {
|
|
62
|
+
const item = visibleItems[offset];
|
|
63
|
+
if (!item)
|
|
64
|
+
continue;
|
|
65
|
+
const selected = windowStart + offset === cursor;
|
|
66
|
+
const color = colorCode(item.color);
|
|
67
|
+
let label = color
|
|
68
|
+
? `${selected ? '' : ANSI.dim}${color}${item.label}${ANSI.reset}`
|
|
69
|
+
: selected
|
|
70
|
+
? item.label
|
|
71
|
+
: `${ANSI.dim}${item.label}${ANSI.reset}`;
|
|
72
|
+
if (item.hint)
|
|
73
|
+
label += ` ${ANSI.dim}${item.hint}${ANSI.reset}`;
|
|
74
|
+
label = truncateAnsi(label, Math.max(1, columns - 8));
|
|
75
|
+
writeLine(selected
|
|
76
|
+
? `${ANSI.cyan}│${ANSI.reset} ${ANSI.green}●${ANSI.reset} ${label}`
|
|
77
|
+
: `${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}○${ANSI.reset} ${label}`);
|
|
78
|
+
}
|
|
79
|
+
const windowHint = visibleItems.length < items.length
|
|
80
|
+
? ` (${windowStart + 1}-${windowStart + visibleItems.length}/${items.length})`
|
|
81
|
+
: '';
|
|
82
|
+
writeLine(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${truncateAnsi(`Up/Down to select | Enter: confirm | Esc: back${windowHint}`, Math.max(1, columns - 6))}${ANSI.reset}`);
|
|
83
|
+
writeLine(`${ANSI.cyan}└${ANSI.reset}`);
|
|
84
|
+
for (let extra = lines; extra < previousLines; extra++)
|
|
85
|
+
writeLine('');
|
|
86
|
+
renderedLines = lines;
|
|
87
|
+
};
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
90
|
+
const cleanup = () => {
|
|
91
|
+
if (cleaned)
|
|
92
|
+
return;
|
|
93
|
+
cleaned = true;
|
|
94
|
+
if (escapeTimeout)
|
|
95
|
+
clearTimeout(escapeTimeout);
|
|
96
|
+
stdin.removeListener('data', onKey);
|
|
97
|
+
try {
|
|
98
|
+
stdin.setRawMode(wasRaw);
|
|
99
|
+
stdin.pause();
|
|
100
|
+
stdout.write(ANSI.show);
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
signals?.removeListener('SIGINT', onSignal);
|
|
104
|
+
signals?.removeListener('SIGTERM', onSignal);
|
|
105
|
+
};
|
|
106
|
+
const finish = (value) => {
|
|
107
|
+
cleanup();
|
|
108
|
+
resolve(value);
|
|
109
|
+
};
|
|
110
|
+
const onSignal = () => finish(null);
|
|
111
|
+
const onKey = (data) => {
|
|
112
|
+
for (const key of parseKeys(data)) {
|
|
113
|
+
if (cleaned)
|
|
114
|
+
break;
|
|
115
|
+
if (escapeTimeout) {
|
|
116
|
+
clearTimeout(escapeTimeout);
|
|
117
|
+
escapeTimeout = null;
|
|
118
|
+
}
|
|
119
|
+
switch (key) {
|
|
120
|
+
case 'up':
|
|
121
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
122
|
+
render();
|
|
123
|
+
break;
|
|
124
|
+
case 'down':
|
|
125
|
+
cursor = (cursor + 1) % items.length;
|
|
126
|
+
render();
|
|
127
|
+
break;
|
|
128
|
+
case 'enter':
|
|
129
|
+
finish(items[cursor]?.value ?? null);
|
|
130
|
+
break;
|
|
131
|
+
case 'escape':
|
|
132
|
+
finish(null);
|
|
133
|
+
break;
|
|
134
|
+
case 'escape-start':
|
|
135
|
+
// A bare Escape byte is also the start of an arrow-key sequence
|
|
136
|
+
// that may arrive split across reads; only a lone one cancels.
|
|
137
|
+
escapeTimeout = setTimeout(() => finish(null), ESCAPE_TIMEOUT_MS);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
signals?.once('SIGINT', onSignal);
|
|
145
|
+
signals?.once('SIGTERM', onSignal);
|
|
146
|
+
try {
|
|
147
|
+
stdin.setRawMode(true);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
cleanup();
|
|
151
|
+
resolve(null);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
stdin.resume();
|
|
155
|
+
stdout.write(ANSI.hide);
|
|
156
|
+
render();
|
|
157
|
+
stdin.on('data', onKey);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal the menu draws on and reads keys from. The process's own
|
|
3
|
+
* stdin and stdout satisfy it; tests pass a scripted stand-in, so the full
|
|
4
|
+
* key-in, frame-out path runs without a real TTY.
|
|
5
|
+
*/
|
|
6
|
+
export interface MenuTerminal {
|
|
7
|
+
input: MenuInput;
|
|
8
|
+
output: MenuOutput;
|
|
9
|
+
/**
|
|
10
|
+
* Where SIGINT and SIGTERM are heard while a selector holds raw mode, so a
|
|
11
|
+
* kill restores the terminal. Defaults to nothing: raw mode turns Ctrl-C
|
|
12
|
+
* into a key the selector already handles.
|
|
13
|
+
*/
|
|
14
|
+
signals?: MenuSignals;
|
|
15
|
+
}
|
|
16
|
+
export interface MenuInput {
|
|
17
|
+
/** True only on an interactive terminal; anything else gets a plain list. */
|
|
18
|
+
readonly isTTY?: boolean;
|
|
19
|
+
readonly isRaw?: boolean;
|
|
20
|
+
setRawMode(mode: boolean): unknown;
|
|
21
|
+
resume(): unknown;
|
|
22
|
+
pause(): unknown;
|
|
23
|
+
on(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
24
|
+
removeListener(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface MenuOutput {
|
|
27
|
+
write(text: string): unknown;
|
|
28
|
+
readonly columns?: number;
|
|
29
|
+
readonly rows?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface MenuSignals {
|
|
32
|
+
once(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
|
|
33
|
+
removeListener(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
|
|
34
|
+
}
|
|
35
|
+
/** The current process's terminal, with its signals. */
|
|
36
|
+
export declare function processTerminal(): MenuTerminal;
|
|
37
|
+
/**
|
|
38
|
+
* Whether keys can be read one at a time. Only the input is checked, as the
|
|
39
|
+
* plugins' menus always did: output piped to a file still gets the menu.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isInteractive(terminal: MenuTerminal): boolean;
|
|
42
|
+
/** Writes one line of plain output below the menu. */
|
|
43
|
+
export declare function printLine(terminal: MenuTerminal, line?: string): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The current process's terminal, with its signals. */
|
|
2
|
+
export function processTerminal() {
|
|
3
|
+
return {
|
|
4
|
+
input: process.stdin,
|
|
5
|
+
output: process.stdout,
|
|
6
|
+
signals: process,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Whether keys can be read one at a time. Only the input is checked, as the
|
|
11
|
+
* plugins' menus always did: output piped to a file still gets the menu.
|
|
12
|
+
*/
|
|
13
|
+
export function isInteractive(terminal) {
|
|
14
|
+
return terminal.input.isTTY === true;
|
|
15
|
+
}
|
|
16
|
+
/** Writes one line of plain output below the menu. */
|
|
17
|
+
export function printLine(terminal, line = '') {
|
|
18
|
+
terminal.output.write(`${line}\n`);
|
|
19
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ClaustrumReporterSource } from '@cortexkit/claustrum-client';
|
|
2
|
+
import type { QuotaObservation } from '../quota/index.js';
|
|
3
|
+
import type { RoutingRow } from '../routing/index.js';
|
|
4
|
+
import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser } from './custody.js';
|
|
5
|
+
import { type ClaustrumLogger } from './errors.js';
|
|
6
|
+
import { type AccountMapper, type QuotaReceipt, type VaultRosterFile } from './roster.js';
|
|
7
|
+
export interface ClaustrumConsumerOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The roster file: one row per vault account (route id, credential id,
|
|
10
|
+
* account identity, state, quota), plus the accounts the user declined. It
|
|
11
|
+
* never holds a token.
|
|
12
|
+
*/
|
|
13
|
+
rosterPath: string;
|
|
14
|
+
/** This host's enrollment token, written by setup. */
|
|
15
|
+
tokenPath: string;
|
|
16
|
+
family: ClaustrumFamily;
|
|
17
|
+
connect: () => Promise<ClaustrumScopedClient>;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the plugin currently takes its accounts from the vault (custody
|
|
20
|
+
* mode) rather than from local logins. Checked before every discovery,
|
|
21
|
+
* commit and dispatch; false closes the vault connection and refuses sends.
|
|
22
|
+
*/
|
|
23
|
+
isCustodyActive?: () => boolean | Promise<boolean>;
|
|
24
|
+
/** Ids of the plugin's local pool rows; no vault route id may equal one. */
|
|
25
|
+
reservedRouteIds?: () => Iterable<string>;
|
|
26
|
+
routePrefix?: string;
|
|
27
|
+
mapAccount?: AccountMapper;
|
|
28
|
+
parseIdentity?: IdentityParser;
|
|
29
|
+
/**
|
|
30
|
+
* Issue a receipt only when the vault's served reply itself names the
|
|
31
|
+
* credential id and the roster's account identity (see
|
|
32
|
+
* `ClaustrumScopedCustody`). Set it for providers whose tokens do not reveal
|
|
33
|
+
* their account; leave it unset to accept a reply that asserts no identity.
|
|
34
|
+
*/
|
|
35
|
+
requireAssertion?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Fired once per change of the vault's view cursor, including the first
|
|
38
|
+
* roster. A poll that sees the same view does not fire, and neither does a
|
|
39
|
+
* routine token refresh, which never moves the view.
|
|
40
|
+
*/
|
|
41
|
+
onRoster?: (roster: VaultRosterFile) => void;
|
|
42
|
+
onError?: (error: unknown) => void;
|
|
43
|
+
pollIntervalMs?: number;
|
|
44
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
45
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
46
|
+
now?: () => number;
|
|
47
|
+
logger?: ClaustrumLogger;
|
|
48
|
+
}
|
|
49
|
+
export interface SendOptions {
|
|
50
|
+
/**
|
|
51
|
+
* What kind of request this is (for example `model`, `quota`, `profile`),
|
|
52
|
+
* recorded in the 401 retry log line so a retry can be traced to its caller.
|
|
53
|
+
*/
|
|
54
|
+
site: string;
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
reporterSource?: ClaustrumReporterSource;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One plugin host's view of its vault accounts. It turns vault credentials
|
|
60
|
+
* into rows for `/routing`, authorizes each physical send with the vault, and
|
|
61
|
+
* reports a 401 against the exact record version that send used. Metadata
|
|
62
|
+
* (the connection and discovery) is shared and coalesced; credential reads
|
|
63
|
+
* never are. It never enrolls: a missing token is an error here, and setup
|
|
64
|
+
* is where enrollment happens.
|
|
65
|
+
*/
|
|
66
|
+
export declare class ClaustrumConsumer {
|
|
67
|
+
#private;
|
|
68
|
+
constructor(options: ClaustrumConsumerOptions);
|
|
69
|
+
/** The last committed roster this instance saw, without any I/O. */
|
|
70
|
+
snapshot(): VaultRosterFile | undefined;
|
|
71
|
+
/** Rows for `/routing`, from the last committed roster. */
|
|
72
|
+
routingRows(): RoutingRow[];
|
|
73
|
+
refresh(): Promise<VaultRosterFile | undefined>;
|
|
74
|
+
start(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Authorize one physical send on a vault route. The roster file is re-read
|
|
77
|
+
* first, so a decline committed by another process applies before the next
|
|
78
|
+
* poll, and a route whose credential or account changed is refused.
|
|
79
|
+
*/
|
|
80
|
+
authorize(routeId: string, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
|
|
81
|
+
reportFailure(attempt: ClaustrumScopedAttempt, status: number, source: ClaustrumReporterSource): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Send on a vault route. `dispatch` builds and sends the request with the
|
|
84
|
+
* receipt's token and may be called twice, so it must be able to rebuild
|
|
85
|
+
* its body. A 401 is retried once, and only when the vault now serves a new
|
|
86
|
+
* record version of the same credential and account; the final 401 is
|
|
87
|
+
* reported against the version that send actually used. Each call to
|
|
88
|
+
* `dispatch` gets its own receipt.
|
|
89
|
+
*/
|
|
90
|
+
send(routeId: string, dispatch: (attempt: ClaustrumScopedAttempt, signal?: AbortSignal) => Promise<Response>, options: SendOptions): Promise<Response>;
|
|
91
|
+
/** Decline a vault account: it stays listed and never routes until accepted. */
|
|
92
|
+
decline(routeId: string): Promise<void>;
|
|
93
|
+
accept(routeId: string): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Store a quota or profile observation for a vault route. The receipt the
|
|
96
|
+
* reading was taken with is required: the observation lands only while the
|
|
97
|
+
* route still holds the credential and account that receipt was served for
|
|
98
|
+
* (see `recordVaultQuota`), so a reading for a replaced account is dropped.
|
|
99
|
+
*/
|
|
100
|
+
recordQuota(routeId: string, observation: QuotaObservation, attempt: QuotaReceipt): Promise<boolean>;
|
|
101
|
+
close(): void;
|
|
102
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { createLogger } from '../logger/index.js';
|
|
2
|
+
import { combineAbortSignals } from '../util/any-signal.js';
|
|
3
|
+
import { ClaustrumScopedCustody, decideScopedRetryAfter401, } from './custody.js';
|
|
4
|
+
import { ClaustrumConsumerError } from './errors.js';
|
|
5
|
+
import { acceptVaultRoute, declineVaultRoute, readVaultRoster, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from './roster.js';
|
|
6
|
+
/**
|
|
7
|
+
* One plugin host's view of its vault accounts. It turns vault credentials
|
|
8
|
+
* into rows for `/routing`, authorizes each physical send with the vault, and
|
|
9
|
+
* reports a 401 against the exact record version that send used. Metadata
|
|
10
|
+
* (the connection and discovery) is shared and coalesced; credential reads
|
|
11
|
+
* never are. It never enrolls: a missing token is an error here, and setup
|
|
12
|
+
* is where enrollment happens.
|
|
13
|
+
*/
|
|
14
|
+
export class ClaustrumConsumer {
|
|
15
|
+
#options;
|
|
16
|
+
#shutdown = new AbortController();
|
|
17
|
+
#logger;
|
|
18
|
+
#custody;
|
|
19
|
+
#connecting;
|
|
20
|
+
#refreshing;
|
|
21
|
+
#roster;
|
|
22
|
+
#timer;
|
|
23
|
+
#started = false;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#options = options;
|
|
26
|
+
this.#logger = options.logger ?? createLogger('claustrum');
|
|
27
|
+
}
|
|
28
|
+
#assertOpen(signal) {
|
|
29
|
+
this.#shutdown.signal.throwIfAborted();
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
}
|
|
32
|
+
async #active() {
|
|
33
|
+
return (await this.#options.isCustodyActive?.()) ?? true;
|
|
34
|
+
}
|
|
35
|
+
#wait(pending, signal) {
|
|
36
|
+
const combined = combineAbortSignals([
|
|
37
|
+
this.#shutdown.signal,
|
|
38
|
+
...(signal ? [signal] : []),
|
|
39
|
+
]);
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
combined.signal.removeEventListener('abort', abort);
|
|
43
|
+
combined.dispose();
|
|
44
|
+
};
|
|
45
|
+
const abort = () => {
|
|
46
|
+
cleanup();
|
|
47
|
+
reject(combined.signal.reason);
|
|
48
|
+
};
|
|
49
|
+
combined.signal.addEventListener('abort', abort, { once: true });
|
|
50
|
+
// Observe the operation even after cancellation: shared metadata work may
|
|
51
|
+
// finish for other callers, and a late connection must still be closed.
|
|
52
|
+
pending.then((value) => {
|
|
53
|
+
cleanup();
|
|
54
|
+
if (combined.signal.aborted)
|
|
55
|
+
reject(combined.signal.reason);
|
|
56
|
+
else
|
|
57
|
+
resolve(value);
|
|
58
|
+
}, (error) => {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(error);
|
|
61
|
+
});
|
|
62
|
+
if (combined.signal.aborted)
|
|
63
|
+
abort();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async #getCustody() {
|
|
67
|
+
this.#assertOpen();
|
|
68
|
+
if (this.#custody)
|
|
69
|
+
return this.#custody;
|
|
70
|
+
if (!this.#connecting) {
|
|
71
|
+
this.#connecting = this.#options
|
|
72
|
+
.connect()
|
|
73
|
+
.then((client) => {
|
|
74
|
+
if (this.#shutdown.signal.aborted) {
|
|
75
|
+
client.close();
|
|
76
|
+
this.#assertOpen();
|
|
77
|
+
}
|
|
78
|
+
this.#custody = new ClaustrumScopedCustody({
|
|
79
|
+
client,
|
|
80
|
+
family: this.#options.family,
|
|
81
|
+
tokenPath: this.#options.tokenPath,
|
|
82
|
+
parseIdentity: this.#options.parseIdentity,
|
|
83
|
+
requireAssertion: this.#options.requireAssertion,
|
|
84
|
+
now: this.#options.now,
|
|
85
|
+
logger: this.#logger,
|
|
86
|
+
});
|
|
87
|
+
return this.#custody;
|
|
88
|
+
})
|
|
89
|
+
.finally(() => {
|
|
90
|
+
this.#connecting = undefined;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return this.#wait(this.#connecting);
|
|
94
|
+
}
|
|
95
|
+
/** The last committed roster this instance saw, without any I/O. */
|
|
96
|
+
snapshot() {
|
|
97
|
+
return this.#roster;
|
|
98
|
+
}
|
|
99
|
+
/** Rows for `/routing`, from the last committed roster. */
|
|
100
|
+
routingRows() {
|
|
101
|
+
return vaultRoutingRows(this.#roster);
|
|
102
|
+
}
|
|
103
|
+
refresh() {
|
|
104
|
+
this.#assertOpen();
|
|
105
|
+
if (this.#refreshing)
|
|
106
|
+
return this.#refreshing;
|
|
107
|
+
this.#refreshing = (async () => {
|
|
108
|
+
if (!(await this.#active())) {
|
|
109
|
+
this.#roster = undefined;
|
|
110
|
+
this.#custody?.close();
|
|
111
|
+
this.#custody = undefined;
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const custody = await this.#getCustody();
|
|
115
|
+
const roster = await refreshVaultRoster({
|
|
116
|
+
path: this.#options.rosterPath,
|
|
117
|
+
custody,
|
|
118
|
+
isActive: () => this.#active(),
|
|
119
|
+
signal: this.#shutdown.signal,
|
|
120
|
+
projection: () => ({
|
|
121
|
+
reservedRouteIds: new Set(this.#options.reservedRouteIds?.() ?? []),
|
|
122
|
+
routePrefix: this.#options.routePrefix,
|
|
123
|
+
mapAccount: this.#options.mapAccount,
|
|
124
|
+
now: (this.#options.now ?? Date.now)(),
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
this.#assertOpen();
|
|
128
|
+
const changed = roster?.view !== this.#roster?.view;
|
|
129
|
+
this.#roster = roster;
|
|
130
|
+
// Notify on view changes only: subscribers rewrite shared state on each
|
|
131
|
+
// notification, and the poll runs every few seconds.
|
|
132
|
+
if (roster && changed)
|
|
133
|
+
this.#options.onRoster?.(roster);
|
|
134
|
+
return roster;
|
|
135
|
+
})().finally(() => {
|
|
136
|
+
this.#refreshing = undefined;
|
|
137
|
+
});
|
|
138
|
+
return this.#refreshing;
|
|
139
|
+
}
|
|
140
|
+
start() {
|
|
141
|
+
if (this.#started)
|
|
142
|
+
return;
|
|
143
|
+
this.#assertOpen();
|
|
144
|
+
this.#started = true;
|
|
145
|
+
const tick = async () => {
|
|
146
|
+
try {
|
|
147
|
+
await this.refresh();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!this.#shutdown.signal.aborted)
|
|
151
|
+
this.#options.onError?.(error);
|
|
152
|
+
}
|
|
153
|
+
if (this.#shutdown.signal.aborted)
|
|
154
|
+
return;
|
|
155
|
+
const delay = this.#options.pollIntervalMs ?? 5_000;
|
|
156
|
+
if (delay <= 0)
|
|
157
|
+
return;
|
|
158
|
+
this.#timer = (this.#options.setTimeoutImpl ?? setTimeout)(() => {
|
|
159
|
+
this.#timer = undefined;
|
|
160
|
+
void tick();
|
|
161
|
+
}, delay);
|
|
162
|
+
this.#timer.unref?.();
|
|
163
|
+
};
|
|
164
|
+
void tick();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Authorize one physical send on a vault route. The roster file is re-read
|
|
168
|
+
* first, so a decline committed by another process applies before the next
|
|
169
|
+
* poll, and a route whose credential or account changed is refused.
|
|
170
|
+
*/
|
|
171
|
+
async authorize(routeId, signal) {
|
|
172
|
+
this.#assertOpen(signal);
|
|
173
|
+
const roster = this.#roster ?? (await this.#wait(this.refresh(), signal));
|
|
174
|
+
if (!roster || !(await this.#active()))
|
|
175
|
+
throw new ClaustrumConsumerError('not-active', 'Claustrum scoped custody is not active');
|
|
176
|
+
const current = await readVaultRoster(this.#options.rosterPath);
|
|
177
|
+
this.#assertOpen(signal);
|
|
178
|
+
const observed = roster.rows.find((row) => row.routeId === routeId);
|
|
179
|
+
const configured = current?.rows.find((row) => row.routeId === routeId);
|
|
180
|
+
if (configured && !configured.enabled)
|
|
181
|
+
throw new ClaustrumConsumerError('route-declined', 'Claustrum route is disabled');
|
|
182
|
+
if (!observed ||
|
|
183
|
+
!configured ||
|
|
184
|
+
configured.state !== 'active' ||
|
|
185
|
+
configured.credentialId !== observed.credentialId ||
|
|
186
|
+
configured.accountIdentity !== observed.accountIdentity)
|
|
187
|
+
throw new ClaustrumConsumerError('route-unavailable', 'Claustrum route is disabled, removed or changed');
|
|
188
|
+
const custody = await this.#wait(this.#getCustody(), signal);
|
|
189
|
+
const combined = combineAbortSignals([
|
|
190
|
+
this.#shutdown.signal,
|
|
191
|
+
...(signal ? [signal] : []),
|
|
192
|
+
]);
|
|
193
|
+
try {
|
|
194
|
+
return await custody.authorize({
|
|
195
|
+
credentialId: configured.credentialId,
|
|
196
|
+
credentialType: configured.credentialType,
|
|
197
|
+
...(configured.accountIdentity !== undefined && {
|
|
198
|
+
accountIdentity: configured.accountIdentity,
|
|
199
|
+
}),
|
|
200
|
+
}, combined.signal);
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
combined.dispose();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async reportFailure(attempt, status, source) {
|
|
207
|
+
this.#assertOpen();
|
|
208
|
+
if (!this.#custody)
|
|
209
|
+
throw new ClaustrumConsumerError('no-receipt', 'Claustrum dispatch receipt has no active owner');
|
|
210
|
+
await this.#custody.reportFailure(attempt, status, source);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Send on a vault route. `dispatch` builds and sends the request with the
|
|
214
|
+
* receipt's token and may be called twice, so it must be able to rebuild
|
|
215
|
+
* its body. A 401 is retried once, and only when the vault now serves a new
|
|
216
|
+
* record version of the same credential and account; the final 401 is
|
|
217
|
+
* reported against the version that send actually used. Each call to
|
|
218
|
+
* `dispatch` gets its own receipt.
|
|
219
|
+
*/
|
|
220
|
+
async send(routeId, dispatch, options) {
|
|
221
|
+
const signal = options.signal;
|
|
222
|
+
let served = await this.authorize(routeId, signal);
|
|
223
|
+
let response = await dispatch(served, signal);
|
|
224
|
+
if (response.status === 401 && !signal?.aborted) {
|
|
225
|
+
let current;
|
|
226
|
+
try {
|
|
227
|
+
current = await this.authorize(routeId, signal);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Re-authorization failed: keep this 401 and report it against the
|
|
231
|
+
// receipt (served record version) this request was sent with.
|
|
232
|
+
}
|
|
233
|
+
if (decideScopedRetryAfter401(options.site, served, current, this.#logger)) {
|
|
234
|
+
await response.body?.cancel().catch(() => { });
|
|
235
|
+
served = current;
|
|
236
|
+
response = await dispatch(current, signal);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (response.status === 401) {
|
|
240
|
+
await this.reportFailure(served, 401, options.reporterSource ?? 'direct').catch((error) => this.#options.onError?.(error));
|
|
241
|
+
// After the report the vault may mark the account as needing a new
|
|
242
|
+
// login; refresh now so routing drops it without waiting for the next poll. Deferred so a consumer closed in
|
|
243
|
+
// the meantime reports to onError instead of throwing from this send.
|
|
244
|
+
Promise.resolve()
|
|
245
|
+
.then(() => this.refresh())
|
|
246
|
+
.catch((error) => {
|
|
247
|
+
if (!this.#shutdown.signal.aborted)
|
|
248
|
+
this.#options.onError?.(error);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return response;
|
|
252
|
+
}
|
|
253
|
+
/** Decline a vault account: it stays listed and never routes until accepted. */
|
|
254
|
+
async decline(routeId) {
|
|
255
|
+
this.#assertOpen();
|
|
256
|
+
await declineVaultRoute(this.#options.rosterPath, routeId);
|
|
257
|
+
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
258
|
+
}
|
|
259
|
+
async accept(routeId) {
|
|
260
|
+
this.#assertOpen();
|
|
261
|
+
await acceptVaultRoute(this.#options.rosterPath, routeId);
|
|
262
|
+
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Store a quota or profile observation for a vault route. The receipt the
|
|
266
|
+
* reading was taken with is required: the observation lands only while the
|
|
267
|
+
* route still holds the credential and account that receipt was served for
|
|
268
|
+
* (see `recordVaultQuota`), so a reading for a replaced account is dropped.
|
|
269
|
+
*/
|
|
270
|
+
async recordQuota(routeId, observation, attempt) {
|
|
271
|
+
this.#assertOpen();
|
|
272
|
+
const kept = await recordVaultQuota(this.#options.rosterPath, {
|
|
273
|
+
routeId,
|
|
274
|
+
observation,
|
|
275
|
+
credentialId: attempt.credentialId,
|
|
276
|
+
accountIdentitySource: attempt.accountIdentitySource,
|
|
277
|
+
...(attempt.accountIdentity !== undefined && {
|
|
278
|
+
accountIdentity: attempt.accountIdentity,
|
|
279
|
+
}),
|
|
280
|
+
...(attempt.expectedAccountIdentity !== undefined && {
|
|
281
|
+
expectedAccountIdentity: attempt.expectedAccountIdentity,
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
284
|
+
if (kept)
|
|
285
|
+
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
286
|
+
return kept;
|
|
287
|
+
}
|
|
288
|
+
close() {
|
|
289
|
+
if (this.#shutdown.signal.aborted)
|
|
290
|
+
return;
|
|
291
|
+
this.#shutdown.abort(new ClaustrumConsumerError('closed', 'Claustrum consumer is closed'));
|
|
292
|
+
if (this.#timer)
|
|
293
|
+
(this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
|
|
294
|
+
this.#timer = undefined;
|
|
295
|
+
this.#custody?.close();
|
|
296
|
+
this.#roster = undefined;
|
|
297
|
+
}
|
|
298
|
+
}
|