/** * Rondevu API Client - RPC interface */ import { CryptoAdapter, KeyPair } from '../crypto/adapter.js'; import { BatcherOptions } from './batcher.js'; export type { KeyPair } from '../crypto/adapter.js'; export type { BatcherOptions, RequestAuth } from './batcher.js'; export interface OfferRequest { sdp: string; } export interface PublishRequest { tags: string[]; offers: OfferRequest[]; ttl?: number; } export interface DiscoverRequest { tags: string[]; limit?: number; offset?: number; } export interface CountOffersByTagsRequest { tags: string[]; unique?: boolean; } export interface CountOffersByTagsResponse { counts: Record; } export interface TaggedOffer { offerId: string; publicKey: string; tags: string[]; sdp: string; createdAt: number; expiresAt: number; } export interface DiscoverResponse { offers: TaggedOffer[]; count: number; limit: number; offset: number; } export interface PublishResponse { publicKey: string; tags: string[]; offers: Array<{ offerId: string; sdp: string; createdAt: number; expiresAt: number; }>; createdAt: number; expiresAt: number; } export interface IceCandidate { candidate: RTCIceCandidateInit | null; role: 'offerer' | 'answerer'; createdAt: number; } /** * RondevuAPI - RPC-based API client for Rondevu signaling server * * Uses Ed25519 public key cryptography for authentication. * The public key IS the identity (like Ethereum addresses). */ export declare class RondevuAPI { private baseUrl; private keyPair; private static readonly PUBLIC_KEY_LENGTH; private static readonly PRIVATE_KEY_LENGTH; private static readonly MAX_CANONICALIZE_DEPTH; private crypto; private batcher; constructor(baseUrl: string, keyPair: KeyPair, cryptoAdapter?: CryptoAdapter, batcherOptions?: BatcherOptions); /** * Canonical JSON serialization with sorted keys * Ensures deterministic output regardless of property insertion order */ private canonicalJSON; /** * Build signature message following server format * Format: timestamp:nonce:method:canonicalJSON(params || {}) * * Uses canonical JSON (sorted keys) to ensure deterministic serialization * across different JavaScript engines and platforms. * * Note: When params is undefined, it's serialized as "{}" (empty object). * This matches the server's expectation for parameterless RPC calls. */ private buildSignatureMessage; /** * Generate cryptographically secure nonce * Uses crypto.randomUUID() if available, falls back to secure random bytes * * Note: this.crypto is always initialized in constructor (WebCryptoAdapter or NodeCryptoAdapter) * and TypeScript enforces that both implement randomBytes(), so the fallback is always safe. */ private generateNonce; /** * Generate per-request authentication credentials * Uses Ed25519 signature with nonce for replay protection * * Security notes: * - Nonce: Cryptographically secure random value (UUID or 128-bit hex) * - Timestamp: Prevents replay attacks outside the server's time window * - Server validates timestamp is within acceptable range (typically ±5 minutes) * - Tolerates reasonable clock skew between client and server * - Requests with stale timestamps are rejected * - Signature: Ed25519 ensures message integrity and authenticity * - Server validates nonce uniqueness to prevent replay within time window * - Each nonce can only be used once within the timestamp validity window * - Server maintains nonce cache with expiration matching timestamp window */ private generateAuth; /** * Execute RPC call via batcher * Requests are batched with throttling for efficiency * All requests within the batch delay window are sent in a single HTTP call */ private rpc; /** * Generate a new Ed25519 keypair locally * This is completely client-side - no server communication * * @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter) * @returns Generated keypair with publicKey and privateKey as hex strings */ static generateKeyPair(cryptoAdapter?: CryptoAdapter): Promise; /** * Publish offers with tags */ publish(request: PublishRequest): Promise; /** * Discover offers by tags * @param request - Discovery request with tags and optional pagination * @returns Paginated response if limit provided, single offer if not */ discover(request: DiscoverRequest): Promise; /** * Count available offers by tags * Returns the count of available (unanswered, non-expired) offers for each tag * @param request - Request with tags to count and optional unique flag * @returns Object mapping each tag to its count */ countOffersByTags(request: CountOffersByTagsRequest): Promise; /** * Delete an offer by ID */ deleteOffer(offerId: string): Promise<{ success: boolean; }>; /** * Update tags for all offers owned by this identity * @param tags New tags to set on all offers * @returns Number of offers updated */ updateOfferTags(tags: string[]): Promise<{ success: boolean; count: number; }>; /** * Answer an offer * @param offerId The offer ID to answer * @param sdp The SDP answer * @param matchedTags Optional tags that were used to discover this offer */ answerOffer(offerId: string, sdp: string, matchedTags?: string[]): Promise; /** * Get answer for a specific offer (offerer polls this) */ getOfferAnswer(offerId: string): Promise<{ sdp: string; offerId: string; answererPublicKey: string; answeredAt: number; matchedTags?: string[]; } | null>; /** * Combined polling for answers and ICE candidates */ poll(since?: number): Promise<{ answers: Array<{ offerId: string; answererPublicKey: string; sdp: string; answeredAt: number; matchedTags?: string[]; }>; iceCandidates: Record>; }>; /** * Add ICE candidates to a specific offer */ addOfferIceCandidates(offerId: string, candidates: RTCIceCandidateInit[]): Promise<{ count: number; offerId: string; }>; /** * Get ICE candidates for a specific offer */ getOfferIceCandidates(offerId: string, since?: number): Promise<{ candidates: IceCandidate[]; offerId: string; }>; }