@frak-labs/core-sdk
Version:
Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.
612 lines (611 loc) • 20.8 kB
text/typescript
import { $ as SdkResolvedConfig, U as SsoMetadata, V as PrepareSsoParamsType, X as MerchantConfigResponse, a as FrakContextV1, et as Currency, g as TokenAmountType, i as FrakContext, o as FrakContextV2, ot as AttributionDefaults, rt as Language, s as FrakClient, st as AttributionParams, tt as FrakWalletSdkConfig } from "./openSso-BErs7_Bg.cjs";
import { Hex } from "viem";
//#region src/types/compression.d.ts
/**
* The received encoded data from a client
* -> The encoded should contain a HashProtectedData once decoded
* @ignore
*/
type CompressedData = Uint8Array;
/**
* The encoded data to send to a client / received by a client
* @ignore
*/
type HashProtectedData<DataType> = Readonly<DataType & {
validationHash: string;
}>;
/**
* Represent a key provider used for the hashed and secure compression
* @ignore
*/
type KeyProvider<DataType> = (value: DataType) => string[];
//#endregion
//#region src/clients/createIFrameFrakClient.d.ts
/**
* Create a new iframe Frak client
* @param args
* @param args.config - The configuration to use for the Frak Wallet SDK.
* When `config.domain` is set, it is used to resolve the correct merchant config in tunneled/proxied environments (e.g. Shopify dev with Cloudflare tunnel).
* @param args.iframe - The iframe to use for the communication
* @returns The created Frak Client
*
* @example
* const frakConfig: FrakWalletSdkConfig = {
* metadata: {
* name: "My app title",
* },
* }
* const iframe = await createIframe({ config: frakConfig });
* const client = createIFrameFrakClient({ config: frakConfig, iframe });
*/
declare function createIFrameFrakClient({
config,
iframe
}: {
config: FrakWalletSdkConfig;
iframe: HTMLIFrameElement;
}): FrakClient;
//#endregion
//#region src/clients/setupClient.d.ts
/**
* Directly setup the Frak client with an iframe
* Return when the FrakClient is ready (setup and communication estbalished with the wallet)
*
* @param config - The configuration to use for the Frak Wallet SDK
* @returns a Promise with the Frak Client
*
* @example
* const frakConfig: FrakWalletSdkConfig = {
* metadata: {
* name: "My app title",
* },
* }
* const client = await setupClient({ config: frakConfig });
*/
declare function setupClient({
config
}: {
config: FrakWalletSdkConfig;
}): Promise<FrakClient | undefined>;
//#endregion
//#region src/config/backendUrl.d.ts
/**
* Get the backend URL for API calls
* Tries to derive from SDK config, falls back to production
*
* @param walletUrl - Optional wallet URL to derive from (overrides global config)
*/
declare function getBackendUrl(walletUrl?: string): string;
//#endregion
//#region src/config/clientId.d.ts
/**
* Client ID utilities for anonymous tracking
* Generates and persists a UUID fingerprint for referral attribution
*/
/**
* Get the client ID from localStorage, creating one if it doesn't exist
* @returns The client ID (UUID format)
*/
declare function getClientId(): string;
//#endregion
//#region src/config/sdkConfigStore.d.ts
declare const GLOBAL_KEY = "__frakSdkConfig";
declare global {
interface Window {
[GLOBAL_KEY]?: SdkResolvedConfig;
}
interface WindowEventMap {
"frak:config": CustomEvent<SdkResolvedConfig>;
}
}
declare function getConfig(): SdkResolvedConfig;
declare const sdkConfigStore: {
getConfig: typeof getConfig;
readonly isResolved: boolean;
readonly isCacheFresh: boolean;
setCacheScope(domain: string, lang?: string): void;
setConfig(config: SdkResolvedConfig): void;
reset(): void;
clearCache(): void;
resolve(domain?: string, walletUrl?: string, lang?: Language): Promise<MerchantConfigResponse | undefined>;
getMerchantId(): string | undefined;
resolveMerchantId(domain?: string, walletUrl?: string): Promise<string | undefined>;
};
//#endregion
//#region src/constants.d.ts
/**
* Deep link scheme for Frak Wallet mobile app.
*
* Replaced at build time via tsdown/Vite `define`. Defaults to the prod scheme;
* in-monorepo dev builds (listener at wallet-dev.frak.id) override this with
* `frakwallet-dev://` so deep links open the dev wallet variant (id.frak.wallet.dev).
* External integrators consuming the published NPM/CDN bundle always see the prod scheme.
*/
declare const DEEP_LINK_SCHEME: string;
//#endregion
//#region src/context/frakContext.d.ts
/**
* Compress a Frak context into a URL-safe string.
*
* - V2 contexts are encoded using a compact binary layout (see
* {@link encodeFrakContextV2}) then base64url-encoded.
* - V1 contexts encode the wallet address as raw bytes (base64url).
*
* @param context - The context to compress (V1 or V2)
* @returns A compressed base64url string, or undefined on failure
*/
declare function compress(context?: FrakContextV1 | FrakContextV2): string | undefined;
/**
* Decompress a base64url string back into a Frak context.
*
* V1 (exactly 20 bytes) and V2 (37, 41, or 57 bytes) are distinguished by
* their decoded byte length, so there is no ambiguity.
*
* @param context - The compressed context string
* @returns The decompressed FrakContext, or undefined on failure
*/
declare function decompress(context?: string): FrakContext | undefined;
/**
* Parse a URL to extract the Frak referral context from the `fCtx` query parameter.
*
* @param args
* @param args.url - The URL to parse
* @returns The parsed FrakContext, or null if absent
*/
declare function parse({
url
}: {
url: string;
}): FrakContext | null | undefined;
/**
* Add or replace the `fCtx` query parameter in a URL with the given context.
*
* Standard affiliation params (`utm_source`, `utm_medium`, `utm_campaign`,
* `ref`, `via`, ...) are always appended using gap-fill semantics: pre-existing
* params on the URL are preserved, defaults are derived from the context when
* applicable, and `attribution` overrides take precedence when provided.
*
* @param args
* @param args.url - The URL to update
* @param args.context - The context to embed (V1 or V2)
* @param args.attribution - Optional attribution overrides. Defaults are applied even when omitted.
* @returns The updated URL string, or null on failure
*/
declare function update({
url,
context,
attribution
}: {
url?: string;
context: FrakContextV1 | FrakContextV2;
attribution?: AttributionParams;
}): string | null;
/**
* Remove the `fCtx` query parameter from a URL.
*
* @param url - The URL to strip the context from
* @returns The cleaned URL string
*/
declare function remove(url: string): string;
/**
* Replace the current browser URL with an updated Frak context.
*
* - If `context` is non-null, embeds it via {@link update}.
* - If `context` is null, strips the context via {@link remove}.
*
* @param args
* @param args.url - Base URL (defaults to `window.location.href`)
* @param args.context - Context to set, or null to remove
*/
declare function replaceUrl({
url: baseUrl,
context
}: {
url?: string;
context: FrakContextV1 | FrakContextV2 | null;
}): void;
/**
* Manager for Frak referral context in URLs.
*
* Handles compression, decompression, URL parsing, and browser history updates
* for both V1 (wallet address) and V2 (anonymous clientId) referral contexts.
*/
declare const FrakContextManager: {
compress: typeof compress;
decompress: typeof decompress;
parse: typeof parse;
update: typeof update;
remove: typeof remove;
replaceUrl: typeof replaceUrl;
};
//#endregion
//#region src/context/mergeAttribution.d.ts
/**
* Inputs for {@link mergeAttribution}.
*/
type MergeAttributionInput = {
/**
* Per-call attribution override passed to actions like `displaySharingPage`.
*
* - `null` explicitly disables attribution (no UTM/ref/via params are added).
* - `undefined` means "no per-call override" — defaults apply if present.
* - An object (including `{}`) merges field-by-field with defaults.
*/
perCall: AttributionParams | null | undefined;
/**
* Pre-merged merchant-level defaults (backend config > SDK static config).
* `utm_content` is intentionally absent from this shape.
*/
defaults?: AttributionDefaults;
/**
* Per-product `utm_content` override (from the currently selected
* `SharingPageProduct`). Takes precedence over `perCall.utmContent`.
*/
productUtmContent?: string;
};
/**
* Merge the three attribution layers into a single {@link AttributionParams}
* value suitable for `FrakContextManager.update`.
*
* Priority per field:
* 1. `perCall` (wins)
* 2. `defaults` (merchant-level, backend > SDK static, already pre-merged)
* 3. Hardcoded fallbacks resolved later by `FrakContextManager`
*
* Special rules:
* - `perCall === null` returns `undefined` (explicit disable: no UTM/ref/via).
* - `perCall === undefined` (no opinion) yields at least `{}` so `FrakContextManager`
* applies its hardcoded defaults (utm_source=frak, utm_medium=referral,
* utm_campaign=<merchantId>, ref=<clientId>, via=frak).
* - `utm_content` never comes from `defaults`; only `productUtmContent` or
* `perCall.utmContent` can populate it.
*/
declare function mergeAttribution({
perCall,
defaults,
productUtmContent
}: MergeAttributionInput): AttributionParams | undefined;
//#endregion
//#region src/utils/analytics/events/component.d.ts
type ButtonBaseProps = {
placement?: string;
target_interaction?: string;
has_reward?: boolean;
};
type BannerVariant = "referral" | "inapp";
type BannerOutcome = "clicked" | "dismissed";
type PostPurchaseVariant = "referrer" | "referee";
type ShareClickAction = "share-modal" | "embedded-wallet" | "sharing-page";
type SdkComponentEventMap = {
share_button_clicked: ButtonBaseProps & {
click_action: ShareClickAction;
};
share_modal_error: ButtonBaseProps & {
error?: string;
};
open_in_app_clicked: {
placement?: string;
path: string;
};
app_not_installed: {
placement?: string;
path: string;
};
banner_impression: {
placement?: string;
variant: BannerVariant;
has_reward?: boolean;
};
banner_resolved: {
placement?: string;
variant: BannerVariant;
outcome: BannerOutcome;
};
post_purchase_impression: {
placement?: string;
variant: PostPurchaseVariant;
has_reward?: boolean;
};
post_purchase_clicked: {
placement?: string;
variant: PostPurchaseVariant;
};
};
//#endregion
//#region src/utils/analytics/events/lifecycle.d.ts
type SdkHandshakeFailureReason = "timeout" | "origin" | "asset_push" | "unknown";
type SdkLifecycleEventMap = {
sdk_initialized: {
sdkVersion?: string;
};
sdk_iframe_connected: {
handshake_duration_ms: number;
};
sdk_iframe_handshake_failed: {
reason: SdkHandshakeFailureReason;
};
/**
* Emitted by the CDN bootstrap when `initFrakSdk()` throws before a
* client is available. Uses a transient OpenPanel instance so broken
* partner integrations are still captured.
*/
sdk_init_failed: {
reason: string;
config_missing?: boolean;
};
};
//#endregion
//#region src/utils/analytics/events/referral.d.ts
type SdkReferralEventMap = {
user_referred_started: {
referrer?: string;
referrerClientId?: string;
referrerWallet?: string;
walletStatus?: string;
};
user_referred_completed: {
status: "success";
};
};
//#endregion
//#region src/utils/analytics/events/index.d.ts
/**
* Merged SDK event map. Consumed by the SDK's typed `trackEvent`.
* Stays isolated from wallet-shared because the SDK ships in partner
* bundles (different OpenPanel client id, no wallet-shared dependency
* allowed — see `packages/wallet-shared/AGENTS.md`).
*/
type SdkEventMap = SdkLifecycleEventMap & SdkComponentEventMap & SdkReferralEventMap;
//#endregion
//#region src/utils/analytics/trackEvent.d.ts
/**
* Track an analytics event via the SDK's OpenPanel instance.
* Fire-and-forget — silently catches errors so analytics never break a
* partner integration.
*
* The client must be passed explicitly because the OpenPanel instance is
* scoped to each `FrakClient` (a partner site may hold multiple iframes).
*
* @param client - The Frak client instance (no-op if undefined)
* @param event - Typed event name from the SDK event map
* @param properties - Typed properties for the given event
*/
declare function trackEvent<K extends keyof SdkEventMap>(client: FrakClient | undefined, event: K, properties?: SdkEventMap[K]): void;
//#endregion
//#region src/utils/browser/deepLinkWithFallback.d.ts
/**
* Options for deep link with fallback
*/
type DeepLinkFallbackOptions = {
/** Timeout in ms before triggering fallback (default: 2500ms) */timeout?: number; /** Callback invoked when fallback is triggered (app not installed) */
onFallback?: () => void;
};
/**
* Trigger a deep link with visibility-based fallback detection.
*
* Uses the Page Visibility API to detect if the app opened (page goes hidden).
* If the page remains visible after the timeout, assumes app is not installed
* and invokes the onFallback callback.
*
* On Chromium Android, converts custom scheme to intent:// URL to avoid
* the "Continue to app?" confirmation bar.
*
* @param deepLink - The deep link URL to trigger (e.g., "frakwallet://wallet")
* @param options - Optional configuration (timeout, onFallback callback)
*/
declare function triggerDeepLinkWithFallback(deepLink: string, options?: DeepLinkFallbackOptions): void;
//#endregion
//#region src/utils/browser/inAppBrowser.d.ts
/**
* Whether the current device runs iOS (including iPadOS 13+).
*/
declare const isIOS: boolean;
/**
* Check if the current device is a mobile device (iOS, iPadOS, Android,
* webOS, BlackBerry, IEMobile, Opera Mini). Reuses {@link isIOS} so the
* iPadOS-13+ Macintosh heuristic stays in one place.
*/
declare function isMobile(): boolean;
/**
* Whether the current browser is a social media in-app browser
* (Instagram, Facebook).
*/
declare const isInAppBrowser: boolean;
/**
* Redirect to external browser from in-app WebView.
*
* - **iOS**: Uses `x-safari-https://` scheme — server-side 302 redirects
* to custom URL schemes are silently swallowed by WKWebView.
* Direct `window.location.href` assignment works (confirmed iOS 17+).
*
* - **Android**: Uses backend `/common/social` endpoint which returns a PDF
* Content-Type response, forcing the WebView to hand off to the default browser.
*
* @param targetUrl - The URL to open in the external browser
*/
declare function redirectToExternalBrowser(targetUrl: string): void;
//#endregion
//#region src/utils/cache/withCache.d.ts
type WithCacheOptions = {
/** The key to cache the data against */cacheKey: string; /** Time in ms that cached data will remain valid. Default: 30_000 (30s). Set to 0 to disable caching. */
cacheTime?: number;
};
/**
* Returns the result of a given promise, and caches the result for
* subsequent invocations against a provided cache key.
*
* Also deduplicates concurrent calls — if multiple callers request the same
* cache key while the promise is pending, they share the same promise.
*
* @example
* ```ts
* // First call fetches, subsequent calls return cached data for 30s
* const data = await withCache(
* () => client.request({ method: "frak_getMerchantInformation" }),
* { cacheKey: "merchantInfo", cacheTime: 30_000 }
* );
* ```
*/
declare function withCache<TData>(fn: () => Promise<TData>, {
cacheKey,
cacheTime
}: WithCacheOptions): Promise<TData>;
/**
* Clear all cached data (both pending promises and resolved responses).
* Called automatically when the client is destroyed.
*/
declare function clearAllCache(): void;
//#endregion
//#region src/utils/compression/b64.d.ts
/**
* Encode a buffer to a base64url encoded string
* @param buffer The buffer to encode
* @returns The encoded string
*/
declare function base64urlEncode(buffer: Uint8Array): string;
/**
* Decode a base64url encoded string
* @param value The value to decode
* @returns The decoded value
*/
declare function base64urlDecode(value: string): Uint8Array;
//#endregion
//#region src/utils/compression/compress.d.ts
/**
* Compress json data
* @param data
* @ignore
*/
declare function compressJsonToB64(data: unknown): string;
//#endregion
//#region src/utils/compression/decompress.d.ts
/**
* Decompress json data
* @param data
* @ignore
*/
declare function decompressJsonFromB64<T>(data: string): T | null;
//#endregion
//#region src/utils/format/formatAmount.d.ts
/**
* Format a numeric amount as a localized currency string
* @param amount - The raw numeric amount to format
* @param currency - Optional currency config; defaults to EUR/fr-FR when omitted
* @returns Localized currency string (e.g. "1 500 €", "$1,500")
*/
declare function formatAmount(amount: number, currency?: Currency): string;
//#endregion
//#region src/utils/format/getCurrencyAmountKey.d.ts
/**
* Get the currency amount key for a given currency
* @param currency - The currency to use
* @returns The currency amount key
*/
declare function getCurrencyAmountKey(currency?: Currency): keyof TokenAmountType;
//#endregion
//#region src/utils/format/getSupportedCurrency.d.ts
/**
* Get the supported currency for a given currency
* @param currency - The currency to use
* @returns The supported currency
*/
declare function getSupportedCurrency(currency?: Currency): Currency;
//#endregion
//#region src/utils/iframe/iframeHelper.d.ts
/**
* Base props for the iframe
* @ignore
*/
declare const baseIframeProps: {
id: string;
name: string;
title: string;
allow: string;
style: {
width: string;
height: string;
border: string;
position: string;
zIndex: number;
top: string;
left: string;
colorScheme: string;
};
};
/**
* Find an iframe within window.opener by pathname
*
* When a popup is opened via window.open from an iframe, window.opener points to
* the parent window, not the iframe itself. This utility searches through all frames
* in window.opener to find an iframe matching the specified pathname.
*
* @param pathname - The pathname to search for (default: "/listener")
* @returns The matching iframe window, or null if not found
*
* @example
* ```typescript
* // Find the default /listener iframe
* const listenerIframe = findIframeInOpener();
*
* // Find a custom iframe
* const customIframe = findIframeInOpener("/my-custom-iframe");
* ```
*/
declare function findIframeInOpener(pathname?: string): Window | null;
//#endregion
//#region src/utils/sso/sso.d.ts
type AppSpecificSsoMetadata = SsoMetadata & {
name?: string;
css?: string;
};
/**
* The full SSO params that will be used for compression
*/
type FullSsoParams = Omit<PrepareSsoParamsType, "metadata"> & {
metadata: AppSpecificSsoMetadata;
merchantId: string;
clientId: string;
};
/**
* Generate SSO URL with compressed parameters
* This mirrors the wallet's getOpenSsoLink() function
*
* @param walletUrl - Base wallet URL (e.g., "https://wallet.frak.id")
* @param params - SSO parameters
* @param merchantId - Merchant identifier
* @param name - Application name
* @param clientId - Client identifier for identity tracking
* @param css - Optional custom CSS
* @returns Complete SSO URL ready to open in popup or redirect
*
* @example
* ```ts
* const ssoUrl = generateSsoUrl(
* "https://wallet.frak.id",
* { metadata: { logoUrl: "..." }, directExit: true },
* "0x123...",
* "My App"
* );
* // Returns: https://wallet.frak.id/sso?p=<compressed_base64>
* ```
*/
declare function generateSsoUrl(walletUrl: string, params: PrepareSsoParamsType, merchantId: string, name: string | undefined, clientId: string, css?: string): string;
/**
* Type of compressed the sso data
*/
type CompressedSsoData = {
id?: Hex;
cId: string;
r?: string;
d?: boolean;
l?: "en" | "fr";
m: string;
md: {
n?: string;
css?: string;
l?: string;
h?: string;
};
};
//#endregion
export { getClientId as A, SdkEventMap as C, FrakContextManager as D, mergeAttribution as E, HashProtectedData as F, KeyProvider as I, setupClient as M, createIFrameFrakClient as N, DEEP_LINK_SCHEME as O, CompressedData as P, trackEvent as S, MergeAttributionInput as T, isInAppBrowser as _, baseIframeProps as a, DeepLinkFallbackOptions as b, getCurrencyAmountKey as c, compressJsonToB64 as d, base64urlDecode as f, isIOS as g, withCache as h, generateSsoUrl as i, getBackendUrl as j, sdkConfigStore as k, formatAmount as l, clearAllCache as m, CompressedSsoData as n, findIframeInOpener as o, base64urlEncode as p, FullSsoParams as r, getSupportedCurrency as s, AppSpecificSsoMetadata as t, decompressJsonFromB64 as u, isMobile as v, SdkHandshakeFailureReason as w, triggerDeepLinkWithFallback as x, redirectToExternalBrowser as y };