import { ApiConfig, ApiResponse } from './types.js'; /** * Per-request options. A superset of `RequestInit`: the two extra fields are * consumed here and never reach `fetch`. */ export interface ApiRequestOptions extends RequestInit { /** * The widget key for THIS request, overriding the module-global one. * * The global (`config.js`'s `setWidgetKey`) is a single-instance * convenience: two `` elements on one page with different * keys both end up using whichever connected last, and the loser then 403s * on every strict read and renders an empty calendar with no visible cause. * `initVenueCalendar({ widgetKey })` threads the element's own key down to * here so each instance authorizes with its own. The option name is FROZEN * as `widgetKey` — VenueCalendar.js passes it under that name. * * Components read their mount's key with getInstanceWidgetKey() * (widget-key-context.ts) at init and pass it down as `{ widgetKey }`; on * the hosted routes there is no mount key and the module global applies. */ widgetKey?: string; /** * Skip the global `onError` handler for this call. * * For endpoints where a non-2xx is an EXPECTED, handled answer rather than a * fault — /tax-preview's 422 "this venue has no tax address, show $0" is the * live example. Without this the caller has to bypass the whole api-client * with a raw `fetch` just to avoid emitting API-error noise on the happy * path, which is how the hardcoded `https://get-micdrop.com` tax-preview URL * in CartView survived (and broke every venue-hosted embed, which cannot * reach a URL pinned to our own origin under its own CSP/consent rules). */ suppressOnError?: boolean; } /** * Observers of backend failure CODES, for cross-cutting state that no single * caller owns. * * The concrete case is the buy path's `ORIGIN_NOT_REGISTERED` (embed audit * 2026-08-22): every cart/promo/payment call can raise it, but the thing that * has to react is the calendar shell, which makes none of those calls. Rather * than thread the code back through six call sites, whoever cares subscribes * here. Observers see EVERY failure code, so they must filter, and a throwing * observer is contained — a broken subscriber must not turn a handled API * error into an unhandled one. */ type ApiErrorCodeObserver = (_code: string) => void; /** Subscribe to backend failure codes. Returns an unsubscribe function. */ export declare function onApiErrorCode(observer: ApiErrorCodeObserver): () => void; /** * A response, plus the machine-readable failure code when there was one. * * Widens `ApiResponse` rather than replacing it, so every existing caller that * annotates `ApiResponse` keeps compiling. */ export interface ApiResult extends ApiResponse { /** * The backend's `code` for a failure — `WIDGET_KEY_INVALID`, * `WIDGET_VENUE_MISMATCH`, `WIDGET_ORG_MISMATCH`, `NOT_FOUND`, … Before this * existed the only thing that survived a failed request was a human-readable * `error` string handed to `logger.error`, so a widget embedded on the wrong * domain, a key scoped to another org, and a genuine outage were all the same * "something went wrong" to the UI. The shell keys its message off this. */ errorCode?: string; /** * The CALLER's AbortSignal fired — a superseded in-flight request, not a * failure. Distinguished from a timeout (which also surfaces as an * AbortError) because callers debouncing a request must leave their state * untouched on an abort and reset it on a real failure. */ aborted?: boolean; } /** * Merge the widget API key header into a request. * * ON A THIRD-PARTY ORIGIN THE KEY IS REQUIRED, AND EVERY STRICT PUBLIC READ * MUST GO THROUGH HERE. The widget-key middleware in auth * (auth/middlewares/widget_auth.go) has two rejection paths, not one: a * provided-but-invalid key is WIDGET_KEY_INVALID, and a KEYLESS request * carrying a foreign Origin header is WIDGET_KEY_REQUIRED. Keyless is served * only to a first-party origin (get-micdrop.com and its subdomains, localhost) * or to a caller that sends no Origin header at all — which is why a curl probe * and a browser disagree here, and why a bug in this layer is invisible from * the terminal. * * So a single call site that reaches a strict endpoint without this helper 403s * for every real visitor on a venue's own domain while looking fine in local * dev and in any origin-less probe. The strict surface is enumerated in the * strictPublic group in auth/internal/api/v2/router.go: the event detail read, * the venue-by-slug read and the widget-config read are all on it. * * A key is also validated against the request's Origin header, so a key sent * from a domain that is not on its allow-list is a hard 403 — "the read failed" * can mean the key is missing OR that the embedding domain was never * registered, and the response body's code field is what tells those apart. * * @param headers - the caller's headers, passed through unchanged when there is * no key to add. * @param explicitKey - this instance's key, preferred over the module global. */ export declare function withWidgetKey(headers?: HeadersInit, explicitKey?: string): Record | HeadersInit | undefined; /** * The Error handed to the global `onError` handler, carrying the same code the * returned `ApiResult` does. `onError` receives an `Error` by contract, so the * code rides as a property rather than being flattened into the message. */ export declare class ApiRequestError extends Error { readonly code?: string; readonly statusCode?: number; constructor(message: string, code?: string, statusCode?: number); } export declare function configureApi(config: Partial): void; export declare function getApiConfig(): Required; export declare function getPublicBaseUrl(): string; export declare function getLegacyPublicUrl(): string; export declare function getOrdersV2Url(): string; /** @deprecated IP is now resolved server-side via request headers. */ export declare function getClientIP(): Promise; export declare function apiGet(endpoint: string, options?: ApiRequestOptions): Promise>; export declare function apiPost(endpoint: string, body?: unknown, options?: ApiRequestOptions): Promise>; export declare function apiPut(endpoint: string, body?: unknown, options?: ApiRequestOptions): Promise>; export declare function apiDelete(endpoint: string, options?: ApiRequestOptions): Promise>; /** One priced line in a tax quote. `amountCents` is the LINE total, not unit. */ export interface TaxPreviewLineItem { amountCents: number; reference: string; quantity: number; } export interface TaxPreviewRequest { venueId: number; /** WHICH SHOW the quote is for — honours a per-event tax exemption (MIC-2606). */ eventId?: number; lineItems: TaxPreviewLineItem[]; /** Order-level promo discount in integer cents (MIC-2567). */ discountCents?: number; /** Ask the server for the authoritative service fee alongside the tax. */ computeServiceFee?: boolean; } /** Both fields are integer CENTS, and both are contract-optional. */ export interface TaxPreviewResponse { taxTotal?: number; serviceFeeCents?: number; } /** * Server-authoritative tax (and, optionally, service fee) quote. * * Exists so the cart summary does not have to bypass this client to get one. * The raw `fetch` it replaces hardcoded `https://get-micdrop.com/...`, which is * the whole third-party-domain bug in one line: an embed on a venue's own * domain could not retarget it at all, so a self-hosted or proxied deployment * quoted tax from the wrong origin (or, under a strict CSP, not at all). * * `suppressOnError` defaults TRUE because a 422 here is a documented, handled * answer — "this venue has no address configured for Stripe Tax, show $0" — and * routing it through the global onError handler is what made the raw fetch look * necessary. Callers still see the 422 in `statusCode` and must apply their own * fallback; genuine 5xx/network failures are theirs to log. * * @param req - the quote request; `lineItems` may be empty, but prefer not * calling at all in that case (the server has nothing to price). * @param options.signal - abort a superseded quote. An abort comes back as * `{ aborted: true }`, NOT as a failure, so a debouncing caller can return * early without clobbering the numbers it is still showing. */ export declare function taxPreview(req: TaxPreviewRequest, options?: { signal?: AbortSignal; suppressOnError?: boolean; widgetKey?: string; }): Promise>; /** * Whether a GET to `url` answers 2xx. Reads no body and reports nothing. * * For an endpoint whose availability IS the answer and whose body is not JSON: * the Apple Wallet .pkpass download is a 501 until the backend can sign passes, * and a buyer is only shown the badge once it answers. */ export declare function apiResponds(url: string, options?: Pick): Promise; /** * Simple fetch wrapper that returns parsed JSON or null on error. * Inherits the global timeout + retry policy for GETs. * * NOTE: this was slated for deletion on the premise that it had no production * callers. It has them in events.ts, orders.ts and recommendations.ts, so it * stays. What it does NOT do any more is send * credentials; see the apiRequest comment for why that is the point. */ export declare function simpleFetch(url: string, options?: RequestInit & Pick): Promise; export {};