import { ZodError } from 'zod'; /** * License tier levels */ type LicenseTier = 'free' | 'pro'; /** * Activation details for a licensed domain */ type LicenseActivation = { id: string; licenseKeyId: string; label: string; createdAt: string; modifiedAt: string | null; }; /** * License error types */ type LicenseError = 'invalid_key' | 'network_error' | 'parse_error' | 'activation_limit_reached' | 'domain_mismatch'; /** * Flat license state — single source of truth for validity. * Never derive validity from `tier` alone — a pro tier with * `status: 'expired'` is not valid. * * `renderKey` is set only when `status === 'valid'`. It is the * core anti-bypass mechanism consumed by ``. */ type LicenseState = { status: 'valid' | 'invalid' | 'expired' | 'revoked' | 'loading' | 'error'; tier: LicenseTier; activations: number; maxActivations: number; domain: string | null; expiresAt: string | null; /** * Local `Date.now()` captured when validation ran. Used for cache freshness * and the elapsed-time delta in `getDaysLeft`. NOT Polar's server timestamp — * use `serverValidatedAt` for that. Renaming this would break v1.0.x caches. */ validatedAt: number; /** * Polar `last_validated_at` parsed to Unix ms. Null on dev bypass, unlicensed, * invalid, and error states. Used by `getDaysLeft` to anchor trial countdowns * to server time and absorb client clock skew. */ serverValidatedAt?: number | null; renderKey: string | undefined; }; /** * Shape stored in localStorage. * * `keyHash` is set when the cache is written with a license key; readers * compare it against the current key's hash and invalidate on mismatch so * switching `licenseKey` does not return another key's cached state. */ type LicenseCache = { state: LicenseState; cachedAt: number; domain: string; keyHash?: string; }; /** * Config passed to validateLicenseKey() */ type LicenseConfig = { key: string; organizationId: string; }; /** * Raw Polar validate response (after camelCase transform) */ type PolarValidateResponse = { id: string; organizationId: string; status: 'granted' | 'revoked' | 'disabled'; key: string; limitActivations: number | null; usage: number; validations: number; lastValidatedAt: string; expiresAt: string | null; activation: { id: string; licenseKeyId: string; label: string; meta: Record; createdAt: string; modifiedAt: string | null; } | null; }; /** * Raw Polar activate response (after camelCase transform) */ type PolarActivateResponse = { id: string; licenseKeyId: string; label: string; meta: Record; createdAt: string; modifiedAt: string | null; licenseKey: { id: string; organizationId: string; status: 'granted' | 'revoked' | 'disabled'; limitActivations: number | null; usage: number; limitUsage: number | null; validations: number; lastValidatedAt: string; expiresAt: string | null; }; }; /** * Trial context slice. Null on `LicenseContextValue.trial` when no `trialDays` * is configured on ``. `isTrialing` is `daysLeft > 0`. */ type TrialContextValue = { daysLeft: number; isTrialing: boolean; }; /** * License context value (used by React integration). * * `isGated` / `isLoading` / `gracePeriodActive` are derived from `state` and * cache freshness once per validation, so consumers never need to read * localStorage on every render. `trial` is `null` when no `trialDays` is set * on ``. */ type LicenseContextValue = { state: LicenseState; refresh: () => Promise; isGated: boolean; isLoading: boolean; gracePeriodActive: boolean; trial: TrialContextValue | null; }; /** * License provider props */ type LicenseProviderProps = { licenseKey: string; organizationId?: string; /** * Override the issuer base URL. Precedence: this prop > the * `NEXT_PUBLIC_TOUR_KIT_LICENSE_API_BASE` env var > * `TOUR_KIT_LICENSE_API_BASE` env var > Polar default. Load-bearing for the * Polar → tourkit-dash issuer migration (plan/15f) — v1.x customers on * tourkit-dash set this prop (or the env var) without upgrading the SDK. * v2.x customers will get tourkit-dash as the default and only need this * prop for self-host or test environments. */ apiBase?: string; /** * Optional trial length in days. When set, `` exposes a * `trial` slice on the context and `` renders a countdown. * Trial state is CLIENT-DERIVED from `issuedAt + trialDays` because Polar's * `/v1/customer-portal/license-keys/validate` endpoint does not emit a * `tier` field (Phase 0 task 0.6, memory project_polar_api_findings.md #187). */ trialDays?: number; /** * Optional explicit trial start time (Unix ms). Production trials should * pass a stable signup/license-issued timestamp. When omitted, the provider * falls back to `state.serverValidatedAt ?? state.validatedAt` for demo-only * countdowns. */ trialIssuedAt?: number; children: React.ReactNode; onValidate?: (state: LicenseState) => void; onError?: (error: Error) => void; }; /** * License gate props for conditional rendering */ type LicenseGateProps = { require: 'pro'; children: React.ReactNode; fallback?: React.ReactNode; loading?: React.ReactNode; }; /** * License warning banner props */ type LicenseWarningProps = { message?: string; pricingUrl?: string; dismissible?: boolean; onDismiss?: () => void; className?: string; }; /** * Options bag accepted by every low-level Polar call and by `validateLicenseKey`. * * `apiBase` is the load-bearing knob for the Polar → tourkit-dash issuer * migration (plan/15f). When omitted the call falls through `resolveApiBase()` * to the env-var chain and finally the Polar default. */ type ValidateOptions = { apiBase?: string; }; declare class PolarApiError extends Error { readonly statusCode: number; constructor(statusCode: number, message: string); } declare class PolarParseError extends Error { readonly zodError: ZodError; constructor(zodError: ZodError); } declare function validateKey(key: string, organizationId: string, activationId?: string, options?: ValidateOptions): Promise; declare function activateKey(key: string, organizationId: string, label: string, options?: ValidateOptions): Promise; declare function deactivateKey(key: string, organizationId: string, activationId: string, options?: ValidateOptions): Promise; declare function validateLicenseKey(key: string, organizationId?: string, options?: ValidateOptions): Promise; /** * Default issuer URL. Polar in v1.x; flips to tourkit-dash in v2.x at T+90 * per plan/15m. v1.x customers point at tourkit-dash by overriding this * default — never by upgrading the SDK during the dual-run window. */ declare const DEFAULT_API_BASE = "https://api.polar.sh/v1/customer-portal/license-keys"; /** * Resolve which issuer base URL a call should hit. * * Precedence (highest first): * 1. `override` passed by the caller (`options.apiBase` on `validateLicenseKey`, * `validateKey`, `activateKey`, `deactivateKey`, or the `apiBase` prop on * ``). * 2. `process.env.NEXT_PUBLIC_TOUR_KIT_LICENSE_API_BASE` — Next.js client + * server (the same prefix used by `NEXT_PUBLIC_TOUR_KIT_LICENSE_KEY` so * customer apps configure both vars the same way). * 3. `process.env.TOUR_KIT_LICENSE_API_BASE` — server-side / Node / * Vite-with-define / customers using a non-Next.js bundler that inlines * bare env names. * 4. {@link DEFAULT_API_BASE}. * * The override mechanism is the load-bearing seam for the Polar → tourkit-dash * issuer migration (plan/15f). Without it a v1.x customer cannot point at the * cloud issuer until they upgrade to v2.x — which is the whole reason this * function exists. */ declare function resolveApiBase(override?: string): string; declare function getCurrentDomain(): string | null; declare function isDevEnvironment(): boolean; /** * True for ephemeral preview/tunnel hosts (see `EPHEMERAL_HOST_PATTERNS`) and * raw IP hosts. Callers treat these like dev: skip Polar, unlock Pro, consume * no activation slot. `isDevEnvironment()` hosts are excluded since they are * already handled by the dev bypass upstream. */ declare function isEphemeralHost(domain?: string | null): boolean; /** * Compares current hostname against the stored activation label. * Logs a console warning on mismatch. Soft enforcement only — * returns boolean but never blocks rendering. */ declare function validateDomainAtRender(activationLabel: string): boolean; /** * Consumer-supplied trial configuration. Passed to * along with the implicit `issuedAt` derived from the license's first validation * (or a future server-side field when Polar ships one). * * Polar's /v1/customer-portal/license-keys/validate endpoint does NOT emit a * `tier` field today (confirmed Phase 0 task 0.6, memory project_polar_api_findings.md * entry #187). Trial state is therefore CLIENT-DERIVED. If Polar adds server-side * trial signalling later, getDaysLeft will accept an optional server-provided * override (marked `FUTURE:` below) — additive, non-breaking. */ interface TrialConfig { /** Unix ms timestamp of when the trial started. Sourced from license issuance time. */ issuedAt: number; /** Length of the trial window in whole days. E.g. 14. */ trialDays: number; /** Local Date.now() timestamp captured when validation ran. */ validatedAt: number; /** Polar last_validated_at parsed to Unix ms. Null when unavailable. */ serverValidatedAt?: number | null; } /** * Compute days remaining in the trial window. Uses Polar's server validation * timestamp plus local elapsed time when available, and falls back to `now` * when no server anchor exists. Clamps to [0, trialDays]. * * The serverValidatedAt + (now - validatedAt) algebra absorbs client clock * skew: Polar gives the server anchor at validation time; the local clock only * contributes the (now - validatedAt) delta, which on a normal machine is the * real elapsed time and on a skewed machine is the same delta (skew cancels). * * @param config The trial config from . * @param now Override for testing. Defaults to Date.now(). * @returns Integer days remaining in [0, trialDays]. */ declare function getDaysLeft(config: TrialConfig, now?: number): number; export { DEFAULT_API_BASE as D, type LicenseState as L, type PolarActivateResponse as P, type TrialConfig as T, type ValidateOptions as V, type LicenseActivation as a, type LicenseCache as b, type LicenseConfig as c, type LicenseError as d, PolarApiError as e, PolarParseError as f, type PolarValidateResponse as g, activateKey as h, deactivateKey as i, getCurrentDomain as j, getDaysLeft as k, isDevEnvironment as l, isEphemeralHost as m, validateKey as n, validateLicenseKey as o, type LicenseContextValue as p, type LicenseProviderProps as q, resolveApiBase as r, type LicenseGateProps as s, type LicenseTier as t, type LicenseWarningProps as u, validateDomainAtRender as v, type TrialContextValue as w };