@frak-labs/core-sdk 1.1.4 → 1.1.5-beta.1700e6ec

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/cdn/bundle.js +1 -1
  2. package/dist/actions-Cyn4asew.cjs +2 -0
  3. package/dist/actions-Cyn4asew.cjs.map +1 -0
  4. package/dist/actions-DtPVuk2e.js +2 -0
  5. package/dist/actions-DtPVuk2e.js.map +1 -0
  6. package/dist/actions.cjs +1 -1
  7. package/dist/actions.d.cts +3 -3
  8. package/dist/actions.d.ts +3 -3
  9. package/dist/actions.js +1 -1
  10. package/dist/bundle.cjs +1 -1
  11. package/dist/bundle.d.cts +5 -4
  12. package/dist/bundle.d.ts +5 -4
  13. package/dist/bundle.js +1 -1
  14. package/dist/frakContext-Bgidv-Se.js +2 -0
  15. package/dist/frakContext-Bgidv-Se.js.map +1 -0
  16. package/dist/frakContext-CeVlWFcX.cjs +2 -0
  17. package/dist/frakContext-CeVlWFcX.cjs.map +1 -0
  18. package/dist/getCurrencyAmountKey-DQcwjJiB.js +2 -0
  19. package/dist/getCurrencyAmountKey-DQcwjJiB.js.map +1 -0
  20. package/dist/getCurrencyAmountKey-h2eOiYzv.cjs +2 -0
  21. package/dist/getCurrencyAmountKey-h2eOiYzv.cjs.map +1 -0
  22. package/dist/{index-Cm1PNC9z.d.ts → index-CD-GW6rL.d.ts} +8 -6
  23. package/dist/{index-Cumn_FNI.d.cts → index-D5_EwZ3f.d.cts} +8 -6
  24. package/dist/{index-lcImYQyL.d.ts → index-DPez7SXh.d.cts} +4 -2
  25. package/dist/{index-DR4n-QOe.d.cts → index-DQYipoH_.d.ts} +4 -2
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.cts +4 -3
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +1 -1
  30. package/dist/merchantInformation--tLnowAN.d.ts +344 -0
  31. package/dist/merchantInformation-BSuahpjl.d.cts +344 -0
  32. package/dist/{openSso-BErs7_Bg.d.cts → openSso-CjZ8lAEA.d.ts} +8 -281
  33. package/dist/{openSso-DRLH4D9a.d.ts → openSso-DSXbUP-j.d.cts} +8 -281
  34. package/dist/rewards.cjs +2 -0
  35. package/dist/rewards.cjs.map +1 -0
  36. package/dist/rewards.d.cts +130 -0
  37. package/dist/rewards.d.ts +130 -0
  38. package/dist/rewards.js +2 -0
  39. package/dist/rewards.js.map +1 -0
  40. package/dist/src-BXtnEE3a.js +2 -0
  41. package/dist/src-BXtnEE3a.js.map +1 -0
  42. package/dist/src-TpX-7MwJ.cjs +2 -0
  43. package/dist/src-TpX-7MwJ.cjs.map +1 -0
  44. package/package.json +16 -3
  45. package/src/actions/getMerchantInformation.test.ts +2 -0
  46. package/src/actions/referral/processReferral.test.ts +4 -4
  47. package/src/actions/referral/processReferral.ts +4 -4
  48. package/src/clients/createIFrameFrakClient.ts +7 -7
  49. package/src/clients/transports/iframeLifecycleManager.ts +5 -21
  50. package/src/config/sdkConfigStore.ts +6 -3
  51. package/src/index.ts +6 -0
  52. package/src/rewards/conditions.test.ts +60 -0
  53. package/src/rewards/conditions.ts +72 -0
  54. package/src/rewards/example.test.ts +92 -0
  55. package/src/rewards/example.ts +57 -0
  56. package/src/rewards/format.test.ts +143 -0
  57. package/src/rewards/format.ts +81 -0
  58. package/src/rewards/index.ts +29 -0
  59. package/src/rewards/select.test.ts +281 -0
  60. package/src/rewards/select.ts +197 -0
  61. package/src/rewards/value.test.ts +89 -0
  62. package/src/rewards/value.ts +73 -0
  63. package/src/types/index.ts +6 -0
  64. package/src/types/resolvedConfig.ts +4 -0
  65. package/src/types/rpc/merchantInformation.ts +120 -13
  66. package/src/utils/analytics/events/lifecycle.ts +1 -1
  67. package/src/utils/analytics/events/referral.ts +3 -3
  68. package/src/utils/browser/deepLinkWithFallback.ts +4 -1
  69. package/src/utils/browser/inAppBrowser.ts +2 -2
  70. package/src/utils/cache/withCache.ts +30 -11
  71. package/src/utils/iframe/iframeHelper.test.ts +39 -0
  72. package/src/utils/iframe/iframeHelper.ts +31 -2
  73. package/dist/actions-D5GbNcvt.cjs +0 -1
  74. package/dist/actions-DE2dHqtp.js +0 -1
  75. package/dist/frakContext-Bl17GGa3.cjs +0 -1
  76. package/dist/frakContext-DmI5CaSw.js +0 -1
  77. package/dist/src-5vpUuhfY.js +0 -1
  78. package/dist/src-BQsjMpvA.cjs +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewards.js","names":[],"sources":["../src/rewards/conditions.ts","../src/rewards/example.ts","../src/rewards/value.ts","../src/rewards/format.ts","../src/rewards/select.ts"],"sourcesContent":["import type { ConditionGroup, RuleCondition, RuleConditions } from \"../types\";\n\ntype ConditionNode = RuleCondition | ConditionGroup;\n\nfunction conditionNodes(conditions: RuleConditions): ConditionNode[] {\n return Array.isArray(conditions) ? conditions : conditions.conditions;\n}\n\nfunction conditionValueToNumber(\n value: RuleCondition[\"value\"]\n): number | undefined {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\" && value.trim() !== \"\") {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n}\n\n// Conditions arrive as a flat list or recursive groups; collect every numeric\n// value whose field + operator match, descending into nested groups.\nfunction collectConditionValues(\n conditions: RuleConditions,\n field: string,\n operators: ReadonlySet<RuleCondition[\"operator\"]>\n): number[] {\n const values: number[] = [];\n for (const node of conditionNodes(conditions)) {\n if (\"logic\" in node) {\n values.push(...collectConditionValues(node, field, operators));\n continue;\n }\n if (node.field === field && operators.has(node.operator)) {\n const value = conditionValueToNumber(node.value);\n if (value != null) values.push(value);\n }\n }\n return values;\n}\n\nconst MIN_PURCHASE_OPERATORS: ReadonlySet<RuleCondition[\"operator\"]> = new Set([\n \"gt\",\n \"gte\",\n \"between\",\n]);\nconst START_DATE_OPERATORS: ReadonlySet<RuleCondition[\"operator\"]> = new Set([\n \"gt\",\n \"gte\",\n]);\n\nexport function extractMinPurchaseAmount(\n conditions: RuleConditions\n): number | undefined {\n const values = collectConditionValues(\n conditions,\n \"purchase.amount\",\n MIN_PURCHASE_OPERATORS\n );\n return values.length > 0 ? Math.min(...values) : undefined;\n}\n\n// The business app encodes a campaign start as a `time.timestamp >= <unix s>`\n// rule condition; the endpoint returns it verbatim inside `conditions`.\nexport function extractStartDate(conditions: RuleConditions): Date | undefined {\n const values = collectConditionValues(\n conditions,\n \"time.timestamp\",\n START_DATE_OPERATORS\n );\n if (values.length === 0) return undefined;\n return new Date(Math.min(...values) * 1000);\n}\n","import type { EstimatedReward } from \"../types\";\n\ntype PercentageReward = Extract<EstimatedReward, { payoutType: \"percentage\" }>;\n\n// Reference order total used to turn a \"% of basket\" reward into a concrete\n// example amount. The campaign's minimum purchase overrides it when higher.\nconst REFERENCE_BASKET = 100;\n\nexport type RewardExample = {\n basket: number;\n reward: number;\n};\n\nfunction roundWithinRange(min: number, max: number): number {\n const rounded = Math.round((min + max) / 2 / 10) * 10;\n return Math.min(max, Math.max(min, rounded));\n}\n\nexport function pickFlatBasket(minPurchase: number | undefined): number {\n return Math.max(REFERENCE_BASKET, minPurchase ?? 0);\n}\n\n// Basket for a percentage tier: the reference when it sits inside the tier\n// range, a round number within the range otherwise, or the lower bound for an\n// open-ended (uncapped) top tier.\nexport function pickTierBasket(\n minValue: number,\n maxValue: number | undefined\n): number {\n if (maxValue == null) return minValue;\n if (minValue <= REFERENCE_BASKET && REFERENCE_BASKET <= maxValue) {\n return REFERENCE_BASKET;\n }\n return roundWithinRange(minValue, maxValue);\n}\n\nexport function buildPercentageExample(\n reward: PercentageReward,\n minPurchase: number | undefined\n): RewardExample | undefined {\n const basket = pickFlatBasket(minPurchase);\n if (basket <= 0) return undefined;\n let amount = (reward.percent / 100) * basket;\n if (reward.minAmount) amount = Math.max(amount, reward.minAmount.eurAmount);\n if (reward.maxAmount) amount = Math.min(amount, reward.maxAmount.eurAmount);\n return { basket, reward: amount };\n}\n\nexport function buildTierExample(\n percent: number,\n minValue: number,\n maxValue: number | undefined\n): RewardExample | undefined {\n const basket = pickTierBasket(minValue, maxValue);\n if (basket <= 0) return undefined;\n return { basket, reward: (percent / 100) * basket };\n}\n","import type { EstimatedReward, TokenAmountType } from \"../types\";\n\n/**\n * Comparable fiat value of a single reward, used to rank rewards against each\n * other.\n *\n * - `fixed` → the token amount in the requested currency\n * - `tiered` → the highest token amount across tiers\n * - `percentage` → the capped (`maxAmount`) value when present, otherwise `0`\n * (an uncapped percentage has no comparable fiat value)\n */\nexport function getRewardValue(\n reward: EstimatedReward,\n key: keyof TokenAmountType\n): number {\n switch (reward.payoutType) {\n case \"fixed\":\n return reward.amount[key];\n case \"percentage\":\n return reward.maxAmount?.[key] ?? 0;\n case \"tiered\":\n return reward.tiers.reduce(\n (max, tier) =>\n \"amount\" in tier ? Math.max(max, tier.amount[key]) : max,\n 0\n );\n }\n}\n\n/**\n * Highest percent a reward exposes: the flat `percentage` percent, or the\n * richest percent across `tiered` tiers, or `0` when it carries none.\n *\n * Shared by {@link getRewardRank} (to weight a percentage-only reward) and\n * `formatEstimatedReward` (to render it as `\"X %\"`), so the value the UI ranks\n * by and the value it prints stay derived from one tier traversal.\n */\nexport function maxRewardPercent(reward: EstimatedReward): number {\n if (reward.payoutType === \"percentage\") return reward.percent;\n if (reward.payoutType === \"tiered\") {\n return reward.tiers.reduce(\n (max, tier) =>\n \"percent\" in tier ? Math.max(max, tier.percent) : max,\n 0\n );\n }\n return 0;\n}\n\n// A reward with no money value (an uncapped percentage, or a percent-only\n// tier set) still renders as \"X %\", so it is worth surfacing. We give it a\n// positive ranking weight derived from its percent but scaled far below any\n// real-money reward, so the two invariants the UI relies on hold: (1) a reward\n// with real money always outranks a percentage-only reward, and (2) a\n// percentage-only reward still outranks a zero-value reward. Together they\n// guarantee the reward the ranking picks is always one we can display.\nconst PERCENT_ONLY_RANK_WEIGHT = 1e-6;\n\n/**\n * Ranking weight used to pick the single most attractive reward to surface.\n *\n * Mirrors {@link getRewardValue} (money value) but lifts a percentage-only\n * reward to a tiny positive weight instead of `0`, so it is never buried\n * behind a zero-value reward when choosing what to display.\n */\nexport function getRewardRank(\n reward: EstimatedReward,\n key: keyof TokenAmountType\n): number {\n const value = getRewardValue(reward, key);\n if (value > 0) return value;\n return maxRewardPercent(reward) * PERCENT_ONLY_RANK_WEIGHT;\n}\n","import type { Currency, EstimatedReward } from \"../types\";\nimport { formatAmount } from \"../utils/format/formatAmount\";\nimport { getCurrencyAmountKey } from \"../utils/format/getCurrencyAmountKey\";\nimport { getSupportedCurrency } from \"../utils/format/getSupportedCurrency\";\nimport { getRewardRank, getRewardValue, maxRewardPercent } from \"./value\";\n\n/**\n * Format an {@link EstimatedReward} into a human-readable string.\n *\n * - `fixed` → e.g. `\"5 €\"`\n * - `percentage` → e.g. `\"10 %\"`\n * - `tiered` → the richest tier: max token amount (e.g. `\"50 €\"`) or, when\n * tiers only carry percentages, the max percent (e.g. `\"10 %\"`)\n *\n * Percentages are always rendered as a `\"X %\"` string: the backend never sends\n * a reference basket, so the reward cannot be resolved to a concrete amount\n * here. Callers that need a worked example use the `example` helpers instead.\n */\nexport function formatEstimatedReward(\n reward: EstimatedReward,\n currency?: Currency\n): string {\n const supportedCurrency = getSupportedCurrency(currency);\n const key = getCurrencyAmountKey(supportedCurrency);\n\n switch (reward.payoutType) {\n case \"fixed\":\n return formatAmount(\n Math.round(reward.amount[key]),\n supportedCurrency\n );\n\n case \"percentage\":\n return `${reward.percent} %`;\n\n case \"tiered\": {\n const maxAmount = getRewardValue(reward, key);\n if (maxAmount > 0) {\n return formatAmount(Math.round(maxAmount), supportedCurrency);\n }\n const maxPercent = maxRewardPercent(reward);\n if (maxPercent > 0) {\n return `${maxPercent} %`;\n }\n return formatAmount(0, supportedCurrency);\n }\n }\n}\n\n/**\n * Format a reward for display, or return `undefined` when it is not worth\n * advertising. Callers rely on `undefined` to hide a badge / fall back to\n * other copy.\n *\n * A reward is hidden only when it carries no displayable value — a `fixed` or\n * `tiered` reward whose money value is `0` (e.g. an unknown token price). An\n * uncapped percentage always renders as `\"X %\"`, since the percent itself is\n * meaningful even without a money value.\n */\nexport function formatRewardOrHide(\n reward: EstimatedReward | undefined,\n currency?: Currency\n): string | undefined {\n if (!reward) return undefined;\n const key = getCurrencyAmountKey(getSupportedCurrency(currency));\n if (getRewardRank(reward, key) <= 0) return undefined;\n return formatEstimatedReward(reward, currency);\n}\n\n/**\n * Replace the `{REWARD}` placeholder in a text string with a reward value.\n * If no reward is provided, returns the text with `{REWARD}` stripped.\n */\nexport function applyRewardPlaceholder(\n text: string,\n reward: string | undefined\n): string {\n return reward\n ? text.replace(\"{REWARD}\", reward)\n : text.replace(\"{REWARD}\", \"\");\n}\n","import type { InteractionTypeKey } from \"../constants/interactionTypes\";\nimport type {\n Currency,\n EstimatedReward,\n MerchantReward,\n TokenAmountType,\n} from \"../types\";\nimport { formatAmount } from \"../utils/format/formatAmount\";\nimport { getCurrencyAmountKey } from \"../utils/format/getCurrencyAmountKey\";\nimport { getSupportedCurrency } from \"../utils/format/getSupportedCurrency\";\nimport { extractMinPurchaseAmount, extractStartDate } from \"./conditions\";\nimport { formatRewardOrHide } from \"./format\";\nimport { getRewardRank } from \"./value\";\n\n/** Reward side a surface cares about: the sharer (`referrer`) or the referred\n * user (`referee`). Drives both campaign ranking and which side is displayed. */\nexport type RewardAudience = \"referrer\" | \"referee\";\n\nexport type DisplayCampaign = {\n campaign: MerchantReward;\n status: \"live\" | \"upcoming\";\n startsAt?: Date;\n};\n\nexport type SelectDisplayCampaignOptions = {\n /** Reference \"now\" for expiry / start gating; defaults to the current time. */\n now?: Date;\n /** Currency the ranking is expressed in; defaults to EUR. */\n currency?: Currency;\n /** When set, only campaigns triggered by this interaction are considered. */\n targetInteraction?: InteractionTypeKey;\n /** Reward side to rank campaigns by; defaults to `\"referrer\"`. */\n audience?: RewardAudience;\n};\n\nfunction isExpired(campaign: MerchantReward, nowMs: number): boolean {\n return (\n campaign.expiresAt != null &&\n new Date(campaign.expiresAt).getTime() <= nowMs\n );\n}\n\nfunction hasStarted(campaign: MerchantReward, nowMs: number): boolean {\n const startsAt = extractStartDate(campaign.conditions);\n return startsAt == null || startsAt.getTime() <= nowMs;\n}\n\nfunction audienceReward(campaign: MerchantReward, audience: RewardAudience) {\n return audience === \"referee\" ? campaign.referee : campaign.referrer;\n}\n\nfunction campaignRank(\n campaign: MerchantReward,\n key: keyof TokenAmountType,\n audience: RewardAudience\n): number {\n const reward = audienceReward(campaign, audience);\n return reward ? getRewardRank(reward, key) : 0;\n}\n\n/**\n * Pick the single campaign a merchant surface should display.\n *\n * Filters out expired (and, when `targetInteraction` is set, non-matching)\n * campaigns, then prefers the highest-ranked *live* campaign — ranked by the\n * `audience` reward side in the requested currency. When none has started yet\n * it falls back to the soonest-starting upcoming campaign (the endpoint does\n * not gate on the start-date condition, so future-start campaigns come\n * through).\n */\nexport function selectDisplayCampaign(\n rewards: readonly MerchantReward[],\n options: SelectDisplayCampaignOptions = {}\n): DisplayCampaign | undefined {\n const nowMs = (options.now ?? new Date()).getTime();\n const audience = options.audience ?? \"referrer\";\n const key = getCurrencyAmountKey(getSupportedCurrency(options.currency));\n\n const matching = options.targetInteraction\n ? rewards.filter(\n (campaign) =>\n campaign.interactionTypeKey === options.targetInteraction\n )\n : rewards;\n const active = matching.filter((campaign) => !isExpired(campaign, nowMs));\n\n const live = active.filter((campaign) => hasStarted(campaign, nowMs));\n if (live.length > 0) {\n const best = live.reduce((a, b) =>\n campaignRank(b, key, audience) > campaignRank(a, key, audience)\n ? b\n : a\n );\n return { campaign: best, status: \"live\" };\n }\n\n const upcoming = active\n .map((campaign) => ({\n campaign,\n startsAt: extractStartDate(campaign.conditions),\n }))\n .filter(\n (entry): entry is { campaign: MerchantReward; startsAt: Date } =>\n entry.startsAt != null\n );\n if (upcoming.length === 0) return undefined;\n\n const soonest = upcoming.reduce((a, b) =>\n b.startsAt.getTime() < a.startsAt.getTime() ? b : a\n );\n return {\n campaign: soonest.campaign,\n status: \"upcoming\",\n startsAt: soonest.startsAt,\n };\n}\n\n/**\n * The single reward a merchant surface should display: its formatted string\n * plus the `payoutType` of the underlying reward, so surfaces can adapt their\n * presentation (e.g. hide percentage rewards, prefix tiered ones with \"Up to\").\n */\nexport type BestReward = {\n /** Display-ready reward string (e.g. `\"5 €\"`, `\"10 %\"`). */\n formatted: string;\n /** Payout type of the selected reward. */\n payoutType: EstimatedReward[\"payoutType\"];\n /**\n * Minimum purchase amount gating the reward, formatted with the requested\n * currency (e.g. `\"10 €\"`), or `undefined` when the campaign sets no\n * minimum.\n */\n minPurchaseAmount?: string;\n /**\n * Whole-day lockup applied before the reward settles, or `undefined` when\n * the campaign has no lockup.\n */\n lockupDurationDays?: number;\n};\n\n/**\n * Pick the best campaign for `options` and resolve its `audience`-side reward\n * to a formatted string plus its `payoutType`, or `undefined` when there is\n * nothing worth showing.\n *\n * Single entry point shared by every \"headline reward\" surface (share button,\n * wallet modal, sharing/install screens) so they all show the same number for\n * a given merchant and can branch on the payout type.\n */\nexport function selectBestReward(\n rewards: readonly MerchantReward[],\n options: SelectDisplayCampaignOptions = {}\n): BestReward | undefined {\n const selected = selectDisplayCampaign(rewards, options);\n if (!selected) return undefined;\n const reward = audienceReward(\n selected.campaign,\n options.audience ?? \"referrer\"\n );\n if (!reward) return undefined;\n const formatted = formatRewardOrHide(reward, options.currency);\n if (!formatted) return undefined;\n\n const minPurchase = extractMinPurchaseAmount(selected.campaign.conditions);\n const minPurchaseAmount =\n minPurchase != null\n ? formatAmount(minPurchase, options.currency)\n : undefined;\n\n const lockupSeconds = selected.campaign.defaultLockupSeconds;\n const lockupDurationDays =\n lockupSeconds && lockupSeconds > 0\n ? Math.round(lockupSeconds / 86_400)\n : undefined;\n\n return {\n formatted,\n payoutType: reward.payoutType,\n minPurchaseAmount,\n lockupDurationDays,\n };\n}\n\n/**\n * Headline reward string for a merchant: picks the best campaign for `options`\n * and formats its `audience`-side reward, or returns `undefined` when there is\n * nothing worth showing.\n *\n * Thin wrapper over {@link selectBestReward} for callers that only need the\n * formatted string.\n */\nexport function formatBestReward(\n rewards: readonly MerchantReward[],\n options: SelectDisplayCampaignOptions = {}\n): string | undefined {\n return selectBestReward(rewards, options)?.formatted;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{_ as e,g as t,h as n,p as r,y as i}from"./frakContext-Bgidv-Se.js";import{r as a}from"./getCurrencyAmountKey-DQcwjJiB.js";import{Deferred as o,FrakRpcError as s,RpcErrorCodes as c,createRpcClient as l,jsonDecode as u}from"@frak-labs/frame-connector";import{OpenPanel as d}from"@openpanel/web";const f=`nexus-wallet-backup`,p=`frakwallet://`;function m(e,t){if(typeof window>`u`)return;let n=new URL(window.location.href),r=n.searchParams.get(`sso`);r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:`sso-redirect-complete`,data:{compressed:r}}),console.log(`[SSO URL Listener] Forwarded compressed SSO data to iframe`)}).catch(e=>{console.error(`[SSO URL Listener] Failed to forward SSO data:`,e)}),n.searchParams.delete(`sso`),window.history.replaceState({},``,n.toString()),console.log(`[SSO URL Listener] SSO parameter detected and URL cleaned`))}function h(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}const g=p.replace(`://`,``);function _(e){return`intent://${e.slice(13)}#Intent;scheme=${g};end`}function v(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=h()&&y(e)?_(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function y(e){return e.startsWith(p)}function b(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const x=b();function S(){return typeof navigator>`u`?!1:x?!0:/Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function C(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const w=C();function T(e){x&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:x&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}const E={id:`frak-wallet`,name:`frak-wallet`,title:`Frak Wallet`,allow:`publickey-credentials-get *; clipboard-write; web-share *`,style:{width:`0`,height:`0`,border:`0`,position:`absolute`,zIndex:2000001,top:`-1000px`,left:`-1000px`,colorScheme:`auto`}};function D({walletBaseUrl:e,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let a=document.createElement(`iframe`);a.id=E.id,a.name=E.name,a.allow=E.allow,a.style.zIndex=E.style.zIndex.toString(),k({iframe:a,isVisible:!1});let o=n?.walletUrl??e??`https://wallet.frak.id`,s=i();return j(o),j(t(o)),a.src=O({walletUrl:o,clientId:s,preload:n?.preload}),new Promise(e=>{a.addEventListener(`load`,()=>e(a)),document.body.appendChild(a)})}function O({walletUrl:e,clientId:t,preload:n}){let r=`${e}/listener?clientId=${encodeURIComponent(t)}`;return!n||n.length===0?r:`${r}#preload=${n.join(`,`)}`}function k({iframe:e,isVisible:t}){if(!t){e.style.width=`0`,e.style.height=`0`,e.style.border=`0`,e.style.position=`fixed`,e.style.top=`-1000px`,e.style.left=`-1000px`;return}e.style.position=`fixed`,e.style.top=`0`,e.style.left=`0`,e.style.width=`100%`,e.style.height=`100%`,e.style.pointerEvents=`auto`}function A(e=`/listener`){if(!window.opener)return null;let t=t=>{try{return t.location.origin===window.location.origin&&t.location.pathname===e}catch{return!1}};if(t(window.opener))return window.opener;try{let e=window.opener.frames;for(let n=0;n<e.length;n++)if(t(e[n]))return e[n];return null}catch(t){return console.error(`[findIframeInOpener] Error finding iframe with pathname ${e}:`,t),null}}function j(e){if(!(typeof document>`u`))try{let t=new URL(e).origin,n=`link[rel="preconnect"][data-frak-preconnect="${t}"]`;if(document.head.querySelector(n))return;let r=document.createElement(`link`);r.rel=`preconnect`,r.href=t,r.crossOrigin=``,r.dataset.frakPreconnect=t,document.head.appendChild(r)}catch{}}const M=x&&w;function N(e){e?localStorage.setItem(f,e):localStorage.removeItem(f)}function P(e,t){try{let n=new URL(e);if(!n.searchParams.has(`u`))return e;let r=L(window.location.href,t);return n.searchParams.delete(`u`),n.searchParams.append(`u`,r),n.toString()}catch{return e}}function F(e){let t=new URL(window.location.href);e&&t.searchParams.set(`fmt`,e);let n=t.protocol===`http:`?`x-safari-http`:`x-safari-https`;window.location.href=`${n}://${t.host}${t.pathname}${t.search}${t.hash}`}function I(e){return e.includes(`/common/social`)}function L(e,t){if(!t)return e;try{let n=new URL(e);return n.searchParams.set(`fmt`,t),n.toString()}catch{return`${e}${e.includes(`?`)?`&`:`?`}fmt=${encodeURIComponent(t)}`}}function R(e,t,n,r,i){if(i){let e=P(t,r);window.open(e,`_blank`);return}if(y(t)){let i=P(t,r);v(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}})}else if(M&&I(t))F(r);else{let e=P(t,r);window.location.href=e}}function z({iframe:e,targetOrigin:t}){let n=new o;return{handleEvent:r=>{if(!(`iframeLifecycle`in r))return;let{iframeLifecycle:i,data:a}=r;switch(i){case`connected`:n.resolve(!0);break;case`do-backup`:N(a.backup);break;case`remove-backup`:localStorage.removeItem(f);break;case`show`:case`hide`:k({iframe:e,isVisible:i===`show`});break;case`redirect`:R(e,a.baseRedirectUrl,t,a.mergeToken,a.openInNewTab);break}},isConnected:n.promise}}function B({config:t,iframe:r}){let a=t?.walletUrl??`https://wallet.frak.id`,u=typeof navigator<`u`?navigator.language?.split(`-`)[0]:void 0,f=t.metadata.lang??(u===`en`||u===`fr`?u:void 0),p=t.domain??(typeof window<`u`?window.location.hostname:``);n.setCacheScope(p,f),n.reset();let m=n.isCacheFresh?void 0:n.resolve(t.domain,t.walletUrl,f),h=z({iframe:r,targetOrigin:a}),g=new o,_=Date.now();if(!r.contentWindow)throw new s(c.configError,`The iframe does not have a content window`);let v=l({emittingTransport:r.contentWindow,listeningTransport:window,targetOrigin:a,middleware:[{async onRequest(e,t){if(!await h.isConnected)throw new s(c.clientNotConnected,`The iframe provider isn't connected yet`);return await g.promise,t}}],lifecycleHandlers:{iframeLifecycle:(e,t)=>{h.handleEvent(e)}}}),y=V(v,h),b=async()=>{y(),v.cleanup(),r.remove(),e(),n.clearCache(),n.reset()},x;{console.log(`[Frak SDK] Initializing OpenPanel`),x=new d({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdk_version`in t.properties||(t.properties={...t.properties,sdk_version:`1.1.5`,user_anonymous_client_id:i()}),!0)}),x.setGlobalProperties({sdk_version:`1.1.5`,user_anonymous_client_id:i()}),x.init(),x.track(`sdk_initialized`,{sdk_version:`1.1.5`});let e=!1,t=setTimeout(()=>{e||(e=!0,x?.track(`sdk_iframe_handshake_failed`,{reason:`timeout`}))},3e4);h.isConnected.then(()=>{e||(e=!0,clearTimeout(t),x?.track(`sdk_iframe_connected`,{handshake_duration_ms:Date.now()-_}))}).catch(()=>{e||(e=!0,clearTimeout(t),x?.track(`sdk_iframe_handshake_failed`,{reason:`unknown`}))})}let S=H({config:t,rpcClient:v,lifecycleManager:h,configPromise:m,contextSent:g,openPanel:x}).then(()=>{}).catch(e=>{throw g.reject(e),e});return{config:t,waitForConnection:h.isConnected,waitForSetup:S,request:v.request,listenerRequest:v.listen,destroy:b,openPanel:x}}function V(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,250),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function H({config:e,rpcClient:t,lifecycleManager:r,configPromise:a,contextSent:o,openPanel:s}){await r.isConnected,m(t,r.isConnected);let c=new URL(window.location.href),l=c.searchParams.get(`fmt`)??void 0;l&&(c.searchParams.delete(`fmt`),window.history.replaceState({},``,c.toString()));let u=t=>{let r=t?.merchantId??e.metadata.merchantId??``,i=t?.domain??``,a=t?.allowedDomains??[],o=t?.sdkConfig,s=o?.attribution||e.attribution?{...e.attribution,...o?.attribution}:void 0;n.setConfig(o?{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,hasRawSdkConfig:!0,name:o.name??e.metadata.name,logoUrl:o.logoUrl??e.metadata.logoUrl,homepageLink:o.homepageLink??e.metadata.homepageLink,lang:o.lang??e.metadata.lang,currency:o.currency??e.metadata.currency,hidden:o.hidden,css:o.css,translations:o.translations,placements:o.placements,components:o.components,attribution:s}:{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,name:e.metadata.name,logoUrl:e.metadata.logoUrl,homepageLink:e.metadata.homepageLink,lang:e.metadata.lang,currency:e.metadata.currency,attribution:s})},d=!1,p=e=>{let n=d?void 0:l;d=!0;let r=e.hasRawSdkConfig?{name:e.name,logoUrl:e.logoUrl,homepageLink:e.homepageLink,lang:e.lang,currency:e.currency,hidden:e.hidden,css:e.css,translations:e.translations,placements:e.placements,attribution:e.attribution}:e.attribution?{attribution:e.attribution}:void 0,a=i();if(s){let t=s.global??{};s.setGlobalProperties({...t,merchant_id:e.merchantId,domain:e.domain??``})}t.sendLifecycle({clientLifecycle:`resolved-config`,data:{merchantId:e.merchantId,domain:e.domain??``,allowedDomains:e.allowedDomains??[],sourceUrl:window.location.href,...a&&{sdkAnonymousId:a},...n&&{pendingMergeToken:n},...r&&{sdkConfig:r}}})};n.isResolved&&(p(n.getConfig()),o.resolve()),a&&(u(await a),p(n.getConfig()),o.resolve());async function h(){let n=e.customizations?.css;n&&t.sendLifecycle({clientLifecycle:`modal-css`,data:{cssLink:n}})}async function g(){let n=e.customizations?.i18n;n&&t.sendLifecycle({clientLifecycle:`modal-i18n`,data:{i18n:n}})}async function _(){if(typeof window>`u`)return;let e=window.localStorage.getItem(f);e&&t.sendLifecycle({clientLifecycle:`restore-backup`,data:{backup:e}})}(await Promise.allSettled([h(),g(),_()])).some(e=>e.status===`rejected`)&&s?.track(`sdk_iframe_handshake_failed`,{reason:`asset_push`})}function U(e){return u(r(e))}async function W({config:e}){let t=G(e),n=await D({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=B({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function G(e){let t=a(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}function K({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}export{E as a,w as c,v as d,p as f,B as i,S as l,W as n,A as o,U as r,x as s,K as t,T as u};
2
+ //# sourceMappingURL=src-BXtnEE3a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-BXtnEE3a.js","names":[],"sources":["../src/constants.ts","../src/clients/ssoUrlListener.ts","../src/utils/browser/deepLinkWithFallback.ts","../src/utils/browser/inAppBrowser.ts","../src/utils/iframe/iframeHelper.ts","../src/clients/transports/iframeLifecycleManager.ts","../src/clients/createIFrameFrakClient.ts","../src/utils/compression/decompress.ts","../src/clients/setupClient.ts","../src/context/mergeAttribution.ts"],"sourcesContent":["/**\n * The backup key for client side backup if needed\n */\nexport const BACKUP_KEY = \"nexus-wallet-backup\";\n\n/**\n * Deep link scheme for Frak Wallet mobile app.\n *\n * Replaced at build time via tsdown/Vite `define`. Defaults to the prod scheme;\n * in-monorepo dev builds (listener at wallet-dev.frak.id) override this with\n * `frakwallet-dev://` so deep links open the dev wallet variant (id.frak.wallet.dev).\n * External integrators consuming the published NPM/CDN bundle always see the prod scheme.\n */\nexport const DEEP_LINK_SCHEME: string =\n process.env.DEEP_LINK_SCHEME ?? \"frakwallet://\";\n","import type { RpcClient } from \"@frak-labs/frame-connector\";\nimport type { FrakLifecycleEvent } from \"../types\";\nimport type { IFrameRpcSchema } from \"../types/rpc\";\n\n/**\n * Listen for SSO redirect with compressed data in URL\n * Forwards compressed data to iframe via lifecycle event\n * Cleans URL immediately after detection\n *\n * Performance: One-shot URL check, no polling, no re-renders\n *\n * @param rpcClient - RPC client instance to send lifecycle events\n * @param waitForConnection - Promise that resolves when iframe is connected\n */\nexport function setupSsoUrlListener(\n rpcClient: RpcClient<IFrameRpcSchema, FrakLifecycleEvent>,\n waitForConnection: Promise<boolean>\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n // One-shot URL check - no need for MutationObserver or polling\n const url = new URL(window.location.href);\n const compressedSso = url.searchParams.get(\"sso\");\n\n // Early return if no SSO parameter\n if (!compressedSso) {\n return;\n }\n\n // Forward compressed data directly to iframe (no decompression on SDK side)\n // Iframe will decompress and process\n waitForConnection\n .then(() => {\n // Send lifecycle event with compressed string\n // This is a one-way notification, no response expected\n rpcClient.sendLifecycle({\n clientLifecycle: \"sso-redirect-complete\",\n data: { compressed: compressedSso },\n });\n\n console.log(\n \"[SSO URL Listener] Forwarded compressed SSO data to iframe\"\n );\n })\n .catch((error) => {\n console.error(\n \"[SSO URL Listener] Failed to forward SSO data:\",\n error\n );\n });\n\n // Clean URL immediately to prevent exposure in browser history\n // Use replaceState to avoid navigation/re-render\n url.searchParams.delete(\"sso\");\n window.history.replaceState({}, \"\", url.toString());\n\n console.log(\"[SSO URL Listener] SSO parameter detected and URL cleaned\");\n}\n","import { DEEP_LINK_SCHEME } from \"../../constants\";\n\n/**\n * Options for deep link with fallback\n */\nexport type DeepLinkFallbackOptions = {\n /** Timeout in ms before triggering fallback (default: 2500ms) */\n timeout?: number;\n /** Callback invoked when fallback is triggered (app not installed) */\n onFallback?: () => void;\n};\n\n/**\n * Check if running on a Chromium-based Android browser.\n *\n * On Chrome Android, custom scheme deep links (e.g. frakwallet://) trigger\n * a confirmation bar (\"Continue to Frak Wallet?\"). Using intent:// URLs\n * instead bypasses this for Chromium browsers while keeping custom scheme\n * fallback for non-Chromium browsers (e.g. Firefox) where it works fine.\n */\nexport function isChromiumAndroid(): boolean {\n const ua = navigator.userAgent;\n return /Android/i.test(ua) && /Chrome\\/\\d+/i.test(ua);\n}\n\n/**\n * Convert a Frak deep link to an Android intent:// URL.\n *\n * Intent URLs let Chromium browsers open the app directly without\n * showing the \"Continue to app?\" confirmation bar.\n *\n * Note: We intentionally omit the `package` parameter. Including it\n * causes Chrome to redirect to the Play Store when the app is not\n * installed, which breaks the visibility-based fallback detection.\n * Without `package`, Chrome simply does nothing when the app is\n * missing, allowing the fallback mechanism to fire correctly.\n *\n * The scheme is derived from `DEEP_LINK_SCHEME` so the dev variant\n * (`frakwallet-dev://`) routes to the dev app shell, not prod.\n *\n * Format: intent://path#Intent;scheme=<scheme>;end\n */\nconst DEEP_LINK_SCHEME_NAME = /* @__PURE__ */ DEEP_LINK_SCHEME.replace(\n \"://\",\n \"\"\n);\n\nexport function toAndroidIntentUrl(deepLink: string): string {\n // Extract everything after the scheme (e.g. \"frakwallet://\" or \"frakwallet-dev://\")\n const path = deepLink.slice(DEEP_LINK_SCHEME.length);\n return `intent://${path}#Intent;scheme=${DEEP_LINK_SCHEME_NAME};end`;\n}\n\n/**\n * Trigger a deep link with visibility-based fallback detection.\n *\n * Uses the Page Visibility API to detect if the app opened (page goes hidden).\n * If the page remains visible after the timeout, assumes app is not installed\n * and invokes the onFallback callback.\n *\n * On Chromium Android, converts custom scheme to intent:// URL to avoid\n * the \"Continue to app?\" confirmation bar.\n *\n * @param deepLink - The deep link URL to trigger (e.g., \"frakwallet://wallet\")\n * @param options - Optional configuration (timeout, onFallback callback)\n */\nexport function triggerDeepLinkWithFallback(\n deepLink: string,\n options?: DeepLinkFallbackOptions\n): void {\n const timeout = options?.timeout ?? 2500;\n\n // Track if the app opened (page went to background)\n let appOpened = false;\n\n const onVisibilityChange = () => {\n if (document.hidden) {\n appOpened = true;\n }\n };\n\n // Start listening for visibility changes\n document.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n // On Chromium Android, use intent:// to avoid confirmation bar\n const url =\n isChromiumAndroid() && isFrakDeepLink(deepLink)\n ? toAndroidIntentUrl(deepLink)\n : deepLink;\n\n // Trigger the deep link\n window.location.href = url;\n\n // Check after timeout if app opened\n setTimeout(() => {\n // Clean up listener\n document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\n if (!appOpened) {\n // App didn't open - trigger fallback callback\n options?.onFallback?.();\n }\n }, timeout);\n}\n\n/**\n * Check if a URL is a Frak deep link\n */\nexport function isFrakDeepLink(url: string): boolean {\n return url.startsWith(DEEP_LINK_SCHEME);\n}\n","/**\n * Check if the current device runs iOS (including iPadOS 13+).\n */\nfunction checkIsIOS(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent;\n // Standard iOS devices\n if (/iPhone|iPad|iPod/i.test(ua)) return true;\n // iPadOS 13+ reports as Macintosh — detect via touch support\n if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1) return true;\n return false;\n}\n\n/**\n * Whether the current device runs iOS (including iPadOS 13+).\n */\nexport const isIOS: boolean = /* @__PURE__ */ checkIsIOS();\n\n/**\n * Check if the current device is a mobile device (iOS, iPadOS, Android,\n * webOS, BlackBerry, IEMobile, Opera Mini). Reuses {@link isIOS} so the\n * iPadOS-13+ Macintosh heuristic stays in one place.\n */\nexport function isMobile(): boolean {\n if (typeof navigator === \"undefined\") return false;\n if (isIOS) return true;\n return /Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(\n navigator.userAgent\n );\n}\n\n/**\n * Check if the current browser is a social media in-app browser\n * (Instagram, Facebook WebView).\n */\nfunction checkInAppBrowser(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent.toLowerCase();\n return (\n ua.includes(\"instagram\") ||\n ua.includes(\"fban\") ||\n ua.includes(\"fbav\") ||\n ua.includes(\"facebook\")\n );\n}\n\n/**\n * Whether the current browser is a social media in-app browser\n * (Instagram, Facebook).\n */\nexport const isInAppBrowser: boolean = /* @__PURE__ */ checkInAppBrowser();\n\n/**\n * Redirect to external browser from in-app WebView.\n *\n * - **iOS**: Uses `x-safari-https://` scheme — server-side 302 redirects\n * to custom URL schemes are silently swallowed by WKWebView.\n * Direct `window.location.href` assignment works (confirmed iOS 17+).\n *\n * - **Android**: Uses backend `/common/social` endpoint which returns a PDF\n * Content-Type response, forcing the WebView to hand off to the default browser.\n *\n * @param targetUrl - The URL to open in the external browser\n */\nexport function redirectToExternalBrowser(targetUrl: string): void {\n if (isIOS && targetUrl.startsWith(\"https://\")) {\n window.location.href = `x-safari-https://${targetUrl.slice(8)}`;\n } else if (isIOS && targetUrl.startsWith(\"http://\")) {\n window.location.href = `x-safari-http://${targetUrl.slice(7)}`;\n } else {\n window.location.href = `${process.env.BACKEND_URL}/common/social?u=${encodeURIComponent(targetUrl)}`;\n }\n}\n","import { getBackendUrl } from \"../../config/backendUrl\";\nimport { getClientId } from \"../../config/clientId\";\nimport type { FrakWalletSdkConfig, ListenerPreloadOption } from \"../../types\";\n\n/**\n * Base props for the iframe\n * @ignore\n */\nexport const baseIframeProps = {\n id: \"frak-wallet\",\n name: \"frak-wallet\",\n title: \"Frak Wallet\",\n allow: \"publickey-credentials-get *; clipboard-write; web-share *\",\n style: {\n width: \"0\",\n height: \"0\",\n border: \"0\",\n position: \"absolute\",\n zIndex: 2000001,\n top: \"-1000px\",\n left: \"-1000px\",\n colorScheme: \"auto\",\n },\n};\n\n/**\n * Create the Frak iframe\n * @param args\n * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions.\n * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`.\n */\nexport function createIframe({\n walletBaseUrl,\n config,\n}: {\n walletBaseUrl?: string;\n config?: FrakWalletSdkConfig;\n}): Promise<HTMLIFrameElement | undefined> {\n // Check if the iframe is already created\n const alreadyCreatedIFrame = document.querySelector(\"#frak-wallet\");\n\n // If the iframe is already created, remove it\n if (alreadyCreatedIFrame) {\n alreadyCreatedIFrame.remove();\n }\n\n const iframe = document.createElement(\"iframe\");\n\n // Set the base iframe props\n iframe.id = baseIframeProps.id;\n iframe.name = baseIframeProps.name;\n iframe.allow = baseIframeProps.allow;\n iframe.style.zIndex = baseIframeProps.style.zIndex.toString();\n\n changeIframeVisibility({ iframe, isVisible: false });\n\n // Set src BEFORE appending to DOM to avoid about:blank load event\n const walletUrl =\n config?.walletUrl ?? walletBaseUrl ?? \"https://wallet.frak.id\";\n const clientId = getClientId();\n\n // Preconnect to the wallet + backend origins so the handshake doesn't pay\n // for a cold DNS/TLS round-trip on partner sites that didn't warm them.\n preconnect(walletUrl);\n preconnect(getBackendUrl(walletUrl));\n\n iframe.src = buildListenerUrl({\n walletUrl,\n clientId,\n preload: config?.preload,\n });\n\n return new Promise((resolve) => {\n iframe.addEventListener(\"load\", () => resolve(iframe));\n document.body.appendChild(iframe);\n });\n}\n\n/**\n * Build the listener iframe URL.\n *\n * Query params:\n * - `clientId` — anonymous SDK client identifier used for funnel joining.\n *\n * Hash params (consumed by `apps/listener/app/bootstrap.ts#setupPreloadHints`):\n * - `preload=modal,sharing` — idle-warms the matching Ring 1 + Ring 2 chunks.\n * Skipped entirely when no preload hints are provided so the listener\n * doesn't pay for warm-ups that nobody asked for.\n */\nfunction buildListenerUrl({\n walletUrl,\n clientId,\n preload,\n}: {\n walletUrl: string;\n clientId: string;\n preload?: ListenerPreloadOption[];\n}): string {\n const base = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;\n if (!preload || preload.length === 0) return base;\n return `${base}#preload=${preload.join(\",\")}`;\n}\n\n/**\n * Change the visibility of the given iframe\n * @ignore\n */\nexport function changeIframeVisibility({\n iframe,\n isVisible,\n}: {\n iframe: HTMLIFrameElement;\n isVisible: boolean;\n}) {\n if (!isVisible) {\n iframe.style.width = \"0\";\n iframe.style.height = \"0\";\n iframe.style.border = \"0\";\n iframe.style.position = \"fixed\";\n iframe.style.top = \"-1000px\";\n iframe.style.left = \"-1000px\";\n return;\n }\n\n iframe.style.position = \"fixed\";\n iframe.style.top = \"0\";\n iframe.style.left = \"0\";\n iframe.style.width = \"100%\";\n iframe.style.height = \"100%\";\n iframe.style.pointerEvents = \"auto\";\n}\n\n/**\n * Find an iframe within window.opener by pathname\n *\n * When a popup is opened via window.open from an iframe, window.opener points to\n * the parent window, not the iframe itself. This utility searches through all frames\n * in window.opener to find an iframe matching the specified pathname.\n *\n * @param pathname - The pathname to search for (default: \"/listener\")\n * @returns The matching iframe window, or null if not found\n *\n * @example\n * ```typescript\n * // Find the default /listener iframe\n * const listenerIframe = findIframeInOpener();\n *\n * // Find a custom iframe\n * const customIframe = findIframeInOpener(\"/my-custom-iframe\");\n * ```\n */\nexport function findIframeInOpener(pathname = \"/listener\"): Window | null {\n if (!window.opener) return null;\n\n const frameCheck = (frame: Window) => {\n try {\n return (\n frame.location.origin === window.location.origin &&\n frame.location.pathname === pathname\n );\n } catch {\n // Cross-origin frame, skip\n return false;\n }\n };\n\n // Check if the openner window is the right one\n if (frameCheck(window.opener)) return window.opener;\n\n // Search through frames in window.opener\n try {\n const frames = window.opener.frames;\n for (let i = 0; i < frames.length; i++) {\n if (frameCheck(frames[i])) {\n return frames[i];\n }\n }\n return null;\n } catch (error) {\n console.error(\n `[findIframeInOpener] Error finding iframe with pathname ${pathname}:`,\n error\n );\n return null;\n }\n}\n\n/**\n * Inject a `<link rel=\"preconnect\">` for the given origin. Idempotent — browsers\n * de-duplicate preconnects per origin automatically, but we also guard on a data\n * attribute to avoid spamming the <head> in SPA re-inits.\n */\nfunction preconnect(url: string): void {\n if (typeof document === \"undefined\") return;\n try {\n const origin = new URL(url).origin;\n const selector = `link[rel=\"preconnect\"][data-frak-preconnect=\"${origin}\"]`;\n if (document.head.querySelector(selector)) return;\n const link = document.createElement(\"link\");\n link.rel = \"preconnect\";\n link.href = origin;\n link.crossOrigin = \"\";\n link.dataset.frakPreconnect = origin;\n document.head.appendChild(link);\n } catch {\n // Invalid URL — nothing to preconnect.\n }\n}\n","import { Deferred } from \"@frak-labs/frame-connector\";\nimport { BACKUP_KEY } from \"../../constants\";\nimport type { FrakLifecycleEvent } from \"../../types\";\nimport {\n isFrakDeepLink,\n triggerDeepLinkWithFallback,\n} from \"../../utils/browser/deepLinkWithFallback\";\nimport { isInAppBrowser, isIOS } from \"../../utils/browser/inAppBrowser\";\nimport { changeIframeVisibility } from \"../../utils/iframe/iframeHelper\";\n\n// iOS in-app browsers (Instagram, Facebook) silently swallow server-side 302\n// redirects to custom URL schemes (x-safari-https://) in WKWebView; direct\n// window.location.href assignment works.\nconst isIOSInAppBrowser = isIOS && isInAppBrowser;\n\n/** @ignore */\nexport type IframeLifecycleManager = {\n isConnected: Promise<boolean>;\n handleEvent: (messageEvent: FrakLifecycleEvent) => void;\n};\n\n/**\n * Handle backup storage\n */\nfunction handleBackup(backup: string | undefined): void {\n if (backup) {\n localStorage.setItem(BACKUP_KEY, backup);\n } else {\n localStorage.removeItem(BACKUP_KEY);\n }\n}\n\n/**\n * Compute final redirect URL with parameter substitution\n */\nfunction computeRedirectUrl(\n baseRedirectUrl: string,\n mergeToken?: string\n): string {\n try {\n const redirectUrl = new URL(baseRedirectUrl);\n if (!redirectUrl.searchParams.has(\"u\")) {\n return baseRedirectUrl;\n }\n\n // Append merge token to the page URL so it survives\n // the backend /common/social redirect chain\n const finalPageUrl = appendMergeToken(window.location.href, mergeToken);\n\n redirectUrl.searchParams.delete(\"u\");\n redirectUrl.searchParams.append(\"u\", finalPageUrl);\n\n return redirectUrl.toString();\n } catch {\n return baseRedirectUrl;\n }\n}\n\n/**\n * Redirect current page to Safari via x-safari-https:// scheme.\n * Used on iOS in-app browsers where backend 302 → custom scheme fails.\n */\nfunction redirectToSafari(mergeToken?: string) {\n const url = new URL(window.location.href);\n if (mergeToken) {\n url.searchParams.set(\"fmt\", mergeToken);\n }\n const scheme =\n url.protocol === \"http:\" ? \"x-safari-http\" : \"x-safari-https\";\n window.location.href = `${scheme}://${url.host}${url.pathname}${url.search}${url.hash}`;\n}\n\n/**\n * Check if this is a social/in-app-browser escape redirect (contains /common/social)\n */\nfunction isSocialRedirect(url: string): boolean {\n return url.includes(\"/common/social\");\n}\n\n/**\n * Append merge token to a URL as the `fmt` query parameter.\n */\nfunction appendMergeToken(urlString: string, mergeToken?: string): string {\n if (!mergeToken) return urlString;\n try {\n const url = new URL(urlString);\n url.searchParams.set(\"fmt\", mergeToken);\n return url.toString();\n } catch {\n const sep = urlString.includes(\"?\") ? \"&\" : \"?\";\n return `${urlString}${sep}fmt=${encodeURIComponent(mergeToken)}`;\n }\n}\n\n/**\n * Handle redirect with deep link fallback\n */\nfunction handleRedirect(\n iframe: HTMLIFrameElement,\n baseRedirectUrl: string,\n targetOrigin: string,\n mergeToken?: string,\n openInNewTab?: boolean\n): void {\n // If requested, open in a new tab instead of navigating the current page.\n // This preserves the merchant page while triggering universal links.\n // Requires the iframe postMessage to include user activation delegation.\n if (openInNewTab) {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n window.open(finalUrl, \"_blank\");\n return;\n }\n\n if (isFrakDeepLink(baseRedirectUrl)) {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n triggerDeepLinkWithFallback(finalUrl, {\n onFallback: () => {\n iframe.contentWindow?.postMessage(\n {\n clientLifecycle: \"deep-link-failed\",\n data: { originalUrl: finalUrl },\n },\n targetOrigin\n );\n },\n });\n } else if (isIOSInAppBrowser && isSocialRedirect(baseRedirectUrl)) {\n // iOS WKWebView silently swallows 302 redirects to custom URL\n // schemes — bypass the server redirect entirely\n redirectToSafari(mergeToken);\n } else {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n window.location.href = finalUrl;\n }\n}\n\n/**\n * Create a new iframe lifecycle handler\n * @param args\n * @param args.iframe - The iframe element used for wallet communication\n * @param args.targetOrigin - The wallet URL origin for postMessage security\n * @ignore\n */\nexport function createIFrameLifecycleManager({\n iframe,\n targetOrigin,\n}: {\n iframe: HTMLIFrameElement;\n targetOrigin: string;\n}): IframeLifecycleManager {\n // Create the isConnected listener\n const isConnectedDeferred = new Deferred<boolean>();\n\n // Build the handler itself\n const handler = (messageEvent: FrakLifecycleEvent) => {\n if (!(\"iframeLifecycle\" in messageEvent)) return;\n\n const { iframeLifecycle: event, data } = messageEvent;\n\n switch (event) {\n // Resolve the isConnected promise\n case \"connected\":\n isConnectedDeferred.resolve(true);\n break;\n // Perform a frak backup\n case \"do-backup\":\n handleBackup(data.backup);\n break;\n // Remove frak backup\n case \"remove-backup\":\n localStorage.removeItem(BACKUP_KEY);\n break;\n // Change iframe visibility\n case \"show\":\n case \"hide\":\n changeIframeVisibility({ iframe, isVisible: event === \"show\" });\n break;\n // Redirect handling\n case \"redirect\":\n handleRedirect(\n iframe,\n data.baseRedirectUrl,\n targetOrigin,\n data.mergeToken,\n data.openInNewTab\n );\n break;\n }\n };\n\n return {\n handleEvent: handler,\n isConnected: isConnectedDeferred.promise,\n };\n}\n","import {\n createRpcClient,\n Deferred,\n FrakRpcError,\n type RpcClient,\n RpcErrorCodes,\n} from \"@frak-labs/frame-connector\";\nimport { OpenPanel } from \"@openpanel/web\";\nimport { getClientId } from \"../config/clientId\";\nimport { sdkConfigStore } from \"../config/sdkConfigStore\";\nimport { BACKUP_KEY } from \"../constants\";\nimport type { FrakLifecycleEvent } from \"../types\";\nimport type { FrakClient } from \"../types/client\";\nimport type { FrakWalletSdkConfig } from \"../types/config\";\nimport type { SdkResolvedConfig } from \"../types/resolvedConfig\";\nimport type { IFrameRpcSchema } from \"../types/rpc\";\nimport { clearAllCache } from \"../utils/cache\";\nimport { setupSsoUrlListener } from \"./ssoUrlListener\";\nimport {\n createIFrameLifecycleManager,\n type IframeLifecycleManager,\n} from \"./transports/iframeLifecycleManager\";\n\ntype SdkRpcClient = RpcClient<IFrameRpcSchema, FrakLifecycleEvent>;\ntype MerchantConfigResult = Awaited<ReturnType<typeof sdkConfigStore.resolve>>;\n\n/**\n * Create a new iframe Frak client\n * @param args\n * @param args.config - The configuration to use for the Frak Wallet SDK.\n * 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).\n * @param args.iframe - The iframe to use for the communication\n * @returns The created Frak Client\n *\n * @example\n * const frakConfig: FrakWalletSdkConfig = {\n * metadata: {\n * name: \"My app title\",\n * },\n * }\n * const iframe = await createIframe({ config: frakConfig });\n * const client = createIFrameFrakClient({ config: frakConfig, iframe });\n */\nexport function createIFrameFrakClient({\n config,\n iframe,\n}: {\n config: FrakWalletSdkConfig;\n iframe: HTMLIFrameElement;\n}): FrakClient {\n const frakWalletUrl = config?.walletUrl ?? \"https://wallet.frak.id\";\n\n const browserLang =\n typeof navigator !== \"undefined\"\n ? navigator.language?.split(\"-\")[0]\n : undefined;\n const detectedLang =\n config.metadata.lang ??\n (browserLang === \"en\" || browserLang === \"fr\"\n ? browserLang\n : undefined);\n const targetDomain =\n config.domain ??\n (typeof window !== \"undefined\" ? window.location.hostname : \"\");\n sdkConfigStore.setCacheScope(targetDomain, detectedLang);\n sdkConfigStore.reset();\n\n // Skip fetch entirely if cache is fresh, otherwise fetch (SWR)\n const configPromise = sdkConfigStore.isCacheFresh\n ? undefined\n : sdkConfigStore.resolve(config.domain, config.walletUrl, detectedLang);\n\n // Create lifecycle manager\n const lifecycleManager = createIFrameLifecycleManager({\n iframe,\n targetOrigin: frakWalletUrl,\n });\n\n // Resolved after first resolved-config is sent to iframe (prevents RPC before context exists)\n const contextSent = new Deferred<void>();\n\n // Handshake timing: measured from client creation until the iframe\n // lifecycle manager resolves the `isConnected` promise.\n const handshakeStartedAt = Date.now();\n\n // Validate iframe\n if (!iframe.contentWindow) {\n throw new FrakRpcError(\n RpcErrorCodes.configError,\n \"The iframe does not have a content window\"\n );\n }\n\n // Create RPC client with middleware and lifecycle handlers\n const rpcClient = createRpcClient<IFrameRpcSchema, FrakLifecycleEvent>({\n emittingTransport: iframe.contentWindow,\n listeningTransport: window,\n targetOrigin: frakWalletUrl,\n middleware: [\n // Ensure we are connected and context is sent before sending request\n {\n async onRequest(_message, ctx) {\n const isConnected = await lifecycleManager.isConnected;\n if (!isConnected) {\n throw new FrakRpcError(\n RpcErrorCodes.clientNotConnected,\n \"The iframe provider isn't connected yet\"\n );\n }\n await contextSent.promise;\n return ctx;\n },\n },\n ],\n // Add lifecycle handlers to process iframe lifecycle events\n lifecycleHandlers: {\n iframeLifecycle: (event, _context) => {\n // Delegate to lifecycle manager (cast for type compatibility)\n lifecycleManager.handleEvent(event);\n },\n },\n });\n\n // Setup heartbeat\n const stopHeartbeat = setupHeartbeat(rpcClient, lifecycleManager);\n\n const destroy = async () => {\n stopHeartbeat();\n rpcClient.cleanup();\n iframe.remove();\n clearAllCache();\n sdkConfigStore.clearCache();\n sdkConfigStore.reset();\n };\n\n // Init open panel\n let openPanel: OpenPanel | undefined;\n if (\n process.env.OPEN_PANEL_API_URL &&\n process.env.OPEN_PANEL_SDK_CLIENT_ID\n ) {\n console.log(\"[Frak SDK] Initializing OpenPanel\");\n openPanel = new OpenPanel({\n apiUrl: process.env.OPEN_PANEL_API_URL,\n clientId: process.env.OPEN_PANEL_SDK_CLIENT_ID,\n trackScreenViews: true,\n trackOutgoingLinks: true,\n trackAttributes: false,\n // We use a filter to ensure we got the open panel instance initialized\n // A bit hacky, but this way we are sure that we got everything needed for the first event ever sent\n filter: ({ type, payload }) => {\n if (type !== \"track\") return true;\n if (!payload?.properties) return true;\n\n // Check if we we got the properties once initialized\n if (!(\"sdk_version\" in payload.properties)) {\n payload.properties = {\n ...payload.properties,\n sdk_version: process.env.SDK_VERSION,\n user_anonymous_client_id: getClientId(),\n };\n }\n\n return true;\n },\n });\n openPanel.setGlobalProperties({\n sdk_version: process.env.SDK_VERSION,\n user_anonymous_client_id: getClientId(),\n });\n openPanel.init();\n openPanel.track(\"sdk_initialized\", {\n sdk_version: process.env.SDK_VERSION,\n });\n\n // Race the connection against the heartbeat timeout so we can\n // distinguish \"connected\" from \"timeout\" cleanly without touching\n // the heartbeat plumbing. 30s matches `HEARTBEAT_TIMEOUT`.\n let settled = false;\n const timeoutHandle = setTimeout(() => {\n if (settled) return;\n settled = true;\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"timeout\",\n });\n }, 30_000);\n lifecycleManager.isConnected\n .then(() => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n openPanel?.track(\"sdk_iframe_connected\", {\n handshake_duration_ms: Date.now() - handshakeStartedAt,\n });\n })\n .catch(() => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"unknown\",\n });\n });\n }\n\n // Perform the post connection setup\n const waitForSetup = postConnectionSetup({\n config,\n rpcClient,\n lifecycleManager,\n configPromise,\n contextSent,\n openPanel,\n })\n .then(() => {})\n .catch((err) => {\n contextSent.reject(err);\n throw err;\n });\n\n return {\n config,\n waitForConnection: lifecycleManager.isConnected,\n waitForSetup,\n request: rpcClient.request,\n listenerRequest: rpcClient.listen,\n destroy,\n openPanel,\n };\n}\n\n/**\n * Setup the heartbeat\n * @param rpcClient - RPC client to send lifecycle events\n * @param lifecycleManager - Lifecycle manager to track connection\n */\nfunction setupHeartbeat(\n rpcClient: SdkRpcClient,\n lifecycleManager: IframeLifecycleManager\n) {\n const HEARTBEAT_INTERVAL = 250; // Fallback discovery ping until we are connected\n const HEARTBEAT_TIMEOUT = 30_000; // 30 seconds timeout\n let heartbeatInterval: NodeJS.Timeout;\n let timeoutId: NodeJS.Timeout;\n\n const sendHeartbeat = () =>\n rpcClient.sendLifecycle({\n clientLifecycle: \"heartbeat\",\n });\n\n // Start sending heartbeats\n async function startHeartbeat() {\n sendHeartbeat(); // Send initial heartbeat\n heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);\n\n // Set up timeout\n timeoutId = setTimeout(() => {\n stopHeartbeat();\n console.log(\"Heartbeat timeout: connection failed\");\n }, HEARTBEAT_TIMEOUT);\n\n // Once connected, stop it\n await lifecycleManager.isConnected;\n\n // We are now connected, stop the heartbeat\n stopHeartbeat();\n }\n\n // Stop sending heartbeats\n function stopHeartbeat() {\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n }\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n\n startHeartbeat();\n\n // Return cleanup function\n return stopHeartbeat;\n}\n\n/**\n * Perform the post connection setup\n * @param config - SDK configuration\n * @param rpcClient - RPC client to send lifecycle events\n * @param lifecycleManager - Lifecycle manager to track connection\n */\nasync function postConnectionSetup({\n config,\n rpcClient,\n lifecycleManager,\n configPromise,\n contextSent,\n openPanel,\n}: {\n config: FrakWalletSdkConfig;\n rpcClient: SdkRpcClient;\n lifecycleManager: IframeLifecycleManager;\n configPromise: Promise<MerchantConfigResult> | undefined;\n contextSent: Deferred<void>;\n openPanel: OpenPanel | undefined;\n}): Promise<void> {\n await lifecycleManager.isConnected;\n\n setupSsoUrlListener(rpcClient, lifecycleManager.isConnected);\n\n // Read and consume the pending merge token from URL (SSO identity merge)\n const url = new URL(window.location.href);\n const pendingMergeToken = url.searchParams.get(\"fmt\") ?? undefined;\n if (pendingMergeToken) {\n url.searchParams.delete(\"fmt\");\n window.history.replaceState({}, \"\", url.toString());\n }\n\n // Merge a raw backend response with SDK metadata and persist to store\n const mergeAndSetConfig = (merchantConfig: MerchantConfigResult) => {\n const merchantId =\n merchantConfig?.merchantId ?? config.metadata.merchantId ?? \"\";\n const domain = merchantConfig?.domain ?? \"\";\n const allowedDomains = merchantConfig?.allowedDomains ?? [];\n const raw = merchantConfig?.sdkConfig;\n\n // Per-field merge: backend wins over SDK static config.\n const mergedAttribution =\n raw?.attribution || config.attribution\n ? { ...config.attribution, ...raw?.attribution }\n : undefined;\n\n sdkConfigStore.setConfig(\n raw\n ? {\n isResolved: true,\n merchantId,\n domain,\n allowedDomains,\n hasRawSdkConfig: true,\n name: raw.name ?? config.metadata.name,\n logoUrl: raw.logoUrl ?? config.metadata.logoUrl,\n homepageLink:\n raw.homepageLink ?? config.metadata.homepageLink,\n lang: raw.lang ?? config.metadata.lang,\n currency: raw.currency ?? config.metadata.currency,\n hidden: raw.hidden,\n css: raw.css,\n translations: raw.translations,\n placements: raw.placements,\n components: raw.components,\n attribution: mergedAttribution,\n }\n : {\n isResolved: true,\n merchantId,\n domain,\n allowedDomains,\n name: config.metadata.name,\n logoUrl: config.metadata.logoUrl,\n homepageLink: config.metadata.homepageLink,\n lang: config.metadata.lang,\n currency: config.metadata.currency,\n attribution: mergedAttribution,\n }\n );\n };\n\n // Send the resolved-config lifecycle event to the iframe.\n // This is where we also update SDK-side OpenPanel global props with\n // `merchantId` + `domain` (first time they are known) so every\n // subsequent SDK event is merchant-attributed. We pass\n // `sdkAnonymousId` through so the listener can join SDK funnels.\n let mergeTokenConsumed = false;\n const sendLifecycleConfig = (resolved: SdkResolvedConfig) => {\n const token = mergeTokenConsumed ? undefined : pendingMergeToken;\n mergeTokenConsumed = true;\n\n const sdkConfig = resolved.hasRawSdkConfig\n ? {\n name: resolved.name,\n logoUrl: resolved.logoUrl,\n homepageLink: resolved.homepageLink,\n lang: resolved.lang,\n currency: resolved.currency,\n hidden: resolved.hidden,\n css: resolved.css,\n translations: resolved.translations,\n placements: resolved.placements,\n attribution: resolved.attribution,\n }\n : resolved.attribution\n ? { attribution: resolved.attribution }\n : undefined;\n\n const sdkAnonymousId = getClientId();\n\n if (openPanel) {\n const current = openPanel.global ?? {};\n openPanel.setGlobalProperties({\n ...current,\n merchant_id: resolved.merchantId,\n domain: resolved.domain ?? \"\",\n });\n }\n\n rpcClient.sendLifecycle({\n clientLifecycle: \"resolved-config\",\n data: {\n merchantId: resolved.merchantId,\n domain: resolved.domain ?? \"\",\n allowedDomains: resolved.allowedDomains ?? [],\n sourceUrl: window.location.href,\n ...(sdkAnonymousId && { sdkAnonymousId }),\n ...(token && { pendingMergeToken: token }),\n ...(sdkConfig && { sdkConfig }),\n },\n });\n };\n\n // SWR: if we have cached data, send it to the iframe immediately\n if (sdkConfigStore.isResolved) {\n sendLifecycleConfig(sdkConfigStore.getConfig());\n contextSent.resolve();\n }\n\n // If a fetch is running (stale/missing cache), wait for fresh data and update\n if (configPromise) {\n const merchantConfig = await configPromise;\n mergeAndSetConfig(merchantConfig);\n sendLifecycleConfig(sdkConfigStore.getConfig());\n contextSent.resolve();\n }\n\n // Push raw CSS if needed\n async function pushCss() {\n const cssLink = config.customizations?.css;\n if (!cssLink) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"modal-css\" as const,\n data: { cssLink },\n });\n }\n\n // Push i18n if needed\n async function pushI18n() {\n const i18n = config.customizations?.i18n;\n if (!i18n) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"modal-i18n\" as const,\n data: { i18n },\n });\n }\n\n // Push local backup if needed\n async function pushBackup() {\n if (typeof window === \"undefined\") return;\n const backup = window.localStorage.getItem(BACKUP_KEY);\n if (!backup) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"restore-backup\" as const,\n data: { backup },\n });\n }\n\n // Inspect each setup result — a failed CSS/i18n/backup push leaves the\n // partner UI in a broken-but-connected state (iframe reports\n // `sdk_iframe_connected`, user sees no modal styles / wrong locale).\n // Surface it as a distinct handshake reason so dashboards can\n // distinguish timeout vs. asset-push failures.\n const results = await Promise.allSettled([\n pushCss(),\n pushI18n(),\n pushBackup(),\n ]);\n const hasFailedAssetPush = results.some((r) => r.status === \"rejected\");\n if (hasFailedAssetPush) {\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"asset_push\",\n });\n }\n}\n","import { jsonDecode } from \"@frak-labs/frame-connector\";\nimport { base64urlDecode } from \"./b64\";\n\n/**\n * Decompress json data\n * @param data\n * @ignore\n */\nexport function decompressJsonFromB64<T>(data: string): T | null {\n return jsonDecode<T>(base64urlDecode(data));\n}\n","import { createIFrameFrakClient } from \"../clients\";\nimport type { FrakClient, FrakWalletSdkConfig } from \"../types\";\nimport { createIframe, getSupportedCurrency } from \"../utils\";\n\n/**\n * Directly setup the Frak client with an iframe\n * Return when the FrakClient is ready (setup and communication estbalished with the wallet)\n *\n * @param config - The configuration to use for the Frak Wallet SDK\n * @returns a Promise with the Frak Client\n *\n * @example\n * const frakConfig: FrakWalletSdkConfig = {\n * metadata: {\n * name: \"My app title\",\n * },\n * }\n * const client = await setupClient({ config: frakConfig });\n */\nexport async function setupClient({\n config,\n}: {\n config: FrakWalletSdkConfig;\n}): Promise<FrakClient | undefined> {\n // Prepare the config\n const preparedConfig = prepareConfig(config);\n\n // Create our iframe\n const iframe = await createIframe({\n config: preparedConfig,\n });\n\n if (!iframe) {\n console.error(\"Failed to create iframe\");\n return;\n }\n\n // Create our client\n const client = createIFrameFrakClient({\n config: preparedConfig,\n iframe,\n });\n\n // Wait for the client to be all setup\n await client.waitForSetup;\n\n // Wait for the connection to be established\n const waitForConnection = await client.waitForConnection;\n if (!waitForConnection) {\n console.error(\"Failed to connect to client\");\n return;\n }\n\n return client;\n}\n\n/**\n * Prepare the config for the Frak Client\n * @param config - The configuration to use for the Frak Wallet SDK\n * @returns The prepared configuration with the supported currency\n */\nfunction prepareConfig(config: FrakWalletSdkConfig): FrakWalletSdkConfig {\n // Get the supported currency (e.g. \"eur\")\n const supportedCurrency = getSupportedCurrency(config.metadata?.currency);\n\n return {\n ...config,\n metadata: {\n ...config.metadata,\n currency: supportedCurrency,\n },\n };\n}\n","import type { AttributionDefaults, AttributionParams } from \"../types/tracking\";\n\n/**\n * Inputs for {@link mergeAttribution}.\n */\nexport type MergeAttributionInput = {\n /**\n * Per-call attribution override passed to actions like `displaySharingPage`.\n *\n * - `null` explicitly disables attribution (no UTM/ref/via params are added).\n * - `undefined` means \"no per-call override\" — defaults apply if present.\n * - An object (including `{}`) merges field-by-field with defaults.\n */\n perCall: AttributionParams | null | undefined;\n /**\n * Pre-merged merchant-level defaults (backend config > SDK static config).\n * `utm_content` is intentionally absent from this shape.\n */\n defaults?: AttributionDefaults;\n /**\n * Per-product `utm_content` override (from the currently selected\n * `SharingPageProduct`). Takes precedence over `perCall.utmContent`.\n */\n productUtmContent?: string;\n};\n\n/**\n * Merge the three attribution layers into a single {@link AttributionParams}\n * value suitable for `FrakContextManager.update`.\n *\n * Priority per field:\n * 1. `perCall` (wins)\n * 2. `defaults` (merchant-level, backend > SDK static, already pre-merged)\n * 3. Hardcoded fallbacks resolved later by `FrakContextManager`\n *\n * Special rules:\n * - `perCall === null` returns `undefined` (explicit disable: no UTM/ref/via).\n * - `perCall === undefined` (no opinion) yields at least `{}` so `FrakContextManager`\n * applies its hardcoded defaults (utm_source=frak, utm_medium=referral,\n * utm_campaign=<merchantId>, ref=<clientId>, via=frak).\n * - `utm_content` never comes from `defaults`; only `productUtmContent` or\n * `perCall.utmContent` can populate it.\n */\nexport function mergeAttribution({\n perCall,\n defaults,\n productUtmContent,\n}: MergeAttributionInput): AttributionParams | undefined {\n // Explicit disable\n if (perCall === null) return undefined;\n\n const hasPerCall = perCall !== undefined;\n const hasDefaults =\n defaults !== undefined && Object.keys(defaults).length > 0;\n const hasProductUtm =\n productUtmContent !== undefined && productUtmContent !== \"\";\n\n if (!hasPerCall && !hasDefaults && !hasProductUtm) return undefined;\n\n // Per-field merge: per-call wins over defaults.\n const merged: AttributionParams = {\n ...defaults,\n ...(perCall ?? {}),\n };\n\n // utm_content priority: productUtmContent > perCall.utmContent; never from defaults.\n const utmContent = productUtmContent ?? perCall?.utmContent;\n if (utmContent !== undefined && utmContent !== \"\") {\n merged.utmContent = utmContent;\n } else {\n delete merged.utmContent;\n }\n\n return merged;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./frakContext-CeVlWFcX.cjs`),t=require(`./getCurrencyAmountKey-h2eOiYzv.cjs`);let n=require(`@frak-labs/frame-connector`),r=require(`@openpanel/web`);const i=`nexus-wallet-backup`,a=`frakwallet://`;function o(e,t){if(typeof window>`u`)return;let n=new URL(window.location.href),r=n.searchParams.get(`sso`);r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:`sso-redirect-complete`,data:{compressed:r}}),console.log(`[SSO URL Listener] Forwarded compressed SSO data to iframe`)}).catch(e=>{console.error(`[SSO URL Listener] Failed to forward SSO data:`,e)}),n.searchParams.delete(`sso`),window.history.replaceState({},``,n.toString()),console.log(`[SSO URL Listener] SSO parameter detected and URL cleaned`))}function s(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}const c=a.replace(`://`,``);function l(e){return`intent://${e.slice(13)}#Intent;scheme=${c};end`}function u(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=s()&&d(e)?l(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function d(e){return e.startsWith(a)}function f(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const p=f();function m(){return typeof navigator>`u`?!1:p?!0:/Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function h(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const g=h();function _(e){p&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:p&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}const v={id:`frak-wallet`,name:`frak-wallet`,title:`Frak Wallet`,allow:`publickey-credentials-get *; clipboard-write; web-share *`,style:{width:`0`,height:`0`,border:`0`,position:`absolute`,zIndex:2000001,top:`-1000px`,left:`-1000px`,colorScheme:`auto`}};function y({walletBaseUrl:t,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=v.id,i.name=v.name,i.allow=v.allow,i.style.zIndex=v.style.zIndex.toString(),x({iframe:i,isVisible:!1});let a=n?.walletUrl??t??`https://wallet.frak.id`,o=e.y();return C(a),C(e.g(a)),i.src=b({walletUrl:a,clientId:o,preload:n?.preload}),new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function b({walletUrl:e,clientId:t,preload:n}){let r=`${e}/listener?clientId=${encodeURIComponent(t)}`;return!n||n.length===0?r:`${r}#preload=${n.join(`,`)}`}function x({iframe:e,isVisible:t}){if(!t){e.style.width=`0`,e.style.height=`0`,e.style.border=`0`,e.style.position=`fixed`,e.style.top=`-1000px`,e.style.left=`-1000px`;return}e.style.position=`fixed`,e.style.top=`0`,e.style.left=`0`,e.style.width=`100%`,e.style.height=`100%`,e.style.pointerEvents=`auto`}function S(e=`/listener`){if(!window.opener)return null;let t=t=>{try{return t.location.origin===window.location.origin&&t.location.pathname===e}catch{return!1}};if(t(window.opener))return window.opener;try{let e=window.opener.frames;for(let n=0;n<e.length;n++)if(t(e[n]))return e[n];return null}catch(t){return console.error(`[findIframeInOpener] Error finding iframe with pathname ${e}:`,t),null}}function C(e){if(!(typeof document>`u`))try{let t=new URL(e).origin,n=`link[rel="preconnect"][data-frak-preconnect="${t}"]`;if(document.head.querySelector(n))return;let r=document.createElement(`link`);r.rel=`preconnect`,r.href=t,r.crossOrigin=``,r.dataset.frakPreconnect=t,document.head.appendChild(r)}catch{}}const w=p&&g;function T(e){e?localStorage.setItem(i,e):localStorage.removeItem(i)}function E(e,t){try{let n=new URL(e);if(!n.searchParams.has(`u`))return e;let r=k(window.location.href,t);return n.searchParams.delete(`u`),n.searchParams.append(`u`,r),n.toString()}catch{return e}}function D(e){let t=new URL(window.location.href);e&&t.searchParams.set(`fmt`,e);let n=t.protocol===`http:`?`x-safari-http`:`x-safari-https`;window.location.href=`${n}://${t.host}${t.pathname}${t.search}${t.hash}`}function O(e){return e.includes(`/common/social`)}function k(e,t){if(!t)return e;try{let n=new URL(e);return n.searchParams.set(`fmt`,t),n.toString()}catch{return`${e}${e.includes(`?`)?`&`:`?`}fmt=${encodeURIComponent(t)}`}}function A(e,t,n,r,i){if(i){let e=E(t,r);window.open(e,`_blank`);return}if(d(t)){let i=E(t,r);u(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}})}else if(w&&O(t))D(r);else{let e=E(t,r);window.location.href=e}}function j({iframe:e,targetOrigin:t}){let r=new n.Deferred;return{handleEvent:n=>{if(!(`iframeLifecycle`in n))return;let{iframeLifecycle:a,data:o}=n;switch(a){case`connected`:r.resolve(!0);break;case`do-backup`:T(o.backup);break;case`remove-backup`:localStorage.removeItem(i);break;case`show`:case`hide`:x({iframe:e,isVisible:a===`show`});break;case`redirect`:A(e,o.baseRedirectUrl,t,o.mergeToken,o.openInNewTab);break}},isConnected:r.promise}}function M({config:t,iframe:i}){let a=t?.walletUrl??`https://wallet.frak.id`,o=typeof navigator<`u`?navigator.language?.split(`-`)[0]:void 0,s=t.metadata.lang??(o===`en`||o===`fr`?o:void 0),c=t.domain??(typeof window<`u`?window.location.hostname:``);e.h.setCacheScope(c,s),e.h.reset();let l=e.h.isCacheFresh?void 0:e.h.resolve(t.domain,t.walletUrl,s),u=j({iframe:i,targetOrigin:a}),d=new n.Deferred,f=Date.now();if(!i.contentWindow)throw new n.FrakRpcError(n.RpcErrorCodes.configError,`The iframe does not have a content window`);let p=(0,n.createRpcClient)({emittingTransport:i.contentWindow,listeningTransport:window,targetOrigin:a,middleware:[{async onRequest(e,t){if(!await u.isConnected)throw new n.FrakRpcError(n.RpcErrorCodes.clientNotConnected,`The iframe provider isn't connected yet`);return await d.promise,t}}],lifecycleHandlers:{iframeLifecycle:(e,t)=>{u.handleEvent(e)}}}),m=N(p,u),h=async()=>{m(),p.cleanup(),i.remove(),e._(),e.h.clearCache(),e.h.reset()},g;{console.log(`[Frak SDK] Initializing OpenPanel`),g=new r.OpenPanel({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:t,payload:n})=>(t!==`track`||!n?.properties||`sdk_version`in n.properties||(n.properties={...n.properties,sdk_version:`1.1.5`,user_anonymous_client_id:e.y()}),!0)}),g.setGlobalProperties({sdk_version:`1.1.5`,user_anonymous_client_id:e.y()}),g.init(),g.track(`sdk_initialized`,{sdk_version:`1.1.5`});let t=!1,n=setTimeout(()=>{t||(t=!0,g?.track(`sdk_iframe_handshake_failed`,{reason:`timeout`}))},3e4);u.isConnected.then(()=>{t||(t=!0,clearTimeout(n),g?.track(`sdk_iframe_connected`,{handshake_duration_ms:Date.now()-f}))}).catch(()=>{t||(t=!0,clearTimeout(n),g?.track(`sdk_iframe_handshake_failed`,{reason:`unknown`}))})}let _=P({config:t,rpcClient:p,lifecycleManager:u,configPromise:l,contextSent:d,openPanel:g}).then(()=>{}).catch(e=>{throw d.reject(e),e});return{config:t,waitForConnection:u.isConnected,waitForSetup:_,request:p.request,listenerRequest:p.listen,destroy:h,openPanel:g}}function N(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,250),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function P({config:t,rpcClient:n,lifecycleManager:r,configPromise:a,contextSent:s,openPanel:c}){await r.isConnected,o(n,r.isConnected);let l=new URL(window.location.href),u=l.searchParams.get(`fmt`)??void 0;u&&(l.searchParams.delete(`fmt`),window.history.replaceState({},``,l.toString()));let d=n=>{let r=n?.merchantId??t.metadata.merchantId??``,i=n?.domain??``,a=n?.allowedDomains??[],o=n?.sdkConfig,s=o?.attribution||t.attribution?{...t.attribution,...o?.attribution}:void 0;e.h.setConfig(o?{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,hasRawSdkConfig:!0,name:o.name??t.metadata.name,logoUrl:o.logoUrl??t.metadata.logoUrl,homepageLink:o.homepageLink??t.metadata.homepageLink,lang:o.lang??t.metadata.lang,currency:o.currency??t.metadata.currency,hidden:o.hidden,css:o.css,translations:o.translations,placements:o.placements,components:o.components,attribution:s}:{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,name:t.metadata.name,logoUrl:t.metadata.logoUrl,homepageLink:t.metadata.homepageLink,lang:t.metadata.lang,currency:t.metadata.currency,attribution:s})},f=!1,p=t=>{let r=f?void 0:u;f=!0;let i=t.hasRawSdkConfig?{name:t.name,logoUrl:t.logoUrl,homepageLink:t.homepageLink,lang:t.lang,currency:t.currency,hidden:t.hidden,css:t.css,translations:t.translations,placements:t.placements,attribution:t.attribution}:t.attribution?{attribution:t.attribution}:void 0,a=e.y();if(c){let e=c.global??{};c.setGlobalProperties({...e,merchant_id:t.merchantId,domain:t.domain??``})}n.sendLifecycle({clientLifecycle:`resolved-config`,data:{merchantId:t.merchantId,domain:t.domain??``,allowedDomains:t.allowedDomains??[],sourceUrl:window.location.href,...a&&{sdkAnonymousId:a},...r&&{pendingMergeToken:r},...i&&{sdkConfig:i}}})};e.h.isResolved&&(p(e.h.getConfig()),s.resolve()),a&&(d(await a),p(e.h.getConfig()),s.resolve());async function m(){let e=t.customizations?.css;e&&n.sendLifecycle({clientLifecycle:`modal-css`,data:{cssLink:e}})}async function h(){let e=t.customizations?.i18n;e&&n.sendLifecycle({clientLifecycle:`modal-i18n`,data:{i18n:e}})}async function g(){if(typeof window>`u`)return;let e=window.localStorage.getItem(i);e&&n.sendLifecycle({clientLifecycle:`restore-backup`,data:{backup:e}})}(await Promise.allSettled([m(),h(),g()])).some(e=>e.status===`rejected`)&&c?.track(`sdk_iframe_handshake_failed`,{reason:`asset_push`})}function F(t){return(0,n.jsonDecode)(e.p(t))}async function I({config:e}){let t=L(e),n=await y({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=M({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function L(e){let n=t.r(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:n}}}function R({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return _}});
2
+ //# sourceMappingURL=src-TpX-7MwJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-TpX-7MwJ.cjs","names":["getClientId","getBackendUrl","Deferred","sdkConfigStore","Deferred","FrakRpcError","RpcErrorCodes","OpenPanel","getClientId","base64urlDecode","getSupportedCurrency"],"sources":["../src/constants.ts","../src/clients/ssoUrlListener.ts","../src/utils/browser/deepLinkWithFallback.ts","../src/utils/browser/inAppBrowser.ts","../src/utils/iframe/iframeHelper.ts","../src/clients/transports/iframeLifecycleManager.ts","../src/clients/createIFrameFrakClient.ts","../src/utils/compression/decompress.ts","../src/clients/setupClient.ts","../src/context/mergeAttribution.ts"],"sourcesContent":["/**\n * The backup key for client side backup if needed\n */\nexport const BACKUP_KEY = \"nexus-wallet-backup\";\n\n/**\n * Deep link scheme for Frak Wallet mobile app.\n *\n * Replaced at build time via tsdown/Vite `define`. Defaults to the prod scheme;\n * in-monorepo dev builds (listener at wallet-dev.frak.id) override this with\n * `frakwallet-dev://` so deep links open the dev wallet variant (id.frak.wallet.dev).\n * External integrators consuming the published NPM/CDN bundle always see the prod scheme.\n */\nexport const DEEP_LINK_SCHEME: string =\n process.env.DEEP_LINK_SCHEME ?? \"frakwallet://\";\n","import type { RpcClient } from \"@frak-labs/frame-connector\";\nimport type { FrakLifecycleEvent } from \"../types\";\nimport type { IFrameRpcSchema } from \"../types/rpc\";\n\n/**\n * Listen for SSO redirect with compressed data in URL\n * Forwards compressed data to iframe via lifecycle event\n * Cleans URL immediately after detection\n *\n * Performance: One-shot URL check, no polling, no re-renders\n *\n * @param rpcClient - RPC client instance to send lifecycle events\n * @param waitForConnection - Promise that resolves when iframe is connected\n */\nexport function setupSsoUrlListener(\n rpcClient: RpcClient<IFrameRpcSchema, FrakLifecycleEvent>,\n waitForConnection: Promise<boolean>\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n // One-shot URL check - no need for MutationObserver or polling\n const url = new URL(window.location.href);\n const compressedSso = url.searchParams.get(\"sso\");\n\n // Early return if no SSO parameter\n if (!compressedSso) {\n return;\n }\n\n // Forward compressed data directly to iframe (no decompression on SDK side)\n // Iframe will decompress and process\n waitForConnection\n .then(() => {\n // Send lifecycle event with compressed string\n // This is a one-way notification, no response expected\n rpcClient.sendLifecycle({\n clientLifecycle: \"sso-redirect-complete\",\n data: { compressed: compressedSso },\n });\n\n console.log(\n \"[SSO URL Listener] Forwarded compressed SSO data to iframe\"\n );\n })\n .catch((error) => {\n console.error(\n \"[SSO URL Listener] Failed to forward SSO data:\",\n error\n );\n });\n\n // Clean URL immediately to prevent exposure in browser history\n // Use replaceState to avoid navigation/re-render\n url.searchParams.delete(\"sso\");\n window.history.replaceState({}, \"\", url.toString());\n\n console.log(\"[SSO URL Listener] SSO parameter detected and URL cleaned\");\n}\n","import { DEEP_LINK_SCHEME } from \"../../constants\";\n\n/**\n * Options for deep link with fallback\n */\nexport type DeepLinkFallbackOptions = {\n /** Timeout in ms before triggering fallback (default: 2500ms) */\n timeout?: number;\n /** Callback invoked when fallback is triggered (app not installed) */\n onFallback?: () => void;\n};\n\n/**\n * Check if running on a Chromium-based Android browser.\n *\n * On Chrome Android, custom scheme deep links (e.g. frakwallet://) trigger\n * a confirmation bar (\"Continue to Frak Wallet?\"). Using intent:// URLs\n * instead bypasses this for Chromium browsers while keeping custom scheme\n * fallback for non-Chromium browsers (e.g. Firefox) where it works fine.\n */\nexport function isChromiumAndroid(): boolean {\n const ua = navigator.userAgent;\n return /Android/i.test(ua) && /Chrome\\/\\d+/i.test(ua);\n}\n\n/**\n * Convert a Frak deep link to an Android intent:// URL.\n *\n * Intent URLs let Chromium browsers open the app directly without\n * showing the \"Continue to app?\" confirmation bar.\n *\n * Note: We intentionally omit the `package` parameter. Including it\n * causes Chrome to redirect to the Play Store when the app is not\n * installed, which breaks the visibility-based fallback detection.\n * Without `package`, Chrome simply does nothing when the app is\n * missing, allowing the fallback mechanism to fire correctly.\n *\n * The scheme is derived from `DEEP_LINK_SCHEME` so the dev variant\n * (`frakwallet-dev://`) routes to the dev app shell, not prod.\n *\n * Format: intent://path#Intent;scheme=<scheme>;end\n */\nconst DEEP_LINK_SCHEME_NAME = /* @__PURE__ */ DEEP_LINK_SCHEME.replace(\n \"://\",\n \"\"\n);\n\nexport function toAndroidIntentUrl(deepLink: string): string {\n // Extract everything after the scheme (e.g. \"frakwallet://\" or \"frakwallet-dev://\")\n const path = deepLink.slice(DEEP_LINK_SCHEME.length);\n return `intent://${path}#Intent;scheme=${DEEP_LINK_SCHEME_NAME};end`;\n}\n\n/**\n * Trigger a deep link with visibility-based fallback detection.\n *\n * Uses the Page Visibility API to detect if the app opened (page goes hidden).\n * If the page remains visible after the timeout, assumes app is not installed\n * and invokes the onFallback callback.\n *\n * On Chromium Android, converts custom scheme to intent:// URL to avoid\n * the \"Continue to app?\" confirmation bar.\n *\n * @param deepLink - The deep link URL to trigger (e.g., \"frakwallet://wallet\")\n * @param options - Optional configuration (timeout, onFallback callback)\n */\nexport function triggerDeepLinkWithFallback(\n deepLink: string,\n options?: DeepLinkFallbackOptions\n): void {\n const timeout = options?.timeout ?? 2500;\n\n // Track if the app opened (page went to background)\n let appOpened = false;\n\n const onVisibilityChange = () => {\n if (document.hidden) {\n appOpened = true;\n }\n };\n\n // Start listening for visibility changes\n document.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n // On Chromium Android, use intent:// to avoid confirmation bar\n const url =\n isChromiumAndroid() && isFrakDeepLink(deepLink)\n ? toAndroidIntentUrl(deepLink)\n : deepLink;\n\n // Trigger the deep link\n window.location.href = url;\n\n // Check after timeout if app opened\n setTimeout(() => {\n // Clean up listener\n document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n\n if (!appOpened) {\n // App didn't open - trigger fallback callback\n options?.onFallback?.();\n }\n }, timeout);\n}\n\n/**\n * Check if a URL is a Frak deep link\n */\nexport function isFrakDeepLink(url: string): boolean {\n return url.startsWith(DEEP_LINK_SCHEME);\n}\n","/**\n * Check if the current device runs iOS (including iPadOS 13+).\n */\nfunction checkIsIOS(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent;\n // Standard iOS devices\n if (/iPhone|iPad|iPod/i.test(ua)) return true;\n // iPadOS 13+ reports as Macintosh — detect via touch support\n if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1) return true;\n return false;\n}\n\n/**\n * Whether the current device runs iOS (including iPadOS 13+).\n */\nexport const isIOS: boolean = /* @__PURE__ */ checkIsIOS();\n\n/**\n * Check if the current device is a mobile device (iOS, iPadOS, Android,\n * webOS, BlackBerry, IEMobile, Opera Mini). Reuses {@link isIOS} so the\n * iPadOS-13+ Macintosh heuristic stays in one place.\n */\nexport function isMobile(): boolean {\n if (typeof navigator === \"undefined\") return false;\n if (isIOS) return true;\n return /Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(\n navigator.userAgent\n );\n}\n\n/**\n * Check if the current browser is a social media in-app browser\n * (Instagram, Facebook WebView).\n */\nfunction checkInAppBrowser(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent.toLowerCase();\n return (\n ua.includes(\"instagram\") ||\n ua.includes(\"fban\") ||\n ua.includes(\"fbav\") ||\n ua.includes(\"facebook\")\n );\n}\n\n/**\n * Whether the current browser is a social media in-app browser\n * (Instagram, Facebook).\n */\nexport const isInAppBrowser: boolean = /* @__PURE__ */ checkInAppBrowser();\n\n/**\n * Redirect to external browser from in-app WebView.\n *\n * - **iOS**: Uses `x-safari-https://` scheme — server-side 302 redirects\n * to custom URL schemes are silently swallowed by WKWebView.\n * Direct `window.location.href` assignment works (confirmed iOS 17+).\n *\n * - **Android**: Uses backend `/common/social` endpoint which returns a PDF\n * Content-Type response, forcing the WebView to hand off to the default browser.\n *\n * @param targetUrl - The URL to open in the external browser\n */\nexport function redirectToExternalBrowser(targetUrl: string): void {\n if (isIOS && targetUrl.startsWith(\"https://\")) {\n window.location.href = `x-safari-https://${targetUrl.slice(8)}`;\n } else if (isIOS && targetUrl.startsWith(\"http://\")) {\n window.location.href = `x-safari-http://${targetUrl.slice(7)}`;\n } else {\n window.location.href = `${process.env.BACKEND_URL}/common/social?u=${encodeURIComponent(targetUrl)}`;\n }\n}\n","import { getBackendUrl } from \"../../config/backendUrl\";\nimport { getClientId } from \"../../config/clientId\";\nimport type { FrakWalletSdkConfig, ListenerPreloadOption } from \"../../types\";\n\n/**\n * Base props for the iframe\n * @ignore\n */\nexport const baseIframeProps = {\n id: \"frak-wallet\",\n name: \"frak-wallet\",\n title: \"Frak Wallet\",\n allow: \"publickey-credentials-get *; clipboard-write; web-share *\",\n style: {\n width: \"0\",\n height: \"0\",\n border: \"0\",\n position: \"absolute\",\n zIndex: 2000001,\n top: \"-1000px\",\n left: \"-1000px\",\n colorScheme: \"auto\",\n },\n};\n\n/**\n * Create the Frak iframe\n * @param args\n * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions.\n * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`.\n */\nexport function createIframe({\n walletBaseUrl,\n config,\n}: {\n walletBaseUrl?: string;\n config?: FrakWalletSdkConfig;\n}): Promise<HTMLIFrameElement | undefined> {\n // Check if the iframe is already created\n const alreadyCreatedIFrame = document.querySelector(\"#frak-wallet\");\n\n // If the iframe is already created, remove it\n if (alreadyCreatedIFrame) {\n alreadyCreatedIFrame.remove();\n }\n\n const iframe = document.createElement(\"iframe\");\n\n // Set the base iframe props\n iframe.id = baseIframeProps.id;\n iframe.name = baseIframeProps.name;\n iframe.allow = baseIframeProps.allow;\n iframe.style.zIndex = baseIframeProps.style.zIndex.toString();\n\n changeIframeVisibility({ iframe, isVisible: false });\n\n // Set src BEFORE appending to DOM to avoid about:blank load event\n const walletUrl =\n config?.walletUrl ?? walletBaseUrl ?? \"https://wallet.frak.id\";\n const clientId = getClientId();\n\n // Preconnect to the wallet + backend origins so the handshake doesn't pay\n // for a cold DNS/TLS round-trip on partner sites that didn't warm them.\n preconnect(walletUrl);\n preconnect(getBackendUrl(walletUrl));\n\n iframe.src = buildListenerUrl({\n walletUrl,\n clientId,\n preload: config?.preload,\n });\n\n return new Promise((resolve) => {\n iframe.addEventListener(\"load\", () => resolve(iframe));\n document.body.appendChild(iframe);\n });\n}\n\n/**\n * Build the listener iframe URL.\n *\n * Query params:\n * - `clientId` — anonymous SDK client identifier used for funnel joining.\n *\n * Hash params (consumed by `apps/listener/app/bootstrap.ts#setupPreloadHints`):\n * - `preload=modal,sharing` — idle-warms the matching Ring 1 + Ring 2 chunks.\n * Skipped entirely when no preload hints are provided so the listener\n * doesn't pay for warm-ups that nobody asked for.\n */\nfunction buildListenerUrl({\n walletUrl,\n clientId,\n preload,\n}: {\n walletUrl: string;\n clientId: string;\n preload?: ListenerPreloadOption[];\n}): string {\n const base = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;\n if (!preload || preload.length === 0) return base;\n return `${base}#preload=${preload.join(\",\")}`;\n}\n\n/**\n * Change the visibility of the given iframe\n * @ignore\n */\nexport function changeIframeVisibility({\n iframe,\n isVisible,\n}: {\n iframe: HTMLIFrameElement;\n isVisible: boolean;\n}) {\n if (!isVisible) {\n iframe.style.width = \"0\";\n iframe.style.height = \"0\";\n iframe.style.border = \"0\";\n iframe.style.position = \"fixed\";\n iframe.style.top = \"-1000px\";\n iframe.style.left = \"-1000px\";\n return;\n }\n\n iframe.style.position = \"fixed\";\n iframe.style.top = \"0\";\n iframe.style.left = \"0\";\n iframe.style.width = \"100%\";\n iframe.style.height = \"100%\";\n iframe.style.pointerEvents = \"auto\";\n}\n\n/**\n * Find an iframe within window.opener by pathname\n *\n * When a popup is opened via window.open from an iframe, window.opener points to\n * the parent window, not the iframe itself. This utility searches through all frames\n * in window.opener to find an iframe matching the specified pathname.\n *\n * @param pathname - The pathname to search for (default: \"/listener\")\n * @returns The matching iframe window, or null if not found\n *\n * @example\n * ```typescript\n * // Find the default /listener iframe\n * const listenerIframe = findIframeInOpener();\n *\n * // Find a custom iframe\n * const customIframe = findIframeInOpener(\"/my-custom-iframe\");\n * ```\n */\nexport function findIframeInOpener(pathname = \"/listener\"): Window | null {\n if (!window.opener) return null;\n\n const frameCheck = (frame: Window) => {\n try {\n return (\n frame.location.origin === window.location.origin &&\n frame.location.pathname === pathname\n );\n } catch {\n // Cross-origin frame, skip\n return false;\n }\n };\n\n // Check if the openner window is the right one\n if (frameCheck(window.opener)) return window.opener;\n\n // Search through frames in window.opener\n try {\n const frames = window.opener.frames;\n for (let i = 0; i < frames.length; i++) {\n if (frameCheck(frames[i])) {\n return frames[i];\n }\n }\n return null;\n } catch (error) {\n console.error(\n `[findIframeInOpener] Error finding iframe with pathname ${pathname}:`,\n error\n );\n return null;\n }\n}\n\n/**\n * Inject a `<link rel=\"preconnect\">` for the given origin. Idempotent — browsers\n * de-duplicate preconnects per origin automatically, but we also guard on a data\n * attribute to avoid spamming the <head> in SPA re-inits.\n */\nfunction preconnect(url: string): void {\n if (typeof document === \"undefined\") return;\n try {\n const origin = new URL(url).origin;\n const selector = `link[rel=\"preconnect\"][data-frak-preconnect=\"${origin}\"]`;\n if (document.head.querySelector(selector)) return;\n const link = document.createElement(\"link\");\n link.rel = \"preconnect\";\n link.href = origin;\n link.crossOrigin = \"\";\n link.dataset.frakPreconnect = origin;\n document.head.appendChild(link);\n } catch {\n // Invalid URL — nothing to preconnect.\n }\n}\n","import { Deferred } from \"@frak-labs/frame-connector\";\nimport { BACKUP_KEY } from \"../../constants\";\nimport type { FrakLifecycleEvent } from \"../../types\";\nimport {\n isFrakDeepLink,\n triggerDeepLinkWithFallback,\n} from \"../../utils/browser/deepLinkWithFallback\";\nimport { isInAppBrowser, isIOS } from \"../../utils/browser/inAppBrowser\";\nimport { changeIframeVisibility } from \"../../utils/iframe/iframeHelper\";\n\n// iOS in-app browsers (Instagram, Facebook) silently swallow server-side 302\n// redirects to custom URL schemes (x-safari-https://) in WKWebView; direct\n// window.location.href assignment works.\nconst isIOSInAppBrowser = isIOS && isInAppBrowser;\n\n/** @ignore */\nexport type IframeLifecycleManager = {\n isConnected: Promise<boolean>;\n handleEvent: (messageEvent: FrakLifecycleEvent) => void;\n};\n\n/**\n * Handle backup storage\n */\nfunction handleBackup(backup: string | undefined): void {\n if (backup) {\n localStorage.setItem(BACKUP_KEY, backup);\n } else {\n localStorage.removeItem(BACKUP_KEY);\n }\n}\n\n/**\n * Compute final redirect URL with parameter substitution\n */\nfunction computeRedirectUrl(\n baseRedirectUrl: string,\n mergeToken?: string\n): string {\n try {\n const redirectUrl = new URL(baseRedirectUrl);\n if (!redirectUrl.searchParams.has(\"u\")) {\n return baseRedirectUrl;\n }\n\n // Append merge token to the page URL so it survives\n // the backend /common/social redirect chain\n const finalPageUrl = appendMergeToken(window.location.href, mergeToken);\n\n redirectUrl.searchParams.delete(\"u\");\n redirectUrl.searchParams.append(\"u\", finalPageUrl);\n\n return redirectUrl.toString();\n } catch {\n return baseRedirectUrl;\n }\n}\n\n/**\n * Redirect current page to Safari via x-safari-https:// scheme.\n * Used on iOS in-app browsers where backend 302 → custom scheme fails.\n */\nfunction redirectToSafari(mergeToken?: string) {\n const url = new URL(window.location.href);\n if (mergeToken) {\n url.searchParams.set(\"fmt\", mergeToken);\n }\n const scheme =\n url.protocol === \"http:\" ? \"x-safari-http\" : \"x-safari-https\";\n window.location.href = `${scheme}://${url.host}${url.pathname}${url.search}${url.hash}`;\n}\n\n/**\n * Check if this is a social/in-app-browser escape redirect (contains /common/social)\n */\nfunction isSocialRedirect(url: string): boolean {\n return url.includes(\"/common/social\");\n}\n\n/**\n * Append merge token to a URL as the `fmt` query parameter.\n */\nfunction appendMergeToken(urlString: string, mergeToken?: string): string {\n if (!mergeToken) return urlString;\n try {\n const url = new URL(urlString);\n url.searchParams.set(\"fmt\", mergeToken);\n return url.toString();\n } catch {\n const sep = urlString.includes(\"?\") ? \"&\" : \"?\";\n return `${urlString}${sep}fmt=${encodeURIComponent(mergeToken)}`;\n }\n}\n\n/**\n * Handle redirect with deep link fallback\n */\nfunction handleRedirect(\n iframe: HTMLIFrameElement,\n baseRedirectUrl: string,\n targetOrigin: string,\n mergeToken?: string,\n openInNewTab?: boolean\n): void {\n // If requested, open in a new tab instead of navigating the current page.\n // This preserves the merchant page while triggering universal links.\n // Requires the iframe postMessage to include user activation delegation.\n if (openInNewTab) {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n window.open(finalUrl, \"_blank\");\n return;\n }\n\n if (isFrakDeepLink(baseRedirectUrl)) {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n triggerDeepLinkWithFallback(finalUrl, {\n onFallback: () => {\n iframe.contentWindow?.postMessage(\n {\n clientLifecycle: \"deep-link-failed\",\n data: { originalUrl: finalUrl },\n },\n targetOrigin\n );\n },\n });\n } else if (isIOSInAppBrowser && isSocialRedirect(baseRedirectUrl)) {\n // iOS WKWebView silently swallows 302 redirects to custom URL\n // schemes — bypass the server redirect entirely\n redirectToSafari(mergeToken);\n } else {\n const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);\n window.location.href = finalUrl;\n }\n}\n\n/**\n * Create a new iframe lifecycle handler\n * @param args\n * @param args.iframe - The iframe element used for wallet communication\n * @param args.targetOrigin - The wallet URL origin for postMessage security\n * @ignore\n */\nexport function createIFrameLifecycleManager({\n iframe,\n targetOrigin,\n}: {\n iframe: HTMLIFrameElement;\n targetOrigin: string;\n}): IframeLifecycleManager {\n // Create the isConnected listener\n const isConnectedDeferred = new Deferred<boolean>();\n\n // Build the handler itself\n const handler = (messageEvent: FrakLifecycleEvent) => {\n if (!(\"iframeLifecycle\" in messageEvent)) return;\n\n const { iframeLifecycle: event, data } = messageEvent;\n\n switch (event) {\n // Resolve the isConnected promise\n case \"connected\":\n isConnectedDeferred.resolve(true);\n break;\n // Perform a frak backup\n case \"do-backup\":\n handleBackup(data.backup);\n break;\n // Remove frak backup\n case \"remove-backup\":\n localStorage.removeItem(BACKUP_KEY);\n break;\n // Change iframe visibility\n case \"show\":\n case \"hide\":\n changeIframeVisibility({ iframe, isVisible: event === \"show\" });\n break;\n // Redirect handling\n case \"redirect\":\n handleRedirect(\n iframe,\n data.baseRedirectUrl,\n targetOrigin,\n data.mergeToken,\n data.openInNewTab\n );\n break;\n }\n };\n\n return {\n handleEvent: handler,\n isConnected: isConnectedDeferred.promise,\n };\n}\n","import {\n createRpcClient,\n Deferred,\n FrakRpcError,\n type RpcClient,\n RpcErrorCodes,\n} from \"@frak-labs/frame-connector\";\nimport { OpenPanel } from \"@openpanel/web\";\nimport { getClientId } from \"../config/clientId\";\nimport { sdkConfigStore } from \"../config/sdkConfigStore\";\nimport { BACKUP_KEY } from \"../constants\";\nimport type { FrakLifecycleEvent } from \"../types\";\nimport type { FrakClient } from \"../types/client\";\nimport type { FrakWalletSdkConfig } from \"../types/config\";\nimport type { SdkResolvedConfig } from \"../types/resolvedConfig\";\nimport type { IFrameRpcSchema } from \"../types/rpc\";\nimport { clearAllCache } from \"../utils/cache\";\nimport { setupSsoUrlListener } from \"./ssoUrlListener\";\nimport {\n createIFrameLifecycleManager,\n type IframeLifecycleManager,\n} from \"./transports/iframeLifecycleManager\";\n\ntype SdkRpcClient = RpcClient<IFrameRpcSchema, FrakLifecycleEvent>;\ntype MerchantConfigResult = Awaited<ReturnType<typeof sdkConfigStore.resolve>>;\n\n/**\n * Create a new iframe Frak client\n * @param args\n * @param args.config - The configuration to use for the Frak Wallet SDK.\n * 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).\n * @param args.iframe - The iframe to use for the communication\n * @returns The created Frak Client\n *\n * @example\n * const frakConfig: FrakWalletSdkConfig = {\n * metadata: {\n * name: \"My app title\",\n * },\n * }\n * const iframe = await createIframe({ config: frakConfig });\n * const client = createIFrameFrakClient({ config: frakConfig, iframe });\n */\nexport function createIFrameFrakClient({\n config,\n iframe,\n}: {\n config: FrakWalletSdkConfig;\n iframe: HTMLIFrameElement;\n}): FrakClient {\n const frakWalletUrl = config?.walletUrl ?? \"https://wallet.frak.id\";\n\n const browserLang =\n typeof navigator !== \"undefined\"\n ? navigator.language?.split(\"-\")[0]\n : undefined;\n const detectedLang =\n config.metadata.lang ??\n (browserLang === \"en\" || browserLang === \"fr\"\n ? browserLang\n : undefined);\n const targetDomain =\n config.domain ??\n (typeof window !== \"undefined\" ? window.location.hostname : \"\");\n sdkConfigStore.setCacheScope(targetDomain, detectedLang);\n sdkConfigStore.reset();\n\n // Skip fetch entirely if cache is fresh, otherwise fetch (SWR)\n const configPromise = sdkConfigStore.isCacheFresh\n ? undefined\n : sdkConfigStore.resolve(config.domain, config.walletUrl, detectedLang);\n\n // Create lifecycle manager\n const lifecycleManager = createIFrameLifecycleManager({\n iframe,\n targetOrigin: frakWalletUrl,\n });\n\n // Resolved after first resolved-config is sent to iframe (prevents RPC before context exists)\n const contextSent = new Deferred<void>();\n\n // Handshake timing: measured from client creation until the iframe\n // lifecycle manager resolves the `isConnected` promise.\n const handshakeStartedAt = Date.now();\n\n // Validate iframe\n if (!iframe.contentWindow) {\n throw new FrakRpcError(\n RpcErrorCodes.configError,\n \"The iframe does not have a content window\"\n );\n }\n\n // Create RPC client with middleware and lifecycle handlers\n const rpcClient = createRpcClient<IFrameRpcSchema, FrakLifecycleEvent>({\n emittingTransport: iframe.contentWindow,\n listeningTransport: window,\n targetOrigin: frakWalletUrl,\n middleware: [\n // Ensure we are connected and context is sent before sending request\n {\n async onRequest(_message, ctx) {\n const isConnected = await lifecycleManager.isConnected;\n if (!isConnected) {\n throw new FrakRpcError(\n RpcErrorCodes.clientNotConnected,\n \"The iframe provider isn't connected yet\"\n );\n }\n await contextSent.promise;\n return ctx;\n },\n },\n ],\n // Add lifecycle handlers to process iframe lifecycle events\n lifecycleHandlers: {\n iframeLifecycle: (event, _context) => {\n // Delegate to lifecycle manager (cast for type compatibility)\n lifecycleManager.handleEvent(event);\n },\n },\n });\n\n // Setup heartbeat\n const stopHeartbeat = setupHeartbeat(rpcClient, lifecycleManager);\n\n const destroy = async () => {\n stopHeartbeat();\n rpcClient.cleanup();\n iframe.remove();\n clearAllCache();\n sdkConfigStore.clearCache();\n sdkConfigStore.reset();\n };\n\n // Init open panel\n let openPanel: OpenPanel | undefined;\n if (\n process.env.OPEN_PANEL_API_URL &&\n process.env.OPEN_PANEL_SDK_CLIENT_ID\n ) {\n console.log(\"[Frak SDK] Initializing OpenPanel\");\n openPanel = new OpenPanel({\n apiUrl: process.env.OPEN_PANEL_API_URL,\n clientId: process.env.OPEN_PANEL_SDK_CLIENT_ID,\n trackScreenViews: true,\n trackOutgoingLinks: true,\n trackAttributes: false,\n // We use a filter to ensure we got the open panel instance initialized\n // A bit hacky, but this way we are sure that we got everything needed for the first event ever sent\n filter: ({ type, payload }) => {\n if (type !== \"track\") return true;\n if (!payload?.properties) return true;\n\n // Check if we we got the properties once initialized\n if (!(\"sdk_version\" in payload.properties)) {\n payload.properties = {\n ...payload.properties,\n sdk_version: process.env.SDK_VERSION,\n user_anonymous_client_id: getClientId(),\n };\n }\n\n return true;\n },\n });\n openPanel.setGlobalProperties({\n sdk_version: process.env.SDK_VERSION,\n user_anonymous_client_id: getClientId(),\n });\n openPanel.init();\n openPanel.track(\"sdk_initialized\", {\n sdk_version: process.env.SDK_VERSION,\n });\n\n // Race the connection against the heartbeat timeout so we can\n // distinguish \"connected\" from \"timeout\" cleanly without touching\n // the heartbeat plumbing. 30s matches `HEARTBEAT_TIMEOUT`.\n let settled = false;\n const timeoutHandle = setTimeout(() => {\n if (settled) return;\n settled = true;\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"timeout\",\n });\n }, 30_000);\n lifecycleManager.isConnected\n .then(() => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n openPanel?.track(\"sdk_iframe_connected\", {\n handshake_duration_ms: Date.now() - handshakeStartedAt,\n });\n })\n .catch(() => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"unknown\",\n });\n });\n }\n\n // Perform the post connection setup\n const waitForSetup = postConnectionSetup({\n config,\n rpcClient,\n lifecycleManager,\n configPromise,\n contextSent,\n openPanel,\n })\n .then(() => {})\n .catch((err) => {\n contextSent.reject(err);\n throw err;\n });\n\n return {\n config,\n waitForConnection: lifecycleManager.isConnected,\n waitForSetup,\n request: rpcClient.request,\n listenerRequest: rpcClient.listen,\n destroy,\n openPanel,\n };\n}\n\n/**\n * Setup the heartbeat\n * @param rpcClient - RPC client to send lifecycle events\n * @param lifecycleManager - Lifecycle manager to track connection\n */\nfunction setupHeartbeat(\n rpcClient: SdkRpcClient,\n lifecycleManager: IframeLifecycleManager\n) {\n const HEARTBEAT_INTERVAL = 250; // Fallback discovery ping until we are connected\n const HEARTBEAT_TIMEOUT = 30_000; // 30 seconds timeout\n let heartbeatInterval: NodeJS.Timeout;\n let timeoutId: NodeJS.Timeout;\n\n const sendHeartbeat = () =>\n rpcClient.sendLifecycle({\n clientLifecycle: \"heartbeat\",\n });\n\n // Start sending heartbeats\n async function startHeartbeat() {\n sendHeartbeat(); // Send initial heartbeat\n heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);\n\n // Set up timeout\n timeoutId = setTimeout(() => {\n stopHeartbeat();\n console.log(\"Heartbeat timeout: connection failed\");\n }, HEARTBEAT_TIMEOUT);\n\n // Once connected, stop it\n await lifecycleManager.isConnected;\n\n // We are now connected, stop the heartbeat\n stopHeartbeat();\n }\n\n // Stop sending heartbeats\n function stopHeartbeat() {\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n }\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n\n startHeartbeat();\n\n // Return cleanup function\n return stopHeartbeat;\n}\n\n/**\n * Perform the post connection setup\n * @param config - SDK configuration\n * @param rpcClient - RPC client to send lifecycle events\n * @param lifecycleManager - Lifecycle manager to track connection\n */\nasync function postConnectionSetup({\n config,\n rpcClient,\n lifecycleManager,\n configPromise,\n contextSent,\n openPanel,\n}: {\n config: FrakWalletSdkConfig;\n rpcClient: SdkRpcClient;\n lifecycleManager: IframeLifecycleManager;\n configPromise: Promise<MerchantConfigResult> | undefined;\n contextSent: Deferred<void>;\n openPanel: OpenPanel | undefined;\n}): Promise<void> {\n await lifecycleManager.isConnected;\n\n setupSsoUrlListener(rpcClient, lifecycleManager.isConnected);\n\n // Read and consume the pending merge token from URL (SSO identity merge)\n const url = new URL(window.location.href);\n const pendingMergeToken = url.searchParams.get(\"fmt\") ?? undefined;\n if (pendingMergeToken) {\n url.searchParams.delete(\"fmt\");\n window.history.replaceState({}, \"\", url.toString());\n }\n\n // Merge a raw backend response with SDK metadata and persist to store\n const mergeAndSetConfig = (merchantConfig: MerchantConfigResult) => {\n const merchantId =\n merchantConfig?.merchantId ?? config.metadata.merchantId ?? \"\";\n const domain = merchantConfig?.domain ?? \"\";\n const allowedDomains = merchantConfig?.allowedDomains ?? [];\n const raw = merchantConfig?.sdkConfig;\n\n // Per-field merge: backend wins over SDK static config.\n const mergedAttribution =\n raw?.attribution || config.attribution\n ? { ...config.attribution, ...raw?.attribution }\n : undefined;\n\n sdkConfigStore.setConfig(\n raw\n ? {\n isResolved: true,\n merchantId,\n domain,\n allowedDomains,\n hasRawSdkConfig: true,\n name: raw.name ?? config.metadata.name,\n logoUrl: raw.logoUrl ?? config.metadata.logoUrl,\n homepageLink:\n raw.homepageLink ?? config.metadata.homepageLink,\n lang: raw.lang ?? config.metadata.lang,\n currency: raw.currency ?? config.metadata.currency,\n hidden: raw.hidden,\n css: raw.css,\n translations: raw.translations,\n placements: raw.placements,\n components: raw.components,\n attribution: mergedAttribution,\n }\n : {\n isResolved: true,\n merchantId,\n domain,\n allowedDomains,\n name: config.metadata.name,\n logoUrl: config.metadata.logoUrl,\n homepageLink: config.metadata.homepageLink,\n lang: config.metadata.lang,\n currency: config.metadata.currency,\n attribution: mergedAttribution,\n }\n );\n };\n\n // Send the resolved-config lifecycle event to the iframe.\n // This is where we also update SDK-side OpenPanel global props with\n // `merchantId` + `domain` (first time they are known) so every\n // subsequent SDK event is merchant-attributed. We pass\n // `sdkAnonymousId` through so the listener can join SDK funnels.\n let mergeTokenConsumed = false;\n const sendLifecycleConfig = (resolved: SdkResolvedConfig) => {\n const token = mergeTokenConsumed ? undefined : pendingMergeToken;\n mergeTokenConsumed = true;\n\n const sdkConfig = resolved.hasRawSdkConfig\n ? {\n name: resolved.name,\n logoUrl: resolved.logoUrl,\n homepageLink: resolved.homepageLink,\n lang: resolved.lang,\n currency: resolved.currency,\n hidden: resolved.hidden,\n css: resolved.css,\n translations: resolved.translations,\n placements: resolved.placements,\n attribution: resolved.attribution,\n }\n : resolved.attribution\n ? { attribution: resolved.attribution }\n : undefined;\n\n const sdkAnonymousId = getClientId();\n\n if (openPanel) {\n const current = openPanel.global ?? {};\n openPanel.setGlobalProperties({\n ...current,\n merchant_id: resolved.merchantId,\n domain: resolved.domain ?? \"\",\n });\n }\n\n rpcClient.sendLifecycle({\n clientLifecycle: \"resolved-config\",\n data: {\n merchantId: resolved.merchantId,\n domain: resolved.domain ?? \"\",\n allowedDomains: resolved.allowedDomains ?? [],\n sourceUrl: window.location.href,\n ...(sdkAnonymousId && { sdkAnonymousId }),\n ...(token && { pendingMergeToken: token }),\n ...(sdkConfig && { sdkConfig }),\n },\n });\n };\n\n // SWR: if we have cached data, send it to the iframe immediately\n if (sdkConfigStore.isResolved) {\n sendLifecycleConfig(sdkConfigStore.getConfig());\n contextSent.resolve();\n }\n\n // If a fetch is running (stale/missing cache), wait for fresh data and update\n if (configPromise) {\n const merchantConfig = await configPromise;\n mergeAndSetConfig(merchantConfig);\n sendLifecycleConfig(sdkConfigStore.getConfig());\n contextSent.resolve();\n }\n\n // Push raw CSS if needed\n async function pushCss() {\n const cssLink = config.customizations?.css;\n if (!cssLink) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"modal-css\" as const,\n data: { cssLink },\n });\n }\n\n // Push i18n if needed\n async function pushI18n() {\n const i18n = config.customizations?.i18n;\n if (!i18n) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"modal-i18n\" as const,\n data: { i18n },\n });\n }\n\n // Push local backup if needed\n async function pushBackup() {\n if (typeof window === \"undefined\") return;\n const backup = window.localStorage.getItem(BACKUP_KEY);\n if (!backup) return;\n rpcClient.sendLifecycle({\n clientLifecycle: \"restore-backup\" as const,\n data: { backup },\n });\n }\n\n // Inspect each setup result — a failed CSS/i18n/backup push leaves the\n // partner UI in a broken-but-connected state (iframe reports\n // `sdk_iframe_connected`, user sees no modal styles / wrong locale).\n // Surface it as a distinct handshake reason so dashboards can\n // distinguish timeout vs. asset-push failures.\n const results = await Promise.allSettled([\n pushCss(),\n pushI18n(),\n pushBackup(),\n ]);\n const hasFailedAssetPush = results.some((r) => r.status === \"rejected\");\n if (hasFailedAssetPush) {\n openPanel?.track(\"sdk_iframe_handshake_failed\", {\n reason: \"asset_push\",\n });\n }\n}\n","import { jsonDecode } from \"@frak-labs/frame-connector\";\nimport { base64urlDecode } from \"./b64\";\n\n/**\n * Decompress json data\n * @param data\n * @ignore\n */\nexport function decompressJsonFromB64<T>(data: string): T | null {\n return jsonDecode<T>(base64urlDecode(data));\n}\n","import { createIFrameFrakClient } from \"../clients\";\nimport type { FrakClient, FrakWalletSdkConfig } from \"../types\";\nimport { createIframe, getSupportedCurrency } from \"../utils\";\n\n/**\n * Directly setup the Frak client with an iframe\n * Return when the FrakClient is ready (setup and communication estbalished with the wallet)\n *\n * @param config - The configuration to use for the Frak Wallet SDK\n * @returns a Promise with the Frak Client\n *\n * @example\n * const frakConfig: FrakWalletSdkConfig = {\n * metadata: {\n * name: \"My app title\",\n * },\n * }\n * const client = await setupClient({ config: frakConfig });\n */\nexport async function setupClient({\n config,\n}: {\n config: FrakWalletSdkConfig;\n}): Promise<FrakClient | undefined> {\n // Prepare the config\n const preparedConfig = prepareConfig(config);\n\n // Create our iframe\n const iframe = await createIframe({\n config: preparedConfig,\n });\n\n if (!iframe) {\n console.error(\"Failed to create iframe\");\n return;\n }\n\n // Create our client\n const client = createIFrameFrakClient({\n config: preparedConfig,\n iframe,\n });\n\n // Wait for the client to be all setup\n await client.waitForSetup;\n\n // Wait for the connection to be established\n const waitForConnection = await client.waitForConnection;\n if (!waitForConnection) {\n console.error(\"Failed to connect to client\");\n return;\n }\n\n return client;\n}\n\n/**\n * Prepare the config for the Frak Client\n * @param config - The configuration to use for the Frak Wallet SDK\n * @returns The prepared configuration with the supported currency\n */\nfunction prepareConfig(config: FrakWalletSdkConfig): FrakWalletSdkConfig {\n // Get the supported currency (e.g. \"eur\")\n const supportedCurrency = getSupportedCurrency(config.metadata?.currency);\n\n return {\n ...config,\n metadata: {\n ...config.metadata,\n currency: supportedCurrency,\n },\n };\n}\n","import type { AttributionDefaults, AttributionParams } from \"../types/tracking\";\n\n/**\n * Inputs for {@link mergeAttribution}.\n */\nexport type MergeAttributionInput = {\n /**\n * Per-call attribution override passed to actions like `displaySharingPage`.\n *\n * - `null` explicitly disables attribution (no UTM/ref/via params are added).\n * - `undefined` means \"no per-call override\" — defaults apply if present.\n * - An object (including `{}`) merges field-by-field with defaults.\n */\n perCall: AttributionParams | null | undefined;\n /**\n * Pre-merged merchant-level defaults (backend config > SDK static config).\n * `utm_content` is intentionally absent from this shape.\n */\n defaults?: AttributionDefaults;\n /**\n * Per-product `utm_content` override (from the currently selected\n * `SharingPageProduct`). Takes precedence over `perCall.utmContent`.\n */\n productUtmContent?: string;\n};\n\n/**\n * Merge the three attribution layers into a single {@link AttributionParams}\n * value suitable for `FrakContextManager.update`.\n *\n * Priority per field:\n * 1. `perCall` (wins)\n * 2. `defaults` (merchant-level, backend > SDK static, already pre-merged)\n * 3. Hardcoded fallbacks resolved later by `FrakContextManager`\n *\n * Special rules:\n * - `perCall === null` returns `undefined` (explicit disable: no UTM/ref/via).\n * - `perCall === undefined` (no opinion) yields at least `{}` so `FrakContextManager`\n * applies its hardcoded defaults (utm_source=frak, utm_medium=referral,\n * utm_campaign=<merchantId>, ref=<clientId>, via=frak).\n * - `utm_content` never comes from `defaults`; only `productUtmContent` or\n * `perCall.utmContent` can populate it.\n */\nexport function mergeAttribution({\n perCall,\n defaults,\n productUtmContent,\n}: MergeAttributionInput): AttributionParams | undefined {\n // Explicit disable\n if (perCall === null) return undefined;\n\n const hasPerCall = perCall !== undefined;\n const hasDefaults =\n defaults !== undefined && Object.keys(defaults).length > 0;\n const hasProductUtm =\n productUtmContent !== undefined && productUtmContent !== \"\";\n\n if (!hasPerCall && !hasDefaults && !hasProductUtm) return undefined;\n\n // Per-field merge: per-call wins over defaults.\n const merged: AttributionParams = {\n ...defaults,\n ...(perCall ?? {}),\n };\n\n // utm_content priority: productUtmContent > perCall.utmContent; never from defaults.\n const utmContent = productUtmContent ?? perCall?.utmContent;\n if (utmContent !== undefined && utmContent !== \"\") {\n merged.utmContent = utmContent;\n } else {\n delete merged.utmContent;\n }\n\n return merged;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "1.1.4",
14
+ "version": "1.1.5-beta.1700e6ec",
15
15
  "description": "Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -37,10 +37,10 @@
37
37
  "/cdn",
38
38
  "/src"
39
39
  ],
40
- "browser": "./cdn/bundle.js",
41
40
  "exports": {
42
41
  ".": {
43
42
  "development": "./src/index.ts",
43
+ "production": "./src/index.ts",
44
44
  "import": {
45
45
  "types": "./dist/index.d.ts",
46
46
  "default": "./dist/index.js"
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "./actions": {
54
54
  "development": "./src/actions/index.ts",
55
+ "production": "./src/actions/index.ts",
55
56
  "import": {
56
57
  "types": "./dist/actions.d.ts",
57
58
  "default": "./dist/actions.js"
@@ -70,6 +71,18 @@
70
71
  "types": "./dist/bundle.d.cts",
71
72
  "default": "./dist/bundle.cjs"
72
73
  }
74
+ },
75
+ "./rewards": {
76
+ "development": "./src/rewards/index.ts",
77
+ "production": "./src/rewards/index.ts",
78
+ "import": {
79
+ "types": "./dist/rewards.d.ts",
80
+ "default": "./dist/rewards.js"
81
+ },
82
+ "require": {
83
+ "types": "./dist/rewards.d.cts",
84
+ "default": "./dist/rewards.cjs"
85
+ }
73
86
  }
74
87
  },
75
88
  "scripts": {
@@ -91,7 +104,7 @@
91
104
  "viem": "^2.x"
92
105
  },
93
106
  "dependencies": {
94
- "@frak-labs/frame-connector": "0.2.0",
107
+ "@frak-labs/frame-connector": "0.2.0-beta.1700e6ec",
95
108
  "@openpanel/web": "^1.4.1"
96
109
  },
97
110
  "devDependencies": {
@@ -68,6 +68,7 @@ describe("getMerchantInformation", () => {
68
68
  {
69
69
  token: "0x1234567890123456789012345678901234567890" as Address,
70
70
  campaignId: "campaign-1",
71
+ name: "Campaign 1",
71
72
  interactionTypeKey: "referral",
72
73
  referrer: {
73
74
  payoutType: "fixed",
@@ -87,6 +88,7 @@ describe("getMerchantInformation", () => {
87
88
  gbpAmount: 0.45,
88
89
  },
89
90
  },
91
+ conditions: [],
90
92
  },
91
93
  ],
92
94
  };
@@ -105,9 +105,9 @@ describe("processReferral", () => {
105
105
  mockClient,
106
106
  "user_referred_started",
107
107
  {
108
- referrerClientId: "referrer-client-id",
109
- referrerWallet: undefined,
110
- walletStatus: "connected",
108
+ referrer_client_id: "referrer-client-id",
109
+ referrer_wallet: undefined,
110
+ wallet_status: "connected",
111
111
  }
112
112
  );
113
113
 
@@ -235,7 +235,7 @@ describe("processReferral", () => {
235
235
  "user_referred_started",
236
236
  {
237
237
  referrer: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
238
- walletStatus: "connected",
238
+ wallet_status: "connected",
239
239
  }
240
240
  );
241
241
  });
@@ -53,9 +53,9 @@ function trackArrivalIfValid(
53
53
  ): boolean {
54
54
  if (isV2Context(frakContext)) {
55
55
  trackEvent(client, "user_referred_started", {
56
- referrerClientId: frakContext.c,
57
- referrerWallet: frakContext.w,
58
- walletStatus: walletStatus?.key,
56
+ referrer_client_id: frakContext.c,
57
+ referrer_wallet: frakContext.w,
58
+ wallet_status: walletStatus?.key,
59
59
  });
60
60
  sendInteraction(client, {
61
61
  type: "arrival",
@@ -70,7 +70,7 @@ function trackArrivalIfValid(
70
70
  if (isV1Context(frakContext)) {
71
71
  trackEvent(client, "user_referred_started", {
72
72
  referrer: frakContext.r,
73
- walletStatus: walletStatus?.key,
73
+ wallet_status: walletStatus?.key,
74
74
  });
75
75
  sendInteraction(client, {
76
76
  type: "arrival",
@@ -153,11 +153,11 @@ export function createIFrameFrakClient({
153
153
  if (!payload?.properties) return true;
154
154
 
155
155
  // Check if we we got the properties once initialized
156
- if (!("sdkVersion" in payload.properties)) {
156
+ if (!("sdk_version" in payload.properties)) {
157
157
  payload.properties = {
158
158
  ...payload.properties,
159
- sdkVersion: process.env.SDK_VERSION,
160
- userAnonymousClientId: getClientId(),
159
+ sdk_version: process.env.SDK_VERSION,
160
+ user_anonymous_client_id: getClientId(),
161
161
  };
162
162
  }
163
163
 
@@ -165,12 +165,12 @@ export function createIFrameFrakClient({
165
165
  },
166
166
  });
167
167
  openPanel.setGlobalProperties({
168
- sdkVersion: process.env.SDK_VERSION,
169
- userAnonymousClientId: getClientId(),
168
+ sdk_version: process.env.SDK_VERSION,
169
+ user_anonymous_client_id: getClientId(),
170
170
  });
171
171
  openPanel.init();
172
172
  openPanel.track("sdk_initialized", {
173
- sdkVersion: process.env.SDK_VERSION,
173
+ sdk_version: process.env.SDK_VERSION,
174
174
  });
175
175
 
176
176
  // Race the connection against the heartbeat timeout so we can
@@ -398,7 +398,7 @@ async function postConnectionSetup({
398
398
  const current = openPanel.global ?? {};
399
399
  openPanel.setGlobalProperties({
400
400
  ...current,
401
- merchantId: resolved.merchantId,
401
+ merchant_id: resolved.merchantId,
402
402
  domain: resolved.domain ?? "",
403
403
  });
404
404
  }
@@ -5,29 +5,13 @@ import {
5
5
  isFrakDeepLink,
6
6
  triggerDeepLinkWithFallback,
7
7
  } from "../../utils/browser/deepLinkWithFallback";
8
+ import { isInAppBrowser, isIOS } from "../../utils/browser/inAppBrowser";
8
9
  import { changeIframeVisibility } from "../../utils/iframe/iframeHelper";
9
10
 
10
- /**
11
- * Detect iOS in-app browsers (Instagram, Facebook) where server-side
12
- * 302 redirects to custom URL schemes (x-safari-https://) are silently
13
- * swallowed by WKWebView. Direct window.location.href assignment works.
14
- */
15
- const isIOSInAppBrowser = (() => {
16
- if (typeof navigator === "undefined") return false;
17
- const ua = navigator.userAgent;
18
- // Standard iOS or iPadOS 13+ (reports as Macintosh with touch)
19
- const isIOS =
20
- /iPhone|iPad|iPod/i.test(ua) ||
21
- (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1);
22
- if (!isIOS) return false;
23
- const lower = ua.toLowerCase();
24
- return (
25
- lower.includes("instagram") ||
26
- lower.includes("fban") ||
27
- lower.includes("fbav") ||
28
- lower.includes("facebook")
29
- );
30
- })();
11
+ // iOS in-app browsers (Instagram, Facebook) silently swallow server-side 302
12
+ // redirects to custom URL schemes (x-safari-https://) in WKWebView; direct
13
+ // window.location.href assignment works.
14
+ const isIOSInAppBrowser = isIOS && isInAppBrowser;
31
15
 
32
16
  /** @ignore */
33
17
  export type IframeLifecycleManager = {