import type { CaptureHeaderDecl, DomSelectorDecl, DomListSelectorDecl, GraphqlOpDeclaration, IndexedDbScopeDecl } from '@fetchproxy/protocol'; export interface PointerDecl { outputKey: string; storageKey: string; jsonPointer: string; } /** One per-service trust scope. Shape mirrors bootstrap's `Declarations` plus `domains`. */ export interface Profile { domains: string[]; cookies: string[]; localStorage: string[]; sessionStorage: string[]; captureHeaders: CaptureHeaderDecl[]; indexedDb: IndexedDbScopeDecl[]; localStoragePointers: PointerDecl[]; sessionStoragePointers: PointerDecl[]; domSelectors: DomSelectorDecl[]; domListSelectors: DomListSelectorDecl[]; download: boolean; /** * 1.12.0+: may this profile OVERWRITE the cookies it declares? * * Its own flag rather than implied by `cookies`, because a write is a * different privilege from a read and the user approves it as its own line * in the pair popup. It still cannot reach beyond the declared `cookies`. */ cookieWrite: boolean; /** * 2.9.2+: may a fetch from this profile run in the page's MAIN world? * * Its own flag for the reason `cookieWrite` has one: routing a request * through page script gives up fetchproxy's tamper resistance, so it is a * separate line in the pair popup rather than something `fetch` implies. * * It exists on the CLI at all so this class of failure is reproducible from * a shell against the real extension. chrischall/fetchproxy#324 spent two * rounds on wrong answers because the only prober that could reach the MAIN * world was a hosted MCP, and the browser harness reached for instead cannot * make cross-origin requests at all. */ inPage: boolean; /** * 2.10.0+: may this profile snapshot a redirect TARGET? * * Its own flag rather than implied by `fetch`, because what it reads is a * URL the page was sent to and never asked for — a presigned link behind a * 302 is the motivating case, and it is exactly the kind of value worth * approving deliberately. Scope is the profile's declared `domains`; unlike * `captureHeaders` there is no per-entry declaration, so the flag is all * there is to approve. */ captureRedirect: boolean; /** * 2.10.0+: GraphQL operations this profile may invoke, as `name` → * `operationName`. * * Declared per operation rather than granted wholesale: the extension * resolves the name to a DocumentNode the page's own Apollo client already * holds and runs it through the site's own client, so an undeclared name * would be an arbitrary query on the user's session. An empty list means no * operations, even with the capability present. */ graphqlOps: GraphqlOpDeclaration[]; } export declare function cliHome(env?: Record): string; export declare function identityPath(name: string, identityDir?: string): string; /** * The extension pin that sits beside a profile's identity (#208). * * Paired with `identityPath` deliberately: removing a profile has to take both. * Leaving the pin behind means a profile of the same name created later starts * life already committed to a browser identity it never met — inheriting a * refusal, or a trust, that nobody in this installation decided. * * "Beside" is where the pin lands by default and no longer where it must land: * `FETCHPROXY_TRUST_DIR` moves it, and this has to follow the same resolution * the server writes through or `profile remove` deletes a file that is not the * pin. */ export declare function extensionPinPath(name: string, trustDir?: string): string; export declare function emptyProfile(domains: string[]): Profile; export declare function loadProfiles(home?: string): Record; /** * The one write path for `profiles.json` — every profile verb (`add`, * `declare`, `remove`) goes through here. * * Write-then-rename, for the reason `writeExtensionPin` does it (server * `extension-trust.ts`): opening the target truncates it before a byte is * written, so a crash, a full disk or a ^C mid-write leaves an empty file and * `loadProfiles` then refuses EVERY profile as invalid JSON — one interrupted * `fpx profile declare` losing the whole set, including the domains and scope * the user already paired against. `rename` is atomic on every filesystem this * runs on, so a reader sees the old file or the new one and never half of one. * * The temporary name carries a random component rather than a fixed `.tmp` * suffix: two `fpx` processes writing at once would otherwise share one * staging file, and the first to `rename` would publish the second's * half-written bytes — the torn read this exists to remove, reintroduced. The * last writer still wins (a lost update, which it always was); what cannot * happen is a torn file. * * 0600 twice over, as the identity file does it: `writeFileSync`'s `mode` * applies only at creation and is subject to the umask, so the explicit * `chmod` is what actually guarantees it. Under the old in-place write a * `profiles.json` that already existed kept whatever mode it had, and the * `mode` option did nothing at all. */ export declare function saveProfiles(map: Record, home?: string): void; export declare function getProfile(name: string, home?: string): Profile;