/** * One long poll, one offset, both kinds of update. * * Telegram's getUpdates has a single cursor per bot. `offset` confirms every * update older than itself **regardless of `allowed_updates`** — that parameter * only filters what comes back in the response, not what the call acknowledges. * So two pollers on one bot do not coexist: each advances the cursor past * updates the other never saw, and both start losing traffic silently. An * approval tapped on the phone would simply not register, with nothing anywhere * to say why. * * Hence this. Everything that wants updates subscribes here, the loop asks for * every type any subscriber could want, and dispatch happens locally where * losing one is impossible. */ export interface TelegramCredentials { /** From @BotFather. A credential — belongs in the keychain, never in config. */ botToken: string; /** The single chat allowed to answer. Anything else is ignored. */ chatID: string; } export type UpdateKind = 'callback_query' | 'message'; export type UpdateHandler = (payload: unknown) => void | Promise; /** * Told when a poll fails, and when one succeeds after failing. * * Without this the loop was completely silent: a webhook left configured on the * bot answers 409 to every getUpdates, a revoked token answers 401, and both * looked exactly like a phone nobody had messaged. Diagnosing it meant reading * the source. */ export type PollObserver = (event: { ok: boolean; detail: string; }) => void; /** * Where the cursor goes after a batch. * * Never backwards: a retry that returns an older batch, or a response with ids * this build does not understand, must not re-deliver what was already handled. */ export declare function nextOffset(current: number, updates: { update_id?: unknown; }[]): number; /** Every kind this loop asks for, so one cursor can serve every subscriber. */ export declare const POLLED_KINDS: readonly UpdateKind[]; export declare class TelegramUpdates { private readonly botToken; private readonly handlers; private offset; private running; private readonly idlePauseMs; private observer; /** Only the first failure of a streak is reported, then the recovery. */ private failing; constructor(botToken: string, idlePauseMs?: number); /** Watch the health of the poll itself, separately from its payload. */ observe(observer: PollObserver | null): void; private report; /** * Listen for one kind of update. Returns the function that stops listening. * * The loop runs while anyone is listening and stops when the last subscriber * leaves, so a CLI with the inbox switched off never opens a connection. */ subscribe(kind: UpdateKind, handler: UpdateHandler): () => void; private subscriberCount; private loop; private getUpdates; } export declare function sharedUpdates(botToken: string): TelegramUpdates;