@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,100 @@
|
|
|
1
|
+
import type { CaptureSink } from './capture-sink.js';
|
|
2
|
+
import { type RedactionOptions } from './redact.js';
|
|
3
|
+
export type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
4
|
+
/** A logger that writes to a file, and to a capture sink when one is given. */
|
|
5
|
+
export interface InitLoggerOptions extends RedactionOptions {
|
|
6
|
+
/** Receives every emitted record, scrubbed, alongside the file. */
|
|
7
|
+
captureSink?: CaptureSink;
|
|
8
|
+
/**
|
|
9
|
+
* Path of the file lines are appended to, or a function returning it.
|
|
10
|
+
*
|
|
11
|
+
* A function is what a host passes when its destination can move while the
|
|
12
|
+
* process runs — an operator or a test changing the variable that names the
|
|
13
|
+
* log file expects the next line to land in the new file, and a value
|
|
14
|
+
* captured once at init would keep writing to the old one.
|
|
15
|
+
*/
|
|
16
|
+
file: string | (() => string);
|
|
17
|
+
/** Level floor applied when no `setLogLevel` call has overridden it. */
|
|
18
|
+
level?: Level | (() => Level | undefined);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A logger with no file: the capture sink is its only destination. Nothing
|
|
22
|
+
* is written to disk or printed, for a host that forwards records to its own
|
|
23
|
+
* log.
|
|
24
|
+
*/
|
|
25
|
+
export interface SinkOnlyLoggerOptions extends RedactionOptions {
|
|
26
|
+
/** Receives every emitted record, scrubbed. */
|
|
27
|
+
captureSink: CaptureSink;
|
|
28
|
+
file?: undefined;
|
|
29
|
+
/** Level floor applied when no `setLogLevel` call has overridden it. */
|
|
30
|
+
level?: Level | (() => Level | undefined);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What a logger is configured with: a file (`InitLoggerOptions`, the shape
|
|
34
|
+
* every release before this one accepted, kept as its own interface so a
|
|
35
|
+
* caller deriving a type from it with `Pick` or `extends` keeps compiling) or
|
|
36
|
+
* a capture sink alone.
|
|
37
|
+
*/
|
|
38
|
+
export type LoggerOptions = InitLoggerOptions | SinkOnlyLoggerOptions;
|
|
39
|
+
export interface ChannelLogger {
|
|
40
|
+
error(message: string, data?: unknown): void;
|
|
41
|
+
warn(message: string, data?: unknown): void;
|
|
42
|
+
info(message: string, data?: unknown): void;
|
|
43
|
+
debug(message: string, data?: unknown): void;
|
|
44
|
+
trace(message: string, data?: unknown): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A logger with its own file, level, redaction, capture sink and buffer.
|
|
48
|
+
* Two instances in one process never touch each other's settings or lines,
|
|
49
|
+
* so two plugins that load one shared copy of this module each keep their own
|
|
50
|
+
* log by holding their own instance.
|
|
51
|
+
*/
|
|
52
|
+
export interface LoggerInstance {
|
|
53
|
+
/** A logger whose lines carry `channel` and go to this instance's file. */
|
|
54
|
+
createLogger(channel: string): ChannelLogger;
|
|
55
|
+
/**
|
|
56
|
+
* Point this instance at a host's file and level. Idempotent: calling it
|
|
57
|
+
* again replaces both. A runtime level installed by `setLogLevel` is left
|
|
58
|
+
* alone, because it is the operator's explicit choice and outranks the
|
|
59
|
+
* floor a host computed at start-up.
|
|
60
|
+
*/
|
|
61
|
+
configure(options: LoggerOptions): void;
|
|
62
|
+
/** Operator override of the level floor; undefined removes it. */
|
|
63
|
+
setLogLevel(level: Level | undefined): void;
|
|
64
|
+
/**
|
|
65
|
+
* Write whatever is buffered. Safe to call synchronously from a
|
|
66
|
+
* process-exit handler, which is how each host drains the buffer on
|
|
67
|
+
* shutdown.
|
|
68
|
+
*/
|
|
69
|
+
flushLogs(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A logger instance of its own, configured with `options`. A plugin whose
|
|
73
|
+
* copy of this module may be shared with another plugin in the same process
|
|
74
|
+
* uses this instead of `initLogger`, so neither replaces the other's file,
|
|
75
|
+
* level, redaction or capture sink.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createLoggerInstance(options: LoggerOptions): LoggerInstance;
|
|
78
|
+
/**
|
|
79
|
+
* Point the module's default logger at a host's file and level, and return
|
|
80
|
+
* it. Idempotent: calling it again replaces both. A runtime level installed
|
|
81
|
+
* by `setLogLevel` is deliberately left alone, because it is the operator's
|
|
82
|
+
* explicit choice and outranks the floor a host computed at start-up.
|
|
83
|
+
*/
|
|
84
|
+
export declare function initLogger(options: LoggerOptions): LoggerInstance;
|
|
85
|
+
export declare function setLogLevel(l: Level | undefined): void;
|
|
86
|
+
/**
|
|
87
|
+
* Write whatever the default logger has buffered. Safe to call synchronously
|
|
88
|
+
* from a process-exit handler, which is how each host drains the buffer on
|
|
89
|
+
* shutdown.
|
|
90
|
+
*/
|
|
91
|
+
export declare function flushLogs(): void;
|
|
92
|
+
export declare function createLogger(channel: string): ChannelLogger;
|
|
93
|
+
export declare function flushForTest(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Return the default logger to its uninitialised state. Only a test needs
|
|
96
|
+
* this: a single process runs every test file, so a file path left over from
|
|
97
|
+
* one test would keep a later "logger was never initialised" case writing
|
|
98
|
+
* lines.
|
|
99
|
+
*/
|
|
100
|
+
export declare function resetLoggerForTest(): void;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { appendFileSync, chmodSync, existsSync, renameSync, statSync, } from 'node:fs';
|
|
2
|
+
import { createRedactor } from './redact.js';
|
|
3
|
+
const ORDER = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3,
|
|
8
|
+
trace: 4,
|
|
9
|
+
};
|
|
10
|
+
const MAX_BYTES = 5 * 1024 * 1024;
|
|
11
|
+
const ROTATE_KEEP = 3;
|
|
12
|
+
function chmodPrivate(path) {
|
|
13
|
+
try {
|
|
14
|
+
chmodSync(path, 0o600);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
/* never throw */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function rotateIfNeeded(f) {
|
|
21
|
+
try {
|
|
22
|
+
if (!(existsSync(f) && statSync(f).size >= MAX_BYTES))
|
|
23
|
+
return;
|
|
24
|
+
for (let i = ROTATE_KEEP - 1; i >= 1; i--) {
|
|
25
|
+
if (existsSync(`${f}.${i}`)) {
|
|
26
|
+
const rotated = `${f}.${i + 1}`;
|
|
27
|
+
renameSync(`${f}.${i}`, rotated);
|
|
28
|
+
chmodPrivate(rotated);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const rotated = `${f}.1`;
|
|
32
|
+
renameSync(f, rotated);
|
|
33
|
+
chmodPrivate(rotated);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* never throw */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function safeSerialize(data) {
|
|
40
|
+
try {
|
|
41
|
+
return ` ${JSON.stringify(data)}`;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return ' [unserializable]';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One logger's settings and buffer.
|
|
49
|
+
*
|
|
50
|
+
* Where lines are written and the level floor start unset unless `options`
|
|
51
|
+
* supplies them. A host decides where its log lives (that decision reads host
|
|
52
|
+
* environment variables and host directories, neither of which belongs in
|
|
53
|
+
* shared code) and configures the logger before it runs any command. Until
|
|
54
|
+
* then every `log.*` call is a silent no-op: throwing would turn the first
|
|
55
|
+
* command a host forgot to wire into a crash, and buffering would hold
|
|
56
|
+
* credential-bearing lines for a configuration that may never arrive.
|
|
57
|
+
*/
|
|
58
|
+
function createEngine(options) {
|
|
59
|
+
let logFileSource;
|
|
60
|
+
let initLevelSource;
|
|
61
|
+
let runtimeLevel;
|
|
62
|
+
let redactor = createRedactor();
|
|
63
|
+
let captureSink;
|
|
64
|
+
let buffer = [];
|
|
65
|
+
let timer;
|
|
66
|
+
function configure(next) {
|
|
67
|
+
logFileSource = next.file;
|
|
68
|
+
initLevelSource = next.level;
|
|
69
|
+
redactor = createRedactor(next);
|
|
70
|
+
captureSink = next.captureSink;
|
|
71
|
+
}
|
|
72
|
+
function setLogLevel(l) {
|
|
73
|
+
if (l === undefined || l in ORDER)
|
|
74
|
+
runtimeLevel = l;
|
|
75
|
+
}
|
|
76
|
+
function logFilePath() {
|
|
77
|
+
if (logFileSource === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const resolved = typeof logFileSource === 'function' ? logFileSource() : logFileSource;
|
|
80
|
+
return resolved || undefined;
|
|
81
|
+
}
|
|
82
|
+
function configuredLevel() {
|
|
83
|
+
if (runtimeLevel)
|
|
84
|
+
return runtimeLevel;
|
|
85
|
+
const floor = typeof initLevelSource === 'function'
|
|
86
|
+
? initLevelSource()
|
|
87
|
+
: initLevelSource;
|
|
88
|
+
if (floor && floor in ORDER)
|
|
89
|
+
return floor;
|
|
90
|
+
return 'info';
|
|
91
|
+
}
|
|
92
|
+
function flushLogs() {
|
|
93
|
+
if (timer) {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
timer = undefined;
|
|
96
|
+
}
|
|
97
|
+
if (!buffer.length)
|
|
98
|
+
return;
|
|
99
|
+
let file;
|
|
100
|
+
try {
|
|
101
|
+
file = logFilePath();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
buffer = [];
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const text = buffer.join('');
|
|
108
|
+
buffer = [];
|
|
109
|
+
if (!file)
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
rotateIfNeeded(file);
|
|
113
|
+
if (existsSync(file))
|
|
114
|
+
chmodPrivate(file);
|
|
115
|
+
appendFileSync(file, text, { encoding: 'utf8', mode: 0o600 });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* never throw */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function schedule() {
|
|
122
|
+
if (!timer)
|
|
123
|
+
timer = setTimeout(() => {
|
|
124
|
+
timer = undefined;
|
|
125
|
+
flushLogs();
|
|
126
|
+
}, 500);
|
|
127
|
+
}
|
|
128
|
+
function emit(channel, level, message, data) {
|
|
129
|
+
// Unconfigured (neither a file nor a sink) stays a silent no-op.
|
|
130
|
+
if (logFileSource === undefined && captureSink === undefined)
|
|
131
|
+
return;
|
|
132
|
+
try {
|
|
133
|
+
if (ORDER[level] > ORDER[configuredLevel()])
|
|
134
|
+
return;
|
|
135
|
+
const scrubbedMessage = redactor.redactStrings(message);
|
|
136
|
+
let scrubbedData;
|
|
137
|
+
try {
|
|
138
|
+
scrubbedData = redactor.redact(data);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
scrubbedData = '[unserializable]';
|
|
142
|
+
}
|
|
143
|
+
const line = `[${new Date().toISOString()}] ${level.toUpperCase()} [${channel}] ${scrubbedMessage}` +
|
|
144
|
+
(data === undefined ? '' : safeSerialize(scrubbedData)) +
|
|
145
|
+
'\n';
|
|
146
|
+
// A failing observer must not prevent file logging or escape into the host.
|
|
147
|
+
try {
|
|
148
|
+
captureSink?.({
|
|
149
|
+
channel,
|
|
150
|
+
level,
|
|
151
|
+
message: scrubbedMessage,
|
|
152
|
+
data: scrubbedData,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch { }
|
|
156
|
+
// A sink-only logger has no file to buffer lines for.
|
|
157
|
+
if (logFileSource === undefined)
|
|
158
|
+
return;
|
|
159
|
+
buffer.push(line);
|
|
160
|
+
if (buffer.length >= 50)
|
|
161
|
+
flushLogs();
|
|
162
|
+
else
|
|
163
|
+
schedule();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Provider and redaction failures must never turn diagnostics into a host crash.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function createLogger(channel) {
|
|
170
|
+
return {
|
|
171
|
+
error: (m, d) => emit(channel, 'error', m, d),
|
|
172
|
+
warn: (m, d) => emit(channel, 'warn', m, d),
|
|
173
|
+
info: (m, d) => emit(channel, 'info', m, d),
|
|
174
|
+
debug: (m, d) => emit(channel, 'debug', m, d),
|
|
175
|
+
trace: (m, d) => emit(channel, 'trace', m, d),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function reset() {
|
|
179
|
+
buffer = [];
|
|
180
|
+
if (timer) {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
timer = undefined;
|
|
183
|
+
}
|
|
184
|
+
logFileSource = undefined;
|
|
185
|
+
initLevelSource = undefined;
|
|
186
|
+
runtimeLevel = undefined;
|
|
187
|
+
redactor = createRedactor();
|
|
188
|
+
captureSink = undefined;
|
|
189
|
+
}
|
|
190
|
+
if (options)
|
|
191
|
+
configure(options);
|
|
192
|
+
return { configure, createLogger, setLogLevel, flushLogs, reset };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* A logger instance of its own, configured with `options`. A plugin whose
|
|
196
|
+
* copy of this module may be shared with another plugin in the same process
|
|
197
|
+
* uses this instead of `initLogger`, so neither replaces the other's file,
|
|
198
|
+
* level, redaction or capture sink.
|
|
199
|
+
*/
|
|
200
|
+
export function createLoggerInstance(options) {
|
|
201
|
+
const { configure, createLogger, setLogLevel, flushLogs } = createEngine(options);
|
|
202
|
+
return { configure, createLogger, setLogLevel, flushLogs };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The instance behind the module-level functions below, which keep the
|
|
206
|
+
* single-plugin API: one plugin per copy of this module calls `initLogger`
|
|
207
|
+
* and `createLogger` without holding an instance.
|
|
208
|
+
*/
|
|
209
|
+
const defaultEngine = createEngine();
|
|
210
|
+
/**
|
|
211
|
+
* Point the module's default logger at a host's file and level, and return
|
|
212
|
+
* it. Idempotent: calling it again replaces both. A runtime level installed
|
|
213
|
+
* by `setLogLevel` is deliberately left alone, because it is the operator's
|
|
214
|
+
* explicit choice and outranks the floor a host computed at start-up.
|
|
215
|
+
*/
|
|
216
|
+
export function initLogger(options) {
|
|
217
|
+
defaultEngine.configure(options);
|
|
218
|
+
const { configure, createLogger, setLogLevel, flushLogs } = defaultEngine;
|
|
219
|
+
return { configure, createLogger, setLogLevel, flushLogs };
|
|
220
|
+
}
|
|
221
|
+
export function setLogLevel(l) {
|
|
222
|
+
defaultEngine.setLogLevel(l);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Write whatever the default logger has buffered. Safe to call synchronously
|
|
226
|
+
* from a process-exit handler, which is how each host drains the buffer on
|
|
227
|
+
* shutdown.
|
|
228
|
+
*/
|
|
229
|
+
export function flushLogs() {
|
|
230
|
+
defaultEngine.flushLogs();
|
|
231
|
+
}
|
|
232
|
+
export function createLogger(channel) {
|
|
233
|
+
return defaultEngine.createLogger(channel);
|
|
234
|
+
}
|
|
235
|
+
export async function flushForTest() {
|
|
236
|
+
defaultEngine.flushLogs();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Return the default logger to its uninitialised state. Only a test needs
|
|
240
|
+
* this: a single process runs every test file, so a file path left over from
|
|
241
|
+
* one test would keep a later "logger was never initialised" case writing
|
|
242
|
+
* lines.
|
|
243
|
+
*/
|
|
244
|
+
export function resetLoggerForTest() {
|
|
245
|
+
defaultEngine.reset();
|
|
246
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { CaptureSink, LogTestRecord } from './capture-sink.js';
|
|
2
|
+
export { createCaptureSink } from './capture-sink.js';
|
|
3
|
+
export type { ChannelLogger, InitLoggerOptions, Level, LoggerInstance, LoggerOptions, SinkOnlyLoggerOptions, } from './engine.js';
|
|
4
|
+
export { createLogger, createLoggerInstance, flushForTest, flushLogs, initLogger, resetLoggerForTest, setLogLevel, } from './engine.js';
|
|
5
|
+
export type { RedactionOptions, Redactor } from './redact.js';
|
|
6
|
+
export { createRedactor, redact, redactStrings } from './redact.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RedactionOptions {
|
|
2
|
+
extraSecretKeys?: (normalizedKey: string) => boolean;
|
|
3
|
+
extraValuePatterns?: RegExp[];
|
|
4
|
+
}
|
|
5
|
+
export declare function createRedactor(options?: RedactionOptions): {
|
|
6
|
+
redact: (value: unknown) => unknown;
|
|
7
|
+
redactStrings: (value: unknown) => unknown;
|
|
8
|
+
};
|
|
9
|
+
export type Redactor = ReturnType<typeof createRedactor>;
|
|
10
|
+
export declare const redact: (value: unknown) => unknown;
|
|
11
|
+
export declare const redactStrings: (value: unknown) => unknown;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const SECRET_KEY_EXACT = /^(authorization|x-api-key|cookie|set-cookie|refresh|access|token)$/i;
|
|
2
|
+
const TOKEN_VALUE = /\b(Bearer\s+[\w.-]+|sk-[\w-]+|eyJ[\w.-]+)\b|ckh_[A-Za-z0-9_-]{20,}/g;
|
|
3
|
+
const MASK = '***REDACTED***';
|
|
4
|
+
// How many errors deep a cause chain is followed. A chain this long is
|
|
5
|
+
// already unreadable in a log line, and an unbounded one (each cause a fresh
|
|
6
|
+
// object, so the cycle check never trips) must not grow the line forever.
|
|
7
|
+
const MAX_ERROR_DEPTH = 8;
|
|
8
|
+
// Error's own fields are non-enumerable, so a plain entry walk renders an
|
|
9
|
+
// Error as `{}` and the name, message and stack a plugin logged are lost.
|
|
10
|
+
// The tag check also recognises an Error from another realm (a worker or a
|
|
11
|
+
// vm context), whose prototype is not this realm's Error.
|
|
12
|
+
function isError(value) {
|
|
13
|
+
return (value instanceof Error ||
|
|
14
|
+
Object.prototype.toString.call(value) === '[object Error]');
|
|
15
|
+
}
|
|
16
|
+
export function createRedactor(options = {}) {
|
|
17
|
+
const patterns = [TOKEN_VALUE, ...(options.extraValuePatterns ?? [])].map((pattern) => new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`));
|
|
18
|
+
function isSecretKey(key) {
|
|
19
|
+
const normalized = key.toLowerCase().replace(/[-_]/g, '');
|
|
20
|
+
return (SECRET_KEY_EXACT.test(key) ||
|
|
21
|
+
normalized.includes('apikey') ||
|
|
22
|
+
normalized.endsWith('secret') ||
|
|
23
|
+
normalized.endsWith('password') ||
|
|
24
|
+
(normalized.endsWith('token') && !normalized.endsWith('tokens')) ||
|
|
25
|
+
!!options.extraSecretKeys?.(normalized));
|
|
26
|
+
}
|
|
27
|
+
// The fields every log reader wants first, then whatever else the error
|
|
28
|
+
// carries as its own enumerable properties (which is all an Error rendered
|
|
29
|
+
// as before). Every entry goes through the same key and string scrubbing as
|
|
30
|
+
// any other object, so a token in a message or stack is still masked.
|
|
31
|
+
function errorEntries(error) {
|
|
32
|
+
const fields = error;
|
|
33
|
+
const entries = [
|
|
34
|
+
['name', error.name],
|
|
35
|
+
['message', error.message],
|
|
36
|
+
['stack', error.stack],
|
|
37
|
+
];
|
|
38
|
+
for (const key of ['code', 'status', 'cause'])
|
|
39
|
+
if (fields[key] !== undefined)
|
|
40
|
+
entries.push([key, fields[key]]);
|
|
41
|
+
const listed = new Set(entries.map(([key]) => key));
|
|
42
|
+
for (const entry of Object.entries(error))
|
|
43
|
+
if (!listed.has(entry[0]))
|
|
44
|
+
entries.push(entry);
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
function walk(value, keys, seen, errorDepth = 0) {
|
|
48
|
+
if (typeof value === 'string') {
|
|
49
|
+
return patterns.reduce((text, pattern) => text.replace(pattern, MASK), value);
|
|
50
|
+
}
|
|
51
|
+
if (!value || typeof value !== 'object')
|
|
52
|
+
return value;
|
|
53
|
+
if (seen.has(value))
|
|
54
|
+
return '[Circular]';
|
|
55
|
+
seen.add(value);
|
|
56
|
+
try {
|
|
57
|
+
if (Array.isArray(value))
|
|
58
|
+
return value.map((item) => walk(item, keys, seen, errorDepth));
|
|
59
|
+
const error = isError(value);
|
|
60
|
+
if (error && errorDepth >= MAX_ERROR_DEPTH)
|
|
61
|
+
return '[Truncated]';
|
|
62
|
+
const childDepth = error ? errorDepth + 1 : errorDepth;
|
|
63
|
+
// Define own properties so a diagnostic __proto__ key cannot change the output prototype.
|
|
64
|
+
return Object.fromEntries((error ? errorEntries(value) : Object.entries(value)).map(([key, item]) => [
|
|
65
|
+
key,
|
|
66
|
+
keys && isSecretKey(key)
|
|
67
|
+
? MASK
|
|
68
|
+
: walk(item, keys, seen, childDepth),
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
seen.delete(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
redact: (value) => walk(value, true, new WeakSet()),
|
|
77
|
+
// Schema keys describe arguments, not credentials; only their string values are scrubbed.
|
|
78
|
+
redactStrings: (value) => walk(value, false, new WeakSet()),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const defaultRedactor = createRedactor();
|
|
82
|
+
export const redact = defaultRedactor.redact;
|
|
83
|
+
export const redactStrings = defaultRedactor.redactStrings;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { rmdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { mkdir, rmdir, stat, utimes } from 'node:fs/promises';
|
|
3
|
+
// Protocol source: proper-lockfile 4.1.2 lib/lockfile.js and lib/mtime-precision.js;
|
|
4
|
+
// line-level citations and the Pi lock options are recorded in docs/sources.md.
|
|
5
|
+
// Pi's async acquireLockAsync uses stale=30s (update=15s). Keep takeover at 30s
|
|
6
|
+
// so a live async writer's lock cannot be reclaimed between its renewals.
|
|
7
|
+
const STALE = 30_000;
|
|
8
|
+
// Pi's sync acquireLockSyncWithRetry uses proper-lockfile defaults: stale=10s,
|
|
9
|
+
// update=5s. Renew every 3s for margin and stop retrying failed renewals after 10s.
|
|
10
|
+
const RENEWAL_STALE = 10_000;
|
|
11
|
+
const UPDATE = 3_000;
|
|
12
|
+
let precision;
|
|
13
|
+
/** Held locks, with the time each last renewed, for cleanup at process exit. */
|
|
14
|
+
const active = new Map();
|
|
15
|
+
process.once('exit', () => {
|
|
16
|
+
for (const [path, identity] of active) {
|
|
17
|
+
try {
|
|
18
|
+
// Pi's synchronous auth.json writer treats a lock whose mtime has not
|
|
19
|
+
// been refreshed for 10s as abandoned and may take it over. A lease that
|
|
20
|
+
// went 10s without renewing may therefore already be that writer's, so
|
|
21
|
+
// it is left in place; whoever next needs the lock clears it as stale.
|
|
22
|
+
if (identity.renewedAt + RENEWAL_STALE < Date.now())
|
|
23
|
+
continue;
|
|
24
|
+
const observed = statSync(path);
|
|
25
|
+
if (sameDirectory(observed, identity) &&
|
|
26
|
+
observed.mtime.getTime() === identity.mtime)
|
|
27
|
+
rmdirSync(path);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* A compromised or already removed lock is not ours to clean up. */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
function sameDirectory(observed, identity) {
|
|
35
|
+
return observed.dev === identity.dev && observed.ino === identity.ino;
|
|
36
|
+
}
|
|
37
|
+
function errorCode(error) {
|
|
38
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
39
|
+
? error.code
|
|
40
|
+
: undefined;
|
|
41
|
+
}
|
|
42
|
+
function held() {
|
|
43
|
+
return Object.assign(new Error('Pi auth lock held'), { code: 'ELOCKED' });
|
|
44
|
+
}
|
|
45
|
+
async function remove(path) {
|
|
46
|
+
try {
|
|
47
|
+
await rmdir(path);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (errorCode(error) !== 'ENOENT')
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function acquire(path, stale) {
|
|
55
|
+
try {
|
|
56
|
+
await mkdir(path);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (errorCode(error) !== 'EEXIST')
|
|
60
|
+
throw error;
|
|
61
|
+
if (stale <= 0)
|
|
62
|
+
throw held();
|
|
63
|
+
let mtime;
|
|
64
|
+
try {
|
|
65
|
+
mtime = (await stat(path)).mtime.getTime();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (errorCode(error) === 'ENOENT')
|
|
69
|
+
return acquire(path, 0);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
if (!(mtime < Date.now() - stale))
|
|
73
|
+
throw held();
|
|
74
|
+
// Match upstream: rmdir (ignore ENOENT), then one mkdir attempt without another stale check.
|
|
75
|
+
await remove(path);
|
|
76
|
+
return acquire(path, 0);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
if (!precision) {
|
|
80
|
+
const probe = new Date(Math.ceil(Date.now() / 1_000) * 1_000 + 5);
|
|
81
|
+
await utimes(path, probe, probe);
|
|
82
|
+
}
|
|
83
|
+
const observed = await stat(path);
|
|
84
|
+
const mtime = observed.mtime.getTime();
|
|
85
|
+
precision ??= mtime % 1_000 === 0 ? 's' : 'ms';
|
|
86
|
+
return { dev: observed.dev, ino: observed.ino, mtime };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
await remove(path).catch(() => { });
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The same mkdir/mtime/rmdir protocol as Pi's proper-lockfile, with realpath disabled. */
|
|
94
|
+
export async function acquirePiLock(authPath, onCompromised, onRenew) {
|
|
95
|
+
const path = `${authPath}.lock`;
|
|
96
|
+
const identity = await acquire(path, STALE);
|
|
97
|
+
let mtime = identity.mtime;
|
|
98
|
+
let lastUpdate = Date.now();
|
|
99
|
+
let released = false;
|
|
100
|
+
let timer;
|
|
101
|
+
active.set(path, { ...identity, renewedAt: lastUpdate });
|
|
102
|
+
function compromise() {
|
|
103
|
+
released = true;
|
|
104
|
+
if (timer)
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
active.delete(path);
|
|
107
|
+
onCompromised();
|
|
108
|
+
}
|
|
109
|
+
function assertFresh() {
|
|
110
|
+
// A sync writer may already have reclaimed an unrenewed lock after 10s,
|
|
111
|
+
// even if the directory still matches. Never revive that expired lease.
|
|
112
|
+
if (!released && lastUpdate + RENEWAL_STALE < Date.now())
|
|
113
|
+
compromise();
|
|
114
|
+
}
|
|
115
|
+
function schedule(delay = UPDATE) {
|
|
116
|
+
if (released)
|
|
117
|
+
return;
|
|
118
|
+
timer = setTimeout(() => {
|
|
119
|
+
void update();
|
|
120
|
+
}, delay);
|
|
121
|
+
timer.unref();
|
|
122
|
+
}
|
|
123
|
+
function retry(error) {
|
|
124
|
+
if (released)
|
|
125
|
+
return;
|
|
126
|
+
if (errorCode(error) === 'ENOENT' ||
|
|
127
|
+
lastUpdate + RENEWAL_STALE < Date.now())
|
|
128
|
+
compromise();
|
|
129
|
+
else
|
|
130
|
+
schedule(1_000);
|
|
131
|
+
}
|
|
132
|
+
async function update() {
|
|
133
|
+
assertFresh();
|
|
134
|
+
if (released)
|
|
135
|
+
return;
|
|
136
|
+
let observed;
|
|
137
|
+
try {
|
|
138
|
+
observed = await stat(path);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
retry(error);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
assertFresh();
|
|
145
|
+
if (released)
|
|
146
|
+
return;
|
|
147
|
+
if (!sameDirectory(observed, identity) ||
|
|
148
|
+
observed.mtime.getTime() !== mtime) {
|
|
149
|
+
compromise();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const next = new Date(precision === 's' ? Math.ceil(Date.now() / 1_000) * 1_000 : Date.now());
|
|
153
|
+
try {
|
|
154
|
+
await utimes(path, next, next);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
retry(error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (released)
|
|
161
|
+
return;
|
|
162
|
+
mtime = next.getTime();
|
|
163
|
+
lastUpdate = Date.now();
|
|
164
|
+
active.set(path, { ...identity, mtime, renewedAt: lastUpdate });
|
|
165
|
+
schedule();
|
|
166
|
+
}
|
|
167
|
+
// Tests can resume a paused renewal without waiting for the production timer.
|
|
168
|
+
onRenew?.(async () => {
|
|
169
|
+
if (timer)
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
await update();
|
|
172
|
+
});
|
|
173
|
+
schedule();
|
|
174
|
+
return {
|
|
175
|
+
assertFresh,
|
|
176
|
+
async release() {
|
|
177
|
+
if (released)
|
|
178
|
+
return;
|
|
179
|
+
released = true;
|
|
180
|
+
if (timer)
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
active.delete(path);
|
|
183
|
+
// An expired lease is never removed, even when the directory still
|
|
184
|
+
// matches. Pi's synchronous auth.json writer may take over a lock that
|
|
185
|
+
// went 10s without an mtime refresh, so after 10s without renewing the
|
|
186
|
+
// directory may be that writer's. Whoever next needs the lock clears it
|
|
187
|
+
// as stale.
|
|
188
|
+
const expired = () => lastUpdate + RENEWAL_STALE < Date.now();
|
|
189
|
+
if (expired()) {
|
|
190
|
+
compromise();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let observed;
|
|
194
|
+
try {
|
|
195
|
+
observed = await stat(path);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (errorCode(error) !== 'ENOENT')
|
|
199
|
+
throw error;
|
|
200
|
+
compromise();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (expired() ||
|
|
204
|
+
!sameDirectory(observed, identity) ||
|
|
205
|
+
mtime !== observed.mtime.getTime()) {
|
|
206
|
+
compromise();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await remove(path);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|