import { type ApolloSigner } from "./connector.js"; import type { RefreshablePythiaKey } from "./types.js"; type Half = "standard" | "smart"; export type DualLinkHalfStatus = { status: "pending"; } | { status: "active"; secret: string; expiresAt: number; }; export interface DualLinkConnectorOptions { /** Split via `splitDualLinkKey` at construction — a malformed key throws * synchronously, before anything else is constructed. */ dualLinkKey: string; baseUrl: string; standardSigner: ApolloSigner; smartSigner: ApolloSigner; fetchImpl?: typeof fetch; /** Refresh cadence in ms. Default 3h — see {@link DEFAULT_INTERVAL_MS}. */ intervalMs?: number; /** Called when a half's `ensureSecret()` throws during `tick()` — not just * this SDK's own `PythiaConnectorError`s, but also whatever your injected * `standardSigner`/`smartSigner` throw (mirrors `PythiaConnectorOptions. * onKeyProviderError`'s identical caveat). Defaults to `console.error`. * This callback is itself called inside a try/catch (see `tickHalf`) — if * you wire it to a logging/APM sink that can throw (e.g. a network-backed * logger), that throw is caught and reported via a SECOND, fixed * `console.error` fallback rather than propagating, so a misbehaving * `onError` can never break the other half's tick or crash the `start()` * interval loop via an unhandled rejection. */ onError?: (half: Half, error: unknown) => void; } export interface DualLinkStatus { standard: DualLinkHalfStatus; smart: DualLinkHalfStatus; /** Whichever half currently has an active secret — standard preferred, * smart as fallback — since the gate never cares which half issued it. * `null` when neither half is active. */ secret: string | null; expiresAt: number | null; } /** * The class-shaped generalization of `apps/pythia`'s `SelfConnectorLoop` * pattern for an arbitrary already-active dual-link pair: holds two internal * `PythiaConnector`s (one per Apollo half), ticks both on a schedule with * per-half error isolation, and reports one usable status object — both * halves' individual state, plus a single `secret`/`expiresAt` a consumer can * use directly. See `docs/work/pythia-client-dual-link-sdk/design.md`. */ export declare class DualLinkConnector { private readonly standardConnector; private readonly smartConnector; private readonly intervalMs; private readonly onError; private timer; /** The last successful `ensureSecret()` result per half — `{status: * "pending"}` until the first tick succeeds for that half. */ private lastStandard; private lastSmart; constructor(options: DualLinkConnectorOptions); /** * Runs both halves independently: a half whose `ensureSecret()` throws is * caught and reported via `onError` HERE so it can never prevent the other * half's tick from running, nor throw out of `tick()` itself — mirrors * `SelfConnectorLoop.tick()`'s per-half isolation exactly. */ tick(): Promise; private tickHalf; start(): void; stop(): void; /** * Reads the cached per-half status WITHOUT triggering any new network/ * signer call. Top-level `secret`/`expiresAt` prefer the standard half's * active values, falling back to the smart half's — the gate never cares * which half issued the secret. */ status(): DualLinkStatus; /** * Returns a closure suitable for `PythiaClientOptions.pythiaKey`: runs * `tick()` then resolves to `status().secret`, or `undefined` when neither * half is active. `tick()` itself never throws (per-half isolation above), * but this closure catches defensively anyway and resolves `undefined` * rather than rejecting — same contract as `PythiaConnector.keyProvider()`. */ keyProvider(): () => Promise; /** * Drop BOTH halves' cached secrets so the next `tick()`/`ensureSecret()` * re-mints. Used by `PythiaClient`'s 401 self-heal (the unified key could have * come from either half). Idempotent. */ invalidate(): Promise; /** * A {@link RefreshablePythiaKey} for `PythiaClient`'s `pythiaKey` option, giving * automatic 401 self-heal: `get()` is the same driven-tick resolution as * {@link keyProvider}, `invalidate()` drops both halves. Wire * `new PythiaClient({ pythiaKey: dualLink.asKeySource() })`. */ asKeySource(): RefreshablePythiaKey; } export {};