{"version":3,"file":"frakContext-BGx4IVAK.cjs","names":[],"sources":["../src/config/clientId.ts","../src/utils/cache/lruMap.ts","../src/utils/cache/withCache.ts","../src/config/backendUrl.ts","../src/config/sdkConfigStore.ts","../src/utils/compression/b64.ts","../src/utils/compression/compress.ts","../src/utils/sso/sso.ts","../src/actions/openSso.ts","../src/utils/analytics/trackEvent.ts","../src/utils/url/queryParams.ts","../src/context/address.ts","../src/types/context.ts","../src/context/frakContextV2Codec.ts","../src/context/frakContext.ts"],"sourcesContent":["/**\n * Client ID utilities for anonymous tracking\n * Generates and persists a UUID fingerprint for referral attribution\n */\n\nconst CLIENT_ID_KEY = \"frak-client-id\";\n\n/**\n * Generate a UUID v4\n * Uses crypto.randomUUID if available, otherwise falls back to a manual implementation\n */\nfunction generateUUID(): string {\n    if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n        return crypto.randomUUID();\n    }\n    // Fallback for older browsers\n    return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n        const r = (Math.random() * 16) | 0;\n        const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n        return v.toString(16);\n    });\n}\n\n/**\n * Get the client ID from localStorage, creating one if it doesn't exist\n * @returns The client ID (UUID format)\n */\nexport function getClientId(): string {\n    if (typeof window === \"undefined\" || !window.localStorage) {\n        // SSR or no localStorage - generate ephemeral ID\n        console.warn(\n            \"[Frak SDK] No Window / localStorage available to save the clientId\"\n        );\n        return generateUUID();\n    }\n\n    let clientId = localStorage.getItem(CLIENT_ID_KEY);\n    if (!clientId) {\n        clientId = generateUUID();\n        localStorage.setItem(CLIENT_ID_KEY, clientId);\n    }\n    return clientId;\n}\n","/**\n * Map with a LRU (Least Recently Used) eviction policy.\n *\n * When the map exceeds `maxSize`, the least recently accessed entry is removed.\n * Accessing a key via `get()` promotes it to \"most recently used\".\n *\n * Adapted from viem's LruMap utility.\n * @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU\n */\nexport class LruMap<TValue = unknown> extends Map<string, TValue> {\n    maxSize: number;\n\n    constructor(size: number) {\n        super();\n        this.maxSize = size;\n    }\n\n    override get(key: string) {\n        const value = super.get(key);\n        if (super.has(key)) {\n            // Move to end (most recently used)\n            super.delete(key);\n            super.set(key, value as TValue);\n        }\n        return value;\n    }\n\n    override set(key: string, value: TValue) {\n        if (super.has(key)) super.delete(key);\n        super.set(key, value);\n        // Evict least recently used if over capacity\n        if (this.maxSize && this.size > this.maxSize) {\n            const firstKey = super.keys().next().value;\n            if (firstKey !== undefined) super.delete(firstKey);\n        }\n        return this;\n    }\n}\n","import { LruMap } from \"./lruMap\";\n\ntype CacheEntry<TData> = {\n    data: TData;\n    created: number;\n};\n\n/** Default cache time: 30 seconds */\nexport const DEFAULT_CACHE_TIME = 30_000;\n\n/** Short negative cache to avoid flooding on transient failures */\nconst NEGATIVE_CACHE_TIME = 1_000;\n\ntype CacheStore = {\n    /** In-flight promises (dedup concurrent calls) */\n    promiseCache: LruMap<Promise<unknown>>;\n    /** Resolved responses (TTL-based) */\n    responseCache: LruMap<CacheEntry<unknown>>;\n    /** Recently failed keys to avoid request floods */\n    failureCache: LruMap<number>;\n};\n\n// Allocate the three LruMaps lazily on first use rather than at module init.\n// Eager top-level allocation is a side effect at odds with `sideEffects: false`\n// and runs for every consumer that merely imports this module.\nlet store: CacheStore | undefined;\nfunction caches(): CacheStore {\n    if (!store) {\n        store = {\n            promiseCache: new LruMap<Promise<unknown>>(1024),\n            responseCache: new LruMap<CacheEntry<unknown>>(1024),\n            failureCache: new LruMap<number>(1024),\n        };\n    }\n    return store;\n}\n\ntype WithCacheOptions = {\n    /** The key to cache the data against */\n    cacheKey: string;\n    /** Time in ms that cached data will remain valid. Default: 30_000 (30s). Set to 0 to disable caching. */\n    cacheTime?: number;\n};\n\n/**\n * Returns the result of a given promise, and caches the result for\n * subsequent invocations against a provided cache key.\n *\n * Also deduplicates concurrent calls — if multiple callers request the same\n * cache key while the promise is pending, they share the same promise.\n *\n * @example\n * ```ts\n * // First call fetches, subsequent calls return cached data for 30s\n * const data = await withCache(\n *     () => client.request({ method: \"frak_getMerchantInformation\" }),\n *     { cacheKey: \"merchantInfo\", cacheTime: 30_000 }\n * );\n * ```\n */\nexport async function withCache<TData>(\n    fn: () => Promise<TData>,\n    { cacheKey, cacheTime = DEFAULT_CACHE_TIME }: WithCacheOptions\n): Promise<TData> {\n    const { promiseCache, responseCache, failureCache } = caches();\n    // Check response cache — return immediately if fresh\n    if (cacheTime > 0) {\n        const cached = responseCache.get(cacheKey) as\n            | CacheEntry<TData>\n            | undefined;\n        if (cached) {\n            const age = Date.now() - cached.created;\n            if (age < cacheTime) return cached.data;\n        }\n    }\n\n    // Check if this key recently failed — back off briefly\n    const lastFailure = failureCache.get(cacheKey);\n    if (lastFailure && Date.now() - lastFailure < NEGATIVE_CACHE_TIME) {\n        throw new Error(`Cache: ${cacheKey} recently failed, backing off`);\n    }\n\n    // Check if there's already a pending promise (dedup concurrent calls)\n    let promise = promiseCache.get(cacheKey) as Promise<TData> | undefined;\n    if (!promise) {\n        promise = fn();\n        promiseCache.set(cacheKey, promise);\n    }\n\n    try {\n        const data = await promise;\n        // Store the response with a timestamp\n        responseCache.set(cacheKey, { data, created: Date.now() });\n        // Clear any previous failure\n        failureCache.delete(cacheKey);\n        return data;\n    } catch (error) {\n        // Record the failure timestamp\n        failureCache.set(cacheKey, Date.now());\n        throw error;\n    } finally {\n        // Clear the promise cache so subsequent calls can re-fetch after TTL\n        promiseCache.delete(cacheKey);\n    }\n}\n\n/**\n * Get a cache handle for a specific key, useful for manual invalidation.\n *\n * @example\n * ```ts\n * // Invalidate merchant info cache after a mutation\n * getCache(\"frak_getMerchantInformation\").clear();\n * ```\n */\nexport function getCache(cacheKey: string) {\n    return {\n        /** Clear both the pending promise and the cached response */\n        clear: () => {\n            const { promiseCache, responseCache } = caches();\n            promiseCache.delete(cacheKey);\n            responseCache.delete(cacheKey);\n        },\n        /** Check if a non-expired response exists */\n        has: (cacheTime: number = DEFAULT_CACHE_TIME) => {\n            const { responseCache } = caches();\n            const cached = responseCache.get(cacheKey);\n            if (!cached) return false;\n            return Date.now() - cached.created < cacheTime;\n        },\n    };\n}\n\n/**\n * Clear all cached data (both pending promises and resolved responses).\n * Called automatically when the client is destroyed.\n */\nexport function clearAllCache() {\n    if (!store) return;\n    store.promiseCache.clear();\n    store.responseCache.clear();\n    store.failureCache.clear();\n}\n","/**\n * Default production backend URL\n */\nconst DEFAULT_BACKEND_URL = \"https://backend.frak.id\";\n\n/**\n * Check if wallet URL is local development (port 3000 or 3010)\n */\nfunction isLocalDevelopment(walletUrl: string): boolean {\n    return (\n        walletUrl.includes(\"localhost:3000\") ||\n        walletUrl.includes(\"localhost:3010\")\n    );\n}\n\n/**\n * Derive backend URL from wallet URL\n * Maps wallet URLs to their corresponding backend URLs\n */\nfunction deriveBackendUrl(walletUrl: string): string {\n    if (isLocalDevelopment(walletUrl)) {\n        return \"https://localhost:3030\";\n    }\n    // Dev environment\n    if (\n        walletUrl.includes(\"wallet-dev.frak.id\") ||\n        walletUrl.includes(\"wallet.gcp-dev.frak.id\")\n    ) {\n        return \"https://backend.gcp-dev.frak.id\";\n    }\n    // Production\n    return DEFAULT_BACKEND_URL;\n}\n\n/**\n * Get the backend URL for API calls\n * Tries to derive from SDK config, falls back to production\n *\n * @param walletUrl - Optional wallet URL to derive from (overrides global config)\n */\nexport function getBackendUrl(walletUrl?: string): string {\n    // If explicit walletUrl provided, derive from it\n    if (walletUrl) {\n        return deriveBackendUrl(walletUrl);\n    }\n\n    // Try to get from global FrakSetup config\n    if (typeof window !== \"undefined\") {\n        const configWalletUrl = (\n            window as {\n                FrakSetup?: { client?: { config?: { walletUrl?: string } } };\n            }\n        ).FrakSetup?.client?.config?.walletUrl;\n\n        if (configWalletUrl) {\n            return deriveBackendUrl(configWalletUrl);\n        }\n    }\n\n    // Fallback to production\n    return DEFAULT_BACKEND_URL;\n}\n","/**\n * SDK config store — reactive singleton for the resolved merchant config.\n *\n * State lives directly on `window.__frakSdkConfig`.\n * Reactivity is handled via the `frak:config` CustomEvent on `window`.\n * Resolved configs are cached in localStorage (30 s TTL, stale-while-revalidate).\n *\n * Backend fetch responses are cached and deduplicated via `withCache`.\n * Also owns the `frak-merchant-id` sessionStorage compatibility key.\n */\n\nimport type { Language } from \"../types/config\";\nimport type {\n    MerchantConfigResponse,\n    SdkResolvedConfig,\n} from \"../types/resolvedConfig\";\nimport { clearAllCache, withCache } from \"../utils/cache\";\nimport { getBackendUrl } from \"./backendUrl\";\n\nconst GLOBAL_KEY = \"__frakSdkConfig\";\nconst CACHE_TTL = 30_000; // 30 seconds\nconst DEFAULT_CACHE_KEY = \"frak-config-cache\";\nconst MERCHANT_ID_KEY = \"frak-merchant-id\";\n\nconst cacheState = { key: DEFAULT_CACHE_KEY };\n\nconst isBrowser = typeof window !== \"undefined\";\n\ntype CacheEntry = { config: SdkResolvedConfig; timestamp: number };\n\ndeclare global {\n    interface Window {\n        [GLOBAL_KEY]?: SdkResolvedConfig;\n    }\n    interface WindowEventMap {\n        \"frak:config\": CustomEvent<SdkResolvedConfig>;\n    }\n}\n\nfunction freshEmptyConfig(): SdkResolvedConfig {\n    return { isResolved: false, merchantId: \"\" };\n}\n\n// ---------------------------------------------------------------------------\n// localStorage cache (with in-memory parsed copy)\n// ---------------------------------------------------------------------------\n\nlet memoryEntry: CacheEntry | null = null;\n\nfunction loadCacheEntry(): CacheEntry | null {\n    if (!isBrowser) return null;\n    try {\n        const raw = localStorage.getItem(cacheState.key);\n        if (!raw) return null;\n        const entry: CacheEntry = JSON.parse(raw);\n        if (!entry.config?.isResolved) return null;\n        memoryEntry = entry;\n        return entry;\n    } catch {\n        return null;\n    }\n}\n\nfunction readCache(): SdkResolvedConfig | undefined {\n    return (memoryEntry ?? loadCacheEntry())?.config;\n}\n\nfunction isCacheFresh(): boolean {\n    const entry = memoryEntry ?? loadCacheEntry();\n    if (!entry) return false;\n    return Date.now() - entry.timestamp < CACHE_TTL;\n}\n\nfunction writeCache(config: SdkResolvedConfig): void {\n    if (!isBrowser || !config.isResolved) return;\n    try {\n        const entry: CacheEntry = { config, timestamp: Date.now() };\n        localStorage.setItem(cacheState.key, JSON.stringify(entry));\n        memoryEntry = entry;\n    } catch {}\n}\n\nfunction removeCache(): void {\n    if (!isBrowser) return;\n    memoryEntry = null;\n    try {\n        localStorage.removeItem(cacheState.key);\n    } catch {}\n}\n\n// ---------------------------------------------------------------------------\n// Initialise window-backed config (lazily, on first access)\n// ---------------------------------------------------------------------------\n\n// Seed `window.__frakSdkConfig` from the localStorage cache. Do NOT call this at\n// module top-level: that side effect contradicts `sideEffects: false` (a bundler\n// may legally drop it) and forces a localStorage read on every consumer at import.\n// It is invoked lazily from `getConfig` instead.\nfunction initConfig(): void {\n    if (!isBrowser) return;\n    if (window[GLOBAL_KEY]) return;\n    window[GLOBAL_KEY] = readCache() ?? freshEmptyConfig();\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getConfig(): SdkResolvedConfig {\n    if (!isBrowser) return freshEmptyConfig();\n    initConfig();\n    return window[GLOBAL_KEY] ?? freshEmptyConfig();\n}\n\nfunction dispatch(config: SdkResolvedConfig): void {\n    if (!isBrowser) return;\n    window.dispatchEvent(new CustomEvent(\"frak:config\", { detail: config }));\n}\n\nfunction getTargetDomain(domain?: string): string {\n    return domain ?? (isBrowser ? window.location.hostname : \"\");\n}\n\n// ---------------------------------------------------------------------------\n// Merchant config fetching (resolve)\n// ---------------------------------------------------------------------------\n\nasync function fetchFromBackend(\n    targetDomain: string,\n    walletUrl?: string,\n    lang?: Language\n): Promise<MerchantConfigResponse | undefined> {\n    try {\n        const backendUrl = getBackendUrl(walletUrl);\n        const langParam = lang ? `&lang=${encodeURIComponent(lang)}` : \"\";\n        const response = await fetch(\n            `${backendUrl}/user/merchant/resolve?domain=${encodeURIComponent(targetDomain)}${langParam}`\n        );\n\n        if (!response.ok) {\n            console.warn(\n                `[Frak SDK] Merchant lookup failed for domain ${targetDomain}: ${response.status}`\n            );\n            return undefined;\n        }\n\n        const data = (await response.json()) as MerchantConfigResponse;\n\n        // Write compatibility sessionStorage key\n        if (isBrowser) {\n            try {\n                sessionStorage.setItem(MERCHANT_ID_KEY, data.merchantId);\n            } catch {}\n        }\n\n        return data;\n    } catch (error) {\n        console.warn(\"[Frak SDK] Failed to fetch merchant config:\", error);\n        return undefined;\n    }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport const sdkConfigStore = {\n    getConfig,\n\n    get isResolved(): boolean {\n        return getConfig().isResolved;\n    },\n\n    get isCacheFresh(): boolean {\n        return isCacheFresh();\n    },\n\n    setCacheScope(domain: string, lang?: string): void {\n        const suffix = `${domain}:${lang ?? \"\"}`;\n        cacheState.key = `${DEFAULT_CACHE_KEY}:${suffix}`;\n        memoryEntry = null;\n    },\n\n    setConfig(config: SdkResolvedConfig): void {\n        if (isBrowser) window[GLOBAL_KEY] = config;\n        writeCache(config);\n        dispatch(config);\n\n        // Keep sessionStorage merchantId in sync\n        if (isBrowser && config.merchantId) {\n            try {\n                sessionStorage.setItem(MERCHANT_ID_KEY, config.merchantId);\n            } catch {}\n        }\n    },\n\n    reset(): void {\n        const next = readCache() ?? freshEmptyConfig();\n        if (isBrowser) window[GLOBAL_KEY] = next;\n        dispatch(next);\n    },\n\n    clearCache(): void {\n        removeCache();\n        clearAllCache();\n        if (isBrowser) {\n            try {\n                sessionStorage.removeItem(MERCHANT_ID_KEY);\n            } catch {}\n        }\n    },\n\n    resolve(\n        domain?: string,\n        walletUrl?: string,\n        lang?: Language\n    ): Promise<MerchantConfigResponse | undefined> {\n        const targetDomain = getTargetDomain(domain);\n        if (!targetDomain) {\n            return Promise.resolve(undefined);\n        }\n\n        const cacheKey = `sdkConfig:${targetDomain}:${lang ?? \"\"}`;\n\n        return withCache(\n            async () => {\n                const result = await fetchFromBackend(\n                    targetDomain,\n                    walletUrl,\n                    lang\n                );\n                // Throw on failure so withCache doesn't cache undefined\n                if (!result) {\n                    throw new Error(\"Config resolution returned empty\");\n                }\n                return result;\n            },\n            { cacheKey, cacheTime: Number.POSITIVE_INFINITY }\n        ).catch(() => undefined);\n    },\n\n    getMerchantId(): string | undefined {\n        const config = getConfig();\n        if (config.isResolved && config.merchantId) {\n            return config.merchantId;\n        }\n\n        if (isBrowser) {\n            try {\n                return sessionStorage.getItem(MERCHANT_ID_KEY) ?? undefined;\n            } catch {}\n        }\n        return undefined;\n    },\n\n    async resolveMerchantId(\n        domain?: string,\n        walletUrl?: string\n    ): Promise<string | undefined> {\n        const fast = sdkConfigStore.getMerchantId();\n        if (fast) return fast;\n\n        const config = await sdkConfigStore.resolve(domain, walletUrl);\n        return config?.merchantId;\n    },\n};\n","/**\n * Encode a buffer to a base64url encoded string\n * @param buffer The buffer to encode\n * @returns The encoded string\n */\nexport function base64urlEncode(buffer: Uint8Array): string {\n    return btoa(Array.from(buffer, (b) => String.fromCharCode(b)).join(\"\"))\n        .replace(/\\+/g, \"-\")\n        .replace(/\\//g, \"_\")\n        .replace(/=+$/, \"\");\n}\n\n/**\n * Decode a base64url encoded string\n * @param value The value to decode\n * @returns The decoded value\n */\nexport function base64urlDecode(value: string): Uint8Array {\n    const m = value.length % 4;\n    return Uint8Array.from(\n        atob(\n            value\n                .replace(/-/g, \"+\")\n                .replace(/_/g, \"/\")\n                .padEnd(value.length + (m === 0 ? 0 : 4 - m), \"=\")\n        ),\n        (c) => c.charCodeAt(0)\n    );\n}\n","import { jsonEncode } from \"@frak-labs/frame-connector\";\nimport { base64urlEncode } from \"./b64\";\n\n/**\n * Compress json data\n * @param data\n * @ignore\n */\nexport function compressJsonToB64(data: unknown): string {\n    return base64urlEncode(jsonEncode(data));\n}\n","import type { Hex } from \"viem\";\nimport type { PrepareSsoParamsType, SsoMetadata } from \"../../types\";\nimport { compressJsonToB64 } from \"../compression/compress\";\n\nexport type AppSpecificSsoMetadata = SsoMetadata & {\n    name?: string;\n    css?: string;\n};\n\n/**\n * The full SSO params that will be used for compression\n */\nexport type FullSsoParams = Omit<PrepareSsoParamsType, \"metadata\"> & {\n    metadata: AppSpecificSsoMetadata;\n    merchantId: string;\n    clientId: string;\n};\n\n/**\n * Generate SSO URL with compressed parameters\n * This mirrors the wallet's getOpenSsoLink() function\n *\n * @param walletUrl - Base wallet URL (e.g., \"https://wallet.frak.id\")\n * @param params - SSO parameters\n * @param merchantId - Merchant identifier\n * @param name - Application name\n * @param clientId - Client identifier for identity tracking\n * @param css - Optional custom CSS\n * @returns Complete SSO URL ready to open in popup or redirect\n *\n * @example\n * ```ts\n * const ssoUrl = generateSsoUrl(\n *   \"https://wallet.frak.id\",\n *   { metadata: { logoUrl: \"...\" }, directExit: true },\n *   \"0x123...\",\n *   \"My App\"\n * );\n * // Returns: https://wallet.frak.id/sso?p=<compressed_base64>\n * ```\n */\nexport function generateSsoUrl(\n    walletUrl: string,\n    params: PrepareSsoParamsType,\n    merchantId: string,\n    name: string | undefined,\n    clientId: string,\n    css?: string\n): string {\n    // Build full params with app-specific metadata\n    const fullParams: FullSsoParams = {\n        redirectUrl: params.redirectUrl,\n        directExit: params.directExit,\n        lang: params.lang,\n        merchantId,\n        metadata: {\n            name,\n            css,\n            logoUrl: params.metadata?.logoUrl,\n            homepageLink: params.metadata?.homepageLink,\n        },\n        clientId,\n    };\n\n    // Compress params to minimal format\n    const compressedParam = ssoParamsToCompressed(fullParams);\n\n    // Encode to base64url\n    const compressedString = compressJsonToB64(compressedParam);\n\n    // Build URL matching wallet's expected format: /sso?p=<compressed>\n    const ssoUrl = new URL(walletUrl);\n    ssoUrl.pathname = \"/sso\";\n    ssoUrl.searchParams.set(\"p\", compressedString);\n\n    return ssoUrl.toString();\n}\n\n/**\n * Map full sso params to compressed sso params\n * @param params\n */\nfunction ssoParamsToCompressed(params: FullSsoParams): CompressedSsoData {\n    return {\n        r: params.redirectUrl,\n        cId: params.clientId,\n        d: params.directExit,\n        l: params.lang,\n        m: params.merchantId,\n        md: {\n            n: params.metadata?.name,\n            css: params.metadata?.css,\n            l: params.metadata?.logoUrl,\n            h: params.metadata?.homepageLink,\n        },\n    };\n}\n\n/**\n * Type of compressed the sso data\n */\nexport type CompressedSsoData = {\n    // Potential id from backend\n    id?: Hex;\n    // Client id\n    cId: string;\n    // redirect url\n    r?: string;\n    // direct exit\n    d?: boolean;\n    // language\n    l?: \"en\" | \"fr\";\n    // merchant id\n    m: string;\n    // metadata\n    md: {\n        n?: string;\n        css?: string;\n        l?: string;\n        h?: string;\n    };\n};\n","import { getClientId } from \"../config/clientId\";\nimport { sdkConfigStore } from \"../config/sdkConfigStore\";\nimport type {\n    FrakClient,\n    OpenSsoParamsType,\n    OpenSsoReturnType,\n} from \"../types\";\nimport { generateSsoUrl } from \"../utils/sso/sso\";\n\n// SSO popup configuration\nexport const ssoPopupFeatures =\n    \"menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800\";\nexport const ssoPopupName = \"frak-sso\";\n\n/**\n * Function used to open the SSO\n * @param client - The current Frak Client\n * @param args - The SSO parameters\n *\n * @description Two SSO flow modes:\n *\n * **Redirect Mode** (openInSameWindow: true):\n * - Wallet generates URL and triggers redirect\n * - Used when redirectUrl is provided\n *\n * **Popup Mode** (openInSameWindow: false/omitted):\n * - SDK generates URL client-side (or uses provided ssoPopupUrl)\n * - Opens popup synchronously (prevents popup blockers)\n * - Waits for SSO completion via postMessage\n *\n * @example\n * First we build the sso metadata\n * ```ts\n * // Build the metadata\n * const metadata: SsoMetadata = {\n *     logoUrl: \"https://my-app.com/logo.png\",\n *     homepageLink: \"https://my-app.com\",\n * };\n * ```\n *\n * Then, either use it with direct exit (and so user is directly redirected to your website), or a custom redirect URL\n * :::code-group\n * ```ts [Popup (default)]\n * // Opens in popup, SDK generates URL automatically\n * await openSso(frakConfig, {\n *     directExit: true,\n *     metadata,\n * });\n * ```\n * ```ts [Redirect]\n * // Opens in same window with redirect\n * await openSso(frakConfig, {\n *     redirectUrl: \"https://my-app.com/frak-sso\",\n *     metadata,\n *     openInSameWindow: true,\n * });\n * ```\n * ```ts [Custom popup URL]\n * // Advanced: provide custom SSO URL\n * const { ssoUrl } = await prepareSso(frakConfig, { metadata });\n * await openSso(frakConfig, {\n *     metadata,\n *     ssoPopupUrl: `${ssoUrl}&custom=param`,\n * });\n * ```\n * :::\n */\nexport async function openSso(\n    client: FrakClient,\n    inputArgs: OpenSsoParamsType\n): Promise<OpenSsoReturnType> {\n    const { metadata, customizations, walletUrl } = client.config;\n\n    // Apply default: when no redirectUrl is provided we want the SSO popup\n    // to close itself after completion. Without this default the popup\n    // sticks on the success screen and the \"Redirect now\" button is a no-op.\n    const args: OpenSsoParamsType = {\n        ...inputArgs,\n        directExit: inputArgs.directExit ?? !inputArgs.redirectUrl,\n    };\n\n    // Check if redirect mode (default to true if redirectUrl present)\n    const isRedirectMode = args.openInSameWindow ?? !!args.redirectUrl;\n    if (isRedirectMode) {\n        // Redirect flow: Wallet generates URL and triggers redirect via lifecycle event\n        // This must happen on wallet side because only the iframe can trigger the redirect\n        return await client.request({\n            method: \"frak_openSso\",\n            params: [args, metadata.name, customizations?.css],\n        });\n    }\n\n    // Popup flow: Generate URL on SDK side and open synchronously\n    // This ensures window.open() is called in same tick as user gesture (no popup blocker)\n\n    // Step 1: Generate or use provided SSO URL\n    const ssoUrl =\n        args.ssoPopupUrl ??\n        generateSsoUrl(\n            walletUrl ?? \"https://wallet.frak.id\",\n            args,\n            (await sdkConfigStore.resolveMerchantId()) ?? \"\",\n            metadata.name,\n            getClientId(),\n            customizations?.css\n        );\n\n    // Step 2: Open popup synchronously (critical for popup blocker prevention)\n    const popup = window.open(ssoUrl, ssoPopupName, ssoPopupFeatures);\n    if (!popup) {\n        throw new Error(\n            \"Popup was blocked. Please allow popups for this site.\"\n        );\n    }\n    popup.focus();\n\n    // Step 3: Wait for SSO completion via RPC\n    // The wallet iframe will resolve this when SSO page sends sso_complete message\n    const result = await client.request({\n        method: \"frak_openSso\",\n        params: [args, metadata.name, customizations?.css],\n    });\n\n    return result ?? {};\n}\n","import type { FrakClient } from \"../../types\";\nimport type { SdkEventMap } from \"./events\";\n\n/**\n * Track an analytics event via the SDK's OpenPanel instance.\n * Fire-and-forget — silently catches errors so analytics never break a\n * partner integration.\n *\n * The client must be passed explicitly because the OpenPanel instance is\n * scoped to each `FrakClient` (a partner site may hold multiple iframes).\n *\n * @param client - The Frak client instance (no-op if undefined)\n * @param event - Typed event name from the SDK event map\n * @param properties - Typed properties for the given event\n */\nexport function trackEvent<K extends keyof SdkEventMap>(\n    client: FrakClient | undefined,\n    event: K,\n    properties?: SdkEventMap[K]\n): void {\n    if (!client) {\n        console.debug(\"[Frak] No client provided, skipping event tracking\");\n        return;\n    }\n\n    try {\n        client.openPanel?.track(\n            event as string,\n            properties as Record<string, unknown> | undefined\n        );\n    } catch (e) {\n        console.debug(\"[Frak] Failed to track event:\", event, e);\n    }\n}\n","/**\n * Case-insensitive helpers for reading URL query parameters.\n *\n * Some email tools (Klaviyo, Omnisend, Customer.io …) and a few browsers\n * lowercase the entire URL before the recipient opens it, so a mixed-case key\n * authored as `frakAction` or `fCtx` can arrive as `frakaction` / `fctx`. A\n * plain `searchParams.get(\"frakAction\")` would miss it. Matching the key\n * ignoring case keeps UI-triggering and referral params working regardless of\n * how the link was mangled in transit.\n *\n * Note: only the key is normalised. An encoded value (base64url, tokens) is not\n * recoverable if the same channel also lowercased the value itself.\n */\n\n/**\n * Read a query parameter, matching its key case-insensitively.\n *\n * An exact-case match wins when present, so a canonical link is never shadowed\n * by a mangled duplicate (`?fctx=stale&fCtx=real` resolves to `real`). Only when\n * the exact key is absent do we scan for a case-folded variant.\n *\n * @returns the param value, or `null` when no key matches.\n */\nexport function getQueryParamCaseInsensitive(\n    searchParams: URLSearchParams,\n    key: string\n): string | null {\n    const exact = searchParams.get(key);\n    if (exact !== null) return exact;\n\n    const target = key.toLowerCase();\n    for (const [paramKey, value] of searchParams) {\n        if (paramKey.toLowerCase() === target) {\n            return value;\n        }\n    }\n    return null;\n}\n\n/**\n * Delete every query parameter whose key matches `key` case-insensitively.\n *\n * Keys are collected before deletion because mutating a `URLSearchParams`\n * while iterating it skips entries.\n */\nexport function deleteQueryParamCaseInsensitive(\n    searchParams: URLSearchParams,\n    key: string\n): void {\n    const target = key.toLowerCase();\n    const matchingKeys = [...searchParams.keys()].filter(\n        (paramKey) => paramKey.toLowerCase() === target\n    );\n    for (const paramKey of matchingKeys) {\n        searchParams.delete(paramKey);\n    }\n}\n","import type { Address } from \"viem\";\n\n/**\n * Address utilities — minimal, dependency-free replacements for the subset of\n * `viem` helpers we used to import. Keeping these in-house lets the SDK ship\n * without pulling viem's checksum/keccak/error chain into the bundle.\n *\n * Scope is intentionally narrow:\n *  - `isAddress`: shape-only validation (no EIP-55 checksum)\n *  - `areAddressesEqual`: case-insensitive equality\n *  - `addressToBytes` / `bytesToAddress`: fixed 20-byte conversion\n */\n\n/** Matches a 0x-prefixed 40-char hex string regardless of case. */\nconst ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;\n\n/**\n * Check whether a value is a syntactically valid Ethereum address.\n *\n * This intentionally skips EIP-55 checksum validation: the SDK never produces\n * checksum-cased payloads, and downstream consumers (wallet, indexer) treat\n * addresses case-insensitively. Avoiding the checksum path drops keccak256 +\n * @noble/hashes from the bundle.\n */\nexport function isAddress(value: unknown): value is Address {\n    return typeof value === \"string\" && ADDRESS_REGEX.test(value);\n}\n\n/**\n * Case-insensitive equality check for two Ethereum addresses.\n *\n * Both inputs are assumed to be syntactically valid addresses; callers that\n * receive untrusted input should validate via {@link isAddress} first.\n */\nexport function areAddressesEqual(a: Address, b: Address): boolean {\n    return a.toLowerCase() === b.toLowerCase();\n}\n\n/**\n * Decode a 20-byte Ethereum address into a fixed-size Uint8Array(20).\n *\n * Throws when the input is not exactly `0x` + 40 hex chars — callers wrap\n * the call in try/catch (see {@link FrakContextManager.compress}) so any\n * malformed input degrades to a graceful undefined return.\n */\nexport function addressToBytes(address: Address): Uint8Array {\n    const bytes = new Uint8Array(20);\n    for (let i = 0; i < 20; i++) {\n        const byte = Number.parseInt(\n            address.substring(2 + i * 2, 4 + i * 2),\n            16\n        );\n        if (Number.isNaN(byte)) {\n            throw new Error(`Invalid address: ${address}`);\n        }\n        bytes[i] = byte;\n    }\n    return bytes;\n}\n\n/** Lookup table avoids `padStart` overhead in the hot encode loop. */\nconst HEX_BYTE = /*#__PURE__*/ Array.from({ length: 256 }, (_, i) =>\n    i.toString(16).padStart(2, \"0\")\n);\n\n/**\n * Encode a 20-byte Uint8Array (or a 20-byte subarray view) into a lowercase\n * hex Ethereum address. The caller MUST guarantee `bytes.length === 20`.\n */\nexport function bytesToAddress(bytes: Uint8Array): Address {\n    let out = \"0x\";\n    for (let i = 0; i < 20; i++) {\n        out += HEX_BYTE[bytes[i]];\n    }\n    return out as Address;\n}\n","import type { Address } from \"viem\";\n\n/**\n * V1 (legacy) Frak Context — contains only the referrer wallet address.\n * Used for backward compatibility with old sharing links.\n * @ignore\n */\nexport type FrakContextV1 = {\n    /** Referrer wallet address */\n    r: Address;\n};\n\n/**\n * V2 Frak Context — anonymous-first referral context with optional wallet.\n *\n * Carries merchant context (`m`) and creation timestamp (`t`) unconditionally.\n * Identifies the sharer via either the anonymous clientId (`c`) or, when the\n * sharer is authenticated, the stronger wallet identifier (`w`). A valid V2\n * context MUST contain at least one of `c` or `w`; both may be present when\n * a logged-in user shares a link (best attribution signal).\n *\n * `w` takes precedence as the source of truth because the wallet is bound to\n * the user's WebAuthn credential, survives localStorage clears, and is global\n * across merchants — unlike `c`, which is a per-browser UUID.\n *\n * @ignore\n */\nexport type FrakContextV2 = {\n    /** Version discriminator */\n    v: 2;\n    /** Merchant ID (UUID) */\n    m: string;\n    /** Link creation timestamp (epoch seconds) */\n    t: number;\n    /** Sharer's anonymous clientId (UUID from localStorage). Optional when `w` is provided. */\n    c?: string;\n    /** Sharer's wallet address. Preferred source of truth when the sharer is authenticated. Optional when `c` is provided. */\n    w?: Address;\n};\n\n/**\n * The current Frak Context — union of all versions.\n *\n * - No `v` field → V1 (legacy wallet address)\n * - `v: 2` → V2 (anonymous clientId-based)\n *\n * @ignore\n */\nexport type FrakContext = FrakContextV1 | FrakContextV2;\n\n/**\n * Type guard: check if a context is V1 (legacy wallet address).\n * @param ctx - The Frak context to check\n * @returns True if the context is a V1 context\n */\nexport function isV1Context(ctx: FrakContext): ctx is FrakContextV1 {\n    return \"r\" in ctx && !(\"v\" in ctx);\n}\n\n/**\n * Type guard: check if a context is V2 (anonymous clientId-based).\n * @param ctx - The Frak context to check\n * @returns True if the context is a V2 context\n */\nexport function isV2Context(ctx: FrakContext): ctx is FrakContextV2 {\n    return \"v\" in ctx && ctx.v === 2;\n}\n","/**\n * Binary codec for {@link FrakContextV2}.\n *\n * Produces a compact, URL-safe byte layout (~65% smaller than the previous\n * JSON+base64url format). See the layout below.\n *\n * ## Wire layout\n *\n * ```text\n * byte 0:          header\n *                    bits 0-3  version         (= 2)\n *                    bit  4    has_c flag\n *                    bit  5    has_w flag\n *                    bits 6-7  reserved (must be 0)\n * bytes 1..16:     merchant UUID   (16 bytes, mandatory)\n * bytes 17..20:    timestamp       (uint32 big-endian, Unix seconds)\n * bytes 21..36:    client UUID     (16 bytes, only when has_c is set)\n * bytes 37..56:    wallet address  (20 bytes, only when has_w is set)\n * ```\n *\n * Size variants (before base64url):\n * - has_c only:     37 bytes\n * - has_w only:     41 bytes\n * - has_c + has_w:  57 bytes\n *\n * V1 payloads are exactly 20 bytes (raw wallet address); the byte lengths never\n * overlap, so the outer decoder can disambiguate purely on length.\n *\n * @ignore\n */\nimport type { Address } from \"viem\";\nimport type { FrakContextV2 } from \"../types\";\nimport { addressToBytes, bytesToAddress, isAddress } from \"./address\";\n\nconst VERSION_V2 = 0x02;\nconst VERSION_MASK = 0x0f;\nconst FLAG_HAS_C = 1 << 4;\nconst FLAG_HAS_W = 1 << 5;\nconst RESERVED_MASK = 0xc0;\n\nconst UUID_BYTES = 16;\nconst TIMESTAMP_BYTES = 4;\nconst ADDRESS_BYTES = 20;\nconst HEADER_BYTES = 1;\n\nconst UUID_RE =\n    /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Strict lower-case UUID validation (RFC 4122 shape, any version/variant). */\nfunction isUuid(value: unknown): value is string {\n    return typeof value === \"string\" && UUID_RE.test(value);\n}\n\n/** Parse a canonical UUID string into 16 raw bytes. */\nfunction uuidToBytes(uuid: string): Uint8Array {\n    const hex = uuid.replace(/-/g, \"\");\n    const out = new Uint8Array(UUID_BYTES);\n    for (let i = 0; i < UUID_BYTES; i++) {\n        out[i] = Number.parseInt(hex.substring(i * 2, i * 2 + 2), 16);\n    }\n    return out;\n}\n\n/** Format 16 raw bytes as a canonical 8-4-4-4-12 UUID string. */\nfunction bytesToUuid(bytes: Uint8Array): string {\n    let hex = \"\";\n    for (let i = 0; i < UUID_BYTES; i++) {\n        hex += bytes[i].toString(16).padStart(2, \"0\");\n    }\n    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Encode a {@link FrakContextV2} into its binary wire format.\n *\n * Returns `null` when the context fails runtime validation (missing fields,\n * malformed UUIDs, timestamp outside uint32 range, invalid wallet).\n */\nexport function encodeFrakContextV2(ctx: FrakContextV2): Uint8Array | null {\n    if (!isUuid(ctx.m)) return null;\n    if (!Number.isInteger(ctx.t) || ctx.t < 0 || ctx.t > 0xff_ff_ff_ff)\n        return null;\n\n    const hasC = typeof ctx.c === \"string\" && ctx.c.length > 0;\n    const hasW = typeof ctx.w === \"string\" && isAddress(ctx.w);\n    if (!hasC && !hasW) return null;\n    if (hasC && !isUuid(ctx.c)) return null;\n\n    const size =\n        HEADER_BYTES +\n        UUID_BYTES +\n        TIMESTAMP_BYTES +\n        (hasC ? UUID_BYTES : 0) +\n        (hasW ? ADDRESS_BYTES : 0);\n\n    const buf = new Uint8Array(size);\n    const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n    let offset = 0;\n\n    buf[offset++] =\n        VERSION_V2 | (hasC ? FLAG_HAS_C : 0) | (hasW ? FLAG_HAS_W : 0);\n\n    buf.set(uuidToBytes(ctx.m), offset);\n    offset += UUID_BYTES;\n\n    view.setUint32(offset, ctx.t, false);\n    offset += TIMESTAMP_BYTES;\n\n    if (hasC) {\n        buf.set(uuidToBytes(ctx.c as string), offset);\n        offset += UUID_BYTES;\n    }\n\n    if (hasW) {\n        buf.set(addressToBytes(ctx.w as Address), offset);\n        offset += ADDRESS_BYTES;\n    }\n\n    return buf;\n}\n\n/**\n * Decode a binary {@link FrakContextV2} payload.\n *\n * Returns `null` when:\n * - the header version nibble is not V2\n * - reserved header bits are set (guards against future-version payloads)\n * - neither flag is set (invalid: V2 must carry `c` and/or `w`)\n * - the byte length does not match the length implied by the header flags\n * - the decoded wallet does not pass `isAddress` (defense-in-depth against\n *   crafted payloads that round-trip by length but carry junk)\n */\nexport function decodeFrakContextV2(buf: Uint8Array): FrakContextV2 | null {\n    if (buf.length < HEADER_BYTES + UUID_BYTES + TIMESTAMP_BYTES) return null;\n\n    const header = buf[0];\n    if ((header & VERSION_MASK) !== VERSION_V2) return null;\n    if ((header & RESERVED_MASK) !== 0) return null;\n\n    const hasC = (header & FLAG_HAS_C) !== 0;\n    const hasW = (header & FLAG_HAS_W) !== 0;\n    if (!hasC && !hasW) return null;\n\n    const expected =\n        HEADER_BYTES +\n        UUID_BYTES +\n        TIMESTAMP_BYTES +\n        (hasC ? UUID_BYTES : 0) +\n        (hasW ? ADDRESS_BYTES : 0);\n    if (buf.length !== expected) return null;\n\n    let offset = HEADER_BYTES;\n\n    const m = bytesToUuid(buf.subarray(offset, offset + UUID_BYTES));\n    offset += UUID_BYTES;\n\n    const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n    const t = view.getUint32(offset, false);\n    offset += TIMESTAMP_BYTES;\n\n    const out: FrakContextV2 = { v: 2, m, t };\n\n    if (hasC) {\n        out.c = bytesToUuid(buf.subarray(offset, offset + UUID_BYTES));\n        offset += UUID_BYTES;\n    }\n\n    if (hasW) {\n        const walletHex = bytesToAddress(\n            buf.subarray(offset, offset + ADDRESS_BYTES)\n        );\n        if (!isAddress(walletHex)) return null;\n        out.w = walletHex;\n        offset += ADDRESS_BYTES;\n    }\n\n    return out;\n}\n\n/**\n * Quick length-based probe to tell V1 (20-byte wallet address) apart from a V2\n * binary payload. Exposed so the outer decoder can branch without re-parsing.\n */\nexport function isV2BinaryLength(byteLength: number): boolean {\n    return (\n        byteLength ===\n            HEADER_BYTES + UUID_BYTES + TIMESTAMP_BYTES + UUID_BYTES ||\n        byteLength ===\n            HEADER_BYTES + UUID_BYTES + TIMESTAMP_BYTES + ADDRESS_BYTES ||\n        byteLength ===\n            HEADER_BYTES +\n                UUID_BYTES +\n                TIMESTAMP_BYTES +\n                UUID_BYTES +\n                ADDRESS_BYTES\n    );\n}\n","import type {\n    AttributionParams,\n    FrakContext,\n    FrakContextV1,\n    FrakContextV2,\n} from \"../types\";\nimport { isV2Context } from \"../types\";\nimport { base64urlDecode, base64urlEncode } from \"../utils/compression/b64\";\nimport {\n    deleteQueryParamCaseInsensitive,\n    getQueryParamCaseInsensitive,\n} from \"../utils/url/queryParams\";\nimport { addressToBytes, bytesToAddress, isAddress } from \"./address\";\nimport { decodeFrakContextV2, encodeFrakContextV2 } from \"./frakContextV2Codec\";\n\n/**\n * URL parameter key for the Frak referral context\n */\nconst contextKey = \"fCtx\";\n\n/**\n * Compress a Frak context into a URL-safe string.\n *\n * - V2 contexts are encoded using a compact binary layout (see\n *   {@link encodeFrakContextV2}) then base64url-encoded.\n * - V1 contexts encode the wallet address as raw bytes (base64url).\n *\n * @param context - The context to compress (V1 or V2)\n * @returns A compressed base64url string, or undefined on failure\n */\nfunction compress(context?: FrakContextV1 | FrakContextV2): string | undefined {\n    if (!context) return;\n    try {\n        if (isV2Context(context)) {\n            const encoded = encodeFrakContextV2(context);\n            if (!encoded) return undefined;\n            return base64urlEncode(encoded);\n        }\n\n        // V1 legacy: compress wallet address as raw bytes\n        const bytes = addressToBytes(context.r);\n        return base64urlEncode(bytes);\n    } catch (e) {\n        console.error(\"Error compressing Frak context\", { e, context });\n    }\n    return undefined;\n}\n\n/**\n * Decompress a base64url string back into a Frak context.\n *\n * V1 (exactly 20 bytes) and V2 (37, 41, or 57 bytes) are distinguished by\n * their decoded byte length, so there is no ambiguity.\n *\n * @param context - The compressed context string\n * @returns The decompressed FrakContext, or undefined on failure\n */\nfunction decompress(context?: string): FrakContext | undefined {\n    if (!context || context.length === 0) return;\n    try {\n        const bytes = base64urlDecode(context);\n\n        // V1 is a raw 20-byte wallet address; V2 binary is always longer\n        // and starts with a header byte whose low nibble is the version.\n        if (bytes.length !== 20) {\n            const v2 = decodeFrakContextV2(bytes);\n            if (v2) return v2;\n            return undefined;\n        }\n\n        const hex = bytesToAddress(bytes);\n        if (isAddress(hex)) {\n            return { r: hex };\n        }\n    } catch (e) {\n        console.error(\"Error decompressing Frak context\", { e, context });\n    }\n    return undefined;\n}\n\n/**\n * Parse a URL to extract the Frak referral context from the `fCtx` query parameter.\n *\n * The key is matched case-insensitively: some link channels (emails, messaging\n * apps) lowercase query-param keys in transit, so `fCtx` can arrive as `fctx`.\n *\n * @param args\n * @param args.url - The URL to parse\n * @returns The parsed FrakContext, or null if absent\n */\nfunction parse({ url }: { url: string }): FrakContext | null | undefined {\n    if (!url) return null;\n\n    const urlObj = new URL(url);\n    const frakContext = getQueryParamCaseInsensitive(\n        urlObj.searchParams,\n        contextKey\n    );\n    if (!frakContext) return null;\n\n    return decompress(frakContext);\n}\n\n/**\n * Default utm_source / via value when attribution is requested.\n */\nconst DEFAULT_ATTRIBUTION_SOURCE = \"frak\";\n\n/**\n * Resolve attribution defaults from the provided context.\n *\n * V2 contexts expose the merchantId (`m`) and, when anonymous, the clientId\n * (`c`), which feed `utm_campaign` and `ref` respectively. When V2 only carries\n * a wallet (`w`), `ref` is intentionally left unset — we don't want wallet\n * addresses leaking into UTM params. V1 contexts have no equivalent.\n */\nfunction resolveAttributionValues(\n    overrides: AttributionParams\n): Record<string, string | undefined> {\n    return {\n        utm_source: overrides.utmSource ?? DEFAULT_ATTRIBUTION_SOURCE,\n        utm_medium: overrides.utmMedium ?? undefined,\n        utm_campaign: overrides.utmCampaign ?? undefined,\n        utm_content: overrides.utmContent,\n        utm_term: overrides.utmTerm,\n        via: overrides.via ?? undefined,\n        ref: overrides.ref ?? undefined,\n    };\n}\n\n/**\n * Append attribution query params to a URL using gap-fill semantics.\n *\n * Existing params on the URL are preserved untouched (so merchant-provided\n * UTMs take precedence). Only missing keys are populated.\n */\nfunction applyAttributionParams(\n    urlObj: URL,\n    attribution?: AttributionParams\n): void {\n    const values = resolveAttributionValues(attribution ?? {});\n    for (const [key, value] of Object.entries(values)) {\n        if (value === undefined || value === \"\") continue;\n        if (urlObj.searchParams.has(key)) continue;\n        urlObj.searchParams.set(key, value);\n    }\n}\n\n/**\n * Add or replace the `fCtx` query parameter in a URL with the given context.\n *\n * Standard affiliation params (`utm_source`, `utm_medium`, `utm_campaign`,\n * `ref`, `via`, ...) are always appended using gap-fill semantics: pre-existing\n * params on the URL are preserved, defaults are derived from the context when\n * applicable, and `attribution` overrides take precedence when provided.\n *\n * @param args\n * @param args.url - The URL to update\n * @param args.context - The context to embed (V1 or V2)\n * @param args.attribution - Optional attribution overrides. Defaults are applied even when omitted.\n * @returns The updated URL string, or null on failure\n */\nfunction update({\n    url,\n    context,\n    attribution,\n}: {\n    url?: string;\n    context: FrakContextV1 | FrakContextV2;\n    attribution?: AttributionParams;\n}): string | null {\n    if (!url) return null;\n\n    const compressedContext = compress(context);\n    if (!compressedContext) return null;\n\n    const urlObj = new URL(url);\n    deleteQueryParamCaseInsensitive(urlObj.searchParams, contextKey);\n    urlObj.searchParams.set(contextKey, compressedContext);\n    applyAttributionParams(urlObj, attribution);\n    return urlObj.toString();\n}\n\n/**\n * Remove the `fCtx` query parameter from a URL.\n *\n * @param url - The URL to strip the context from\n * @returns The cleaned URL string\n */\nfunction remove(url: string): string {\n    const urlObj = new URL(url);\n    deleteQueryParamCaseInsensitive(urlObj.searchParams, contextKey);\n    return urlObj.toString();\n}\n\n/**\n * Replace the current browser URL with an updated Frak context.\n *\n * - If `context` is non-null, embeds it via {@link update}.\n * - If `context` is null, strips the context via {@link remove}.\n *\n * @param args\n * @param args.url - Base URL (defaults to `window.location.href`)\n * @param args.context - Context to set, or null to remove\n */\nfunction replaceUrl({\n    url: baseUrl,\n    context,\n}: {\n    url?: string;\n    context: FrakContextV1 | FrakContextV2 | null;\n}) {\n    if (!window.location?.href || typeof window === \"undefined\") {\n        console.error(\"No window found, can't update context\");\n        return;\n    }\n\n    const url = baseUrl ?? window.location.href;\n\n    let newUrl: string | null;\n    if (context !== null) {\n        newUrl = update({ url, context });\n    } else {\n        newUrl = remove(url);\n    }\n\n    if (!newUrl) return;\n\n    window.history.replaceState(null, \"\", newUrl.toString());\n}\n\n/**\n * Manager for Frak referral context in URLs.\n *\n * Handles compression, decompression, URL parsing, and browser history updates\n * for both V1 (wallet address) and V2 (anonymous clientId) referral contexts.\n */\nexport const FrakContextManager = {\n    compress,\n    decompress,\n    parse,\n    update,\n    remove,\n    replaceUrl,\n};\n"],"mappings":"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"}