import type { KeyObject } from 'node:crypto'; import type { TlObject } from '@mt-tl/tl'; import type { TlCodec } from '@mt-tl/server/testkit'; import { TestClient } from './client/test-client.js'; import { type ClientTransport } from './client/transport.js'; /** Options for opening a session. */ export interface ConnectOpts { /** Negotiate this TL layer: the first call is wrapped in * `invokeWithLayer(layer, initConnection(..., query))`. Without it the * connection runs at the server's `defaultLayer`. */ layer?: number; /** Override the `initConnection` fields (`api_id`, `device_model`, …). */ initConnection?: Record; /** Called after every {@link TestSession.invoke} settles — for request/response * logging (the CLI `--verbose` flag wires this). */ onInvoke?: (trace: InvokeTrace) => void; /** Called for every server-PUSH (update) this connection receives — for * logging pushes (the CLI `--verbose` flag wires this). */ onUpdate?: (update: TlObject) => void; } /** One `invoke` call's request + outcome, for tracing/logging. */ export interface InvokeTrace { method: string; params: Record; /** The decoded `rpc_result` payload (absent if it failed). */ result?: unknown; /** The `rpc_error` (absent if it succeeded). */ error?: { code: number; message: string; }; durationMs: number; } /** One method's call-shape: its `params` and `result` types. Matches an entry of * the `RpcMethods` map that `@mt-tl/tl` generates (and `mtproto-test types` * writes), so `TestSession` gives a typed, autocompleted `invoke`. */ export interface MethodSpec { params?: unknown; result?: unknown; } /** Default method map: any method name → free-form params, {@link TlObject} result. * This is what an UNtyped session uses, so `invoke('anything', {…})` stays valid * and returns a `TlObject`. Pass your generated `RpcMethods` for typed calls. */ export type AnyMethods = Record; result: TlObject; }>; /** A failed RPC: the server replied `rpc_error`. `code` is the MTProto error code. */ export declare class RpcError extends Error { readonly code: number; constructor(code: number, message: string); } /** Predicate (or a bare constructor name) an update must satisfy. */ export type UpdateMatch = string | ((u: TlObject) => boolean); export interface InvokeOpts { /** Override the per-call timeout (ms) waiting for the matching `rpc_result`. */ timeoutMs?: number; } export interface ExpectUpdateOpts { /** How long (ms) to wait for a matching pushed update. Default 2000. */ timeoutMs?: number; } /** * Ergonomic, framework-agnostic wrapper around {@link TestClient}: one connected, * handshaken client with an auto-unwrapping `invoke` and an `expectUpdate` for * asserting server-push. A single internal receive loop fans each decrypted * message to its waiting caller (`rpc_result` → the matching `invoke`; pushes → * `expectUpdate`/an update queue), so concurrent invokes and update assertions * never race on the socket. * * @example * ```ts * const alice = await server.connect() * const cfg = await alice.invoke('help.getConfig') // → the rpc_result payload * await alice.invoke('account.checkFields', { ... }) // throws RpcError on rpc_error * const upd = await alice.expectUpdate('updateShort') // waits for a push * ``` * * `RM` is your generated `RpcMethods` map (from `mtproto-test types`), making * `invoke` typed + autocompleted. The constraint is a mapped type * (`{ [K in keyof RM]: MethodSpec }`), NOT `Record` — the * generated `RpcMethods` is an `interface` (no index signature) and so does not * satisfy `Record`; the mapped form accepts it. Don't "simplify" it. */ export declare class TestSession { readonly raw: TestClient; private readonly updates; private readonly updateWaiters; private readonly pending; private pumping; private closed; private initSpec?; private readonly negotiated?; private readonly onInvoke?; private readonly onUpdate?; constructor(raw: TestClient, opts?: ConnectOpts); /** Connect a WebSocket transport, run the MTProto handshake, return a session. * Pass your generated `RpcMethods` (`TestSession.open(…)`) for a * typed, autocompleted `invoke`. */ static open(url: string, publicKey: KeyObject, codec: TlCodec, opts?: ConnectOpts): Promise>; /** Connect over any {@link ClientTransport} (e.g. raw TCP), handshake, return a * session. Use this for stands that aren't plain WebSocket. */ static fromTransport(transport: ClientTransport, publicKey: KeyObject, codec: TlCodec, opts?: ConnectOpts): Promise>; /** The TL layer this session negotiated (via {@link ConnectOpts.layer}), or * `undefined` if it runs at the server's default. */ get negotiatedLayer(): number | undefined; /** This session's MTProto identifiers (set after the handshake) — handy for * correlating a client session with the server's logs. */ get authKeyId(): bigint; get sessionId(): bigint; /** * Call `method` with `params`, returning the decoded `rpc_result` payload. * Service messages (`new_session_created`, `msgs_ack`, …) are swallowed and * interleaved updates are queued for {@link expectUpdate}. Throws * {@link RpcError} if the server answers `rpc_error`. */ invoke(method: M, params?: RM[M]['params'], opts?: InvokeOpts): Promise; private invokeInner; /** * Wait for a pushed update matching `match` (a predicate, or a bare * constructor name like `'updateShort'`). Resolves from already-queued * updates first, then from new pushes; rejects on timeout. */ expectUpdate(match: UpdateMatch, opts?: ExpectUpdateOpts): Promise; /** Drain and return any updates received but not yet consumed. */ drainUpdates(): TlObject[]; /** Close the transport and reject any in-flight invoke/expectUpdate. */ close(): void; /** Wrap the FIRST query in `invokeWithLayer(initConnection(...))` when a layer * was requested; the server sets `conn.ctx.apiLayer` and dispatches the inner * query (the rpc_result still carries the outer msg_id we matched on). */ private wrapInit; private ensurePump; private pump; private dispatch; private routeUpdate; } //# sourceMappingURL=session.d.ts.map