@frak-labs/core-sdk 0.1.1 → 0.2.0

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 (125) hide show
  1. package/README.md +58 -0
  2. package/cdn/bundle.js +14 -0
  3. package/dist/actions.cjs +1 -1
  4. package/dist/actions.d.cts +3 -3
  5. package/dist/actions.d.ts +3 -3
  6. package/dist/actions.js +1 -1
  7. package/dist/bundle.cjs +1 -1
  8. package/dist/bundle.d.cts +4 -6
  9. package/dist/bundle.d.ts +4 -6
  10. package/dist/bundle.js +1 -1
  11. package/dist/{index-CRsQWnTs.d.cts → computeLegacyProductId-BkyJ4rEY.d.ts} +197 -10
  12. package/dist/{index-Ck1hudEi.d.ts → computeLegacyProductId-Raks6FXg.d.cts} +197 -10
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +3 -4
  15. package/dist/index.d.ts +3 -4
  16. package/dist/index.js +1 -1
  17. package/dist/{openSso-D--Airj6.d.cts → openSso-BCJGchIb.d.cts} +135 -131
  18. package/dist/{openSso-DsKJ4y0j.d.ts → openSso-DG-_9CED.d.ts} +135 -131
  19. package/dist/setupClient-CQrMDGyZ.js +13 -0
  20. package/dist/setupClient-Ccv3XxwL.cjs +13 -0
  21. package/dist/{index-d8xS4ryI.d.ts → siweAuthenticate-BH7Dn7nZ.d.cts} +90 -65
  22. package/dist/siweAuthenticate-BJHbtty4.js +1 -0
  23. package/dist/{index-C6FxkWPC.d.cts → siweAuthenticate-Btem4QHs.d.ts} +90 -65
  24. package/dist/siweAuthenticate-Cwj3HP0m.cjs +1 -0
  25. package/dist/trackEvent-M2RLTQ2p.js +1 -0
  26. package/dist/trackEvent-T_R9ER2S.cjs +1 -0
  27. package/package.json +11 -22
  28. package/src/actions/displayEmbeddedWallet.ts +1 -0
  29. package/src/actions/displayModal.test.ts +12 -11
  30. package/src/actions/displayModal.ts +7 -18
  31. package/src/actions/ensureIdentity.ts +68 -0
  32. package/src/actions/{getProductInformation.test.ts → getMerchantInformation.test.ts} +33 -50
  33. package/src/actions/getMerchantInformation.ts +16 -0
  34. package/src/actions/index.ts +3 -2
  35. package/src/actions/openSso.ts +4 -2
  36. package/src/actions/referral/processReferral.test.ts +42 -151
  37. package/src/actions/referral/processReferral.ts +18 -42
  38. package/src/actions/referral/referralInteraction.test.ts +1 -7
  39. package/src/actions/referral/referralInteraction.ts +1 -6
  40. package/src/actions/sendInteraction.ts +46 -22
  41. package/src/actions/trackPurchaseStatus.test.ts +354 -141
  42. package/src/actions/trackPurchaseStatus.ts +48 -11
  43. package/src/actions/watchWalletStatus.ts +2 -3
  44. package/src/actions/wrapper/modalBuilder.test.ts +0 -14
  45. package/src/actions/wrapper/modalBuilder.ts +3 -12
  46. package/src/bundle.ts +0 -1
  47. package/src/clients/createIFrameFrakClient.ts +10 -5
  48. package/src/clients/transports/iframeLifecycleManager.test.ts +163 -4
  49. package/src/clients/transports/iframeLifecycleManager.ts +172 -33
  50. package/src/constants/interactionTypes.ts +12 -41
  51. package/src/index.ts +24 -16
  52. package/src/types/config.ts +6 -0
  53. package/src/types/index.ts +13 -10
  54. package/src/types/lifecycle/client.ts +24 -1
  55. package/src/types/lifecycle/iframe.ts +6 -0
  56. package/src/types/rpc/displayModal.ts +2 -4
  57. package/src/types/rpc/embedded/index.ts +2 -2
  58. package/src/types/rpc/interaction.ts +26 -39
  59. package/src/types/rpc/merchantInformation.ts +77 -0
  60. package/src/types/rpc/modal/index.ts +0 -4
  61. package/src/types/rpc/modal/login.ts +5 -1
  62. package/src/types/rpc/walletStatus.ts +1 -7
  63. package/src/types/rpc.ts +22 -30
  64. package/src/types/tracking.ts +60 -0
  65. package/src/utils/backendUrl.test.ts +83 -0
  66. package/src/utils/backendUrl.ts +62 -0
  67. package/src/utils/clientId.test.ts +41 -0
  68. package/src/utils/clientId.ts +43 -0
  69. package/src/utils/compression/compress.test.ts +1 -1
  70. package/src/utils/compression/compress.ts +2 -2
  71. package/src/utils/compression/decompress.test.ts +8 -4
  72. package/src/utils/compression/decompress.ts +2 -2
  73. package/src/utils/{computeProductId.ts → computeLegacyProductId.ts} +2 -2
  74. package/src/utils/constants.ts +5 -0
  75. package/src/utils/deepLinkWithFallback.test.ts +243 -0
  76. package/src/utils/deepLinkWithFallback.ts +103 -0
  77. package/src/utils/formatAmount.ts +6 -0
  78. package/src/utils/iframeHelper.test.ts +18 -5
  79. package/src/utils/iframeHelper.ts +10 -3
  80. package/src/utils/index.ts +16 -1
  81. package/src/utils/merchantId.test.ts +653 -0
  82. package/src/utils/merchantId.ts +143 -0
  83. package/src/utils/sso.ts +18 -11
  84. package/src/utils/trackEvent.test.ts +23 -5
  85. package/src/utils/trackEvent.ts +13 -0
  86. package/cdn/bundle.iife.js +0 -14
  87. package/dist/actions-B5j-i1p0.cjs +0 -1
  88. package/dist/actions-q090Z0oR.js +0 -1
  89. package/dist/index-7OZ39x1U.d.ts +0 -195
  90. package/dist/index-zDq-VlKx.d.cts +0 -195
  91. package/dist/interaction-DMJ3ZfaF.d.cts +0 -45
  92. package/dist/interaction-KX1h9a7V.d.ts +0 -45
  93. package/dist/interactions-DnfM3oe0.js +0 -1
  94. package/dist/interactions-EIXhNLf6.cjs +0 -1
  95. package/dist/interactions.cjs +0 -1
  96. package/dist/interactions.d.cts +0 -2
  97. package/dist/interactions.d.ts +0 -2
  98. package/dist/interactions.js +0 -1
  99. package/dist/productTypes-BUkXJKZ7.cjs +0 -1
  100. package/dist/productTypes-CGb1MmBF.js +0 -1
  101. package/dist/src-1LQ4eLq5.js +0 -13
  102. package/dist/src-hW71KjPN.cjs +0 -13
  103. package/dist/trackEvent-CHnYa85W.js +0 -1
  104. package/dist/trackEvent-GuQm_1Nm.cjs +0 -1
  105. package/src/actions/getProductInformation.ts +0 -14
  106. package/src/actions/openSso.test.ts +0 -407
  107. package/src/actions/sendInteraction.test.ts +0 -219
  108. package/src/constants/interactionTypes.test.ts +0 -128
  109. package/src/constants/productTypes.test.ts +0 -130
  110. package/src/constants/productTypes.ts +0 -33
  111. package/src/interactions/index.ts +0 -5
  112. package/src/interactions/pressEncoder.test.ts +0 -215
  113. package/src/interactions/pressEncoder.ts +0 -53
  114. package/src/interactions/purchaseEncoder.test.ts +0 -291
  115. package/src/interactions/purchaseEncoder.ts +0 -99
  116. package/src/interactions/referralEncoder.test.ts +0 -170
  117. package/src/interactions/referralEncoder.ts +0 -47
  118. package/src/interactions/retailEncoder.test.ts +0 -107
  119. package/src/interactions/retailEncoder.ts +0 -37
  120. package/src/interactions/webshopEncoder.test.ts +0 -56
  121. package/src/interactions/webshopEncoder.ts +0 -30
  122. package/src/types/rpc/modal/openSession.ts +0 -25
  123. package/src/types/rpc/productInformation.ts +0 -59
  124. package/src/utils/computeProductId.test.ts +0 -80
  125. package/src/utils/sso.test.ts +0 -361
@@ -0,0 +1,77 @@
1
+ import type { Address } from "viem";
2
+ import type { InteractionTypeKey } from "../../constants/interactionTypes";
3
+
4
+ /**
5
+ * The type for the amount of tokens
6
+ */
7
+ export type TokenAmountType = {
8
+ amount: number;
9
+ eurAmount: number;
10
+ usdAmount: number;
11
+ gbpAmount: number;
12
+ };
13
+
14
+ /**
15
+ * A tier definition for tiered rewards
16
+ */
17
+ export type RewardTier = {
18
+ minValue: number;
19
+ maxValue?: number;
20
+ amount: TokenAmountType;
21
+ };
22
+
23
+ /**
24
+ * Estimated reward amount — discriminated union by payout type
25
+ *
26
+ * - `fixed`: A known token amount (with fiat equivalents)
27
+ * - `percentage`: A percent of a purchase field (e.g. 5% of purchase_amount), with optional min/max caps
28
+ * - `tiered`: Amount depends on a field value matching tier brackets
29
+ */
30
+ export type EstimatedReward =
31
+ | {
32
+ payoutType: "fixed";
33
+ amount: TokenAmountType;
34
+ }
35
+ | {
36
+ payoutType: "percentage";
37
+ percent: number;
38
+ percentOf: string;
39
+ maxAmount?: TokenAmountType;
40
+ minAmount?: TokenAmountType;
41
+ }
42
+ | {
43
+ payoutType: "tiered";
44
+ tierField: string;
45
+ tiers: RewardTier[];
46
+ };
47
+
48
+ /**
49
+ * Response of the `frak_getMerchantInformation` RPC method
50
+ * @group RPC Schema
51
+ */
52
+ export type GetMerchantInformationReturnType = {
53
+ /**
54
+ * Current merchant id
55
+ */
56
+ id: string;
57
+ /**
58
+ * Some metadata
59
+ */
60
+ onChainMetadata: {
61
+ /**
62
+ * Name of the merchant on-chain
63
+ */
64
+ name: string;
65
+ /**
66
+ * Domain of the merchant on-chain
67
+ */
68
+ domain: string;
69
+ };
70
+ rewards: {
71
+ token?: Address;
72
+ campaignId: string;
73
+ interactionTypeKey: InteractionTypeKey;
74
+ referrer?: EstimatedReward;
75
+ referee?: EstimatedReward;
76
+ }[];
77
+ };
@@ -4,10 +4,6 @@ export type {
4
4
  } from "./final";
5
5
  export type { ModalStepMetadata } from "./generic";
6
6
  export type { LoginModalStepType } from "./login";
7
- export type {
8
- OpenInteractionSessionModalStepType,
9
- OpenInteractionSessionReturnType,
10
- } from "./openSession";
11
7
  export type {
12
8
  SiweAuthenticateModalStepType,
13
9
  SiweAuthenticateReturnType,
@@ -1,4 +1,4 @@
1
- import type { Address } from "viem";
1
+ import type { Address, Hex } from "viem";
2
2
  import type { SsoMetadata } from "../sso";
3
3
  import type { GenericModalStepType } from "./generic";
4
4
 
@@ -28,5 +28,9 @@ export type LoginModalStepType = GenericModalStepType<
28
28
  LoginWithSso | LoginWithoutSso,
29
29
  {
30
30
  wallet: Address;
31
+ webauthnProof?: {
32
+ challenge: Hex;
33
+ authenticatorResponse: string;
34
+ };
31
35
  }
32
36
  >;
@@ -14,13 +14,8 @@ export type WalletConnected = {
14
14
  key: "connected";
15
15
  // The user wallet address
16
16
  wallet: Address;
17
- // The interaction token, used to push interactions to the delegator if needed
17
+ // The interaction token, used to push interactions to the backend
18
18
  interactionToken?: string;
19
- // The current onchain interaction session of the user
20
- interactionSession?: {
21
- startTimestamp: number;
22
- endTimestamp: number;
23
- };
24
19
  };
25
20
 
26
21
  /**
@@ -31,5 +26,4 @@ export type WalletNotConnected = {
31
26
  key: "not-connected";
32
27
  wallet?: never;
33
28
  interactionToken?: never;
34
- interactionSession?: never;
35
29
  };
package/src/types/rpc.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Hex } from "viem";
2
1
  import type { FrakWalletSdkConfig } from "./config";
3
2
  import type {
4
3
  ModalRpcMetadata,
@@ -9,11 +8,8 @@ import type {
9
8
  DisplayEmbeddedWalletParamsType,
10
9
  DisplayEmbeddedWalletResultType,
11
10
  } from "./rpc/embedded";
12
- import type {
13
- PreparedInteraction,
14
- SendInteractionReturnType,
15
- } from "./rpc/interaction";
16
- import type { GetProductInformationReturnType } from "./rpc/productInformation";
11
+ import type { SendInteractionParamsType } from "./rpc/interaction";
12
+ import type { GetMerchantInformationReturnType } from "./rpc/merchantInformation";
17
13
  import type {
18
14
  OpenSsoParamsType,
19
15
  OpenSsoReturnType,
@@ -46,19 +42,14 @@ import type { WalletStatusReturnType } from "./rpc/walletStatus";
46
42
  * - Returns: {@link ModalRpcStepsResultType}
47
43
  * - Response Type: promise (one-shot)
48
44
  *
49
- * #### frak_sendInteraction
50
- * - Params: [productId: Hex, interaction: {@link PreparedInteraction}, signature?: Hex]
51
- * - Returns: {@link SendInteractionReturnType}
52
- * - Response Type: promise (one-shot)
53
- *
54
45
  * #### frak_sso
55
46
  * - Params: [params: {@link OpenSsoParamsType}, name: string, customCss?: string]
56
47
  * - Returns: {@link OpenSsoReturnType}
57
48
  * - Response Type: promise (one-shot)
58
49
  *
59
- * #### frak_getProductInformation
50
+ * #### frak_getMerchantInformation
60
51
  * - Params: None
61
- * - Returns: {@link GetProductInformationReturnType}
52
+ * - Returns: {@link GetMerchantInformationReturnType}
62
53
  * - Response Type: promise (one-shot)
63
54
  *
64
55
  * #### frak_displayEmbeddedWallet
@@ -89,19 +80,6 @@ export type IFrameRpcSchema = [
89
80
  ];
90
81
  ReturnType: ModalRpcStepsResultType;
91
82
  },
92
- /**
93
- * Method to transmit a user interaction
94
- * This is a one-shot request
95
- */
96
- {
97
- Method: "frak_sendInteraction";
98
- Parameters: [
99
- productId: Hex,
100
- interaction: PreparedInteraction,
101
- signature?: Hex,
102
- ];
103
- ReturnType: SendInteractionReturnType;
104
- },
105
83
  /**
106
84
  * Method to prepare SSO (generate URL for popup)
107
85
  * Returns the SSO URL that should be opened in a popup
@@ -132,16 +110,16 @@ export type IFrameRpcSchema = [
132
110
  ReturnType: OpenSsoReturnType;
133
111
  },
134
112
  /**
135
- * Method to get current product information's
136
- * - Is product minted?
113
+ * Method to get current merchant information
114
+ * - Is merchant registered?
137
115
  * - Does it have running campaign?
138
116
  * - Estimated reward on actions
139
117
  * This is a one-shot request
140
118
  */
141
119
  {
142
- Method: "frak_getProductInformation";
120
+ Method: "frak_getMerchantInformation";
143
121
  Parameters?: undefined;
144
- ReturnType: GetProductInformationReturnType;
122
+ ReturnType: GetMerchantInformationReturnType;
145
123
  },
146
124
  /**
147
125
  * Method to show the embedded wallet, with potential customization
@@ -155,4 +133,18 @@ export type IFrameRpcSchema = [
155
133
  ];
156
134
  ReturnType: DisplayEmbeddedWalletResultType;
157
135
  },
136
+ /**
137
+ * Method to send interactions (arrival, sharing, custom events)
138
+ * Fire-and-forget method - no return value expected
139
+ * merchantId is resolved from context
140
+ * clientId is passed via metadata as safeguard against handshake race condition
141
+ */
142
+ {
143
+ Method: "frak_sendInteraction";
144
+ Parameters: [
145
+ interaction: SendInteractionParamsType,
146
+ metadata?: { clientId?: string },
147
+ ];
148
+ ReturnType: undefined;
149
+ },
158
150
  ];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Types for arrival tracking and referral attribution
3
+ * @category Tracking
4
+ */
5
+
6
+ import type { Address } from "viem";
7
+
8
+ /**
9
+ * UTM parameters for marketing attribution
10
+ * @category Tracking
11
+ */
12
+ export type UtmParams = {
13
+ source?: string;
14
+ medium?: string;
15
+ campaign?: string;
16
+ term?: string;
17
+ content?: string;
18
+ };
19
+
20
+ /**
21
+ * Parameters for tracking an arrival event
22
+ * @category Tracking
23
+ */
24
+ export type TrackArrivalParams = {
25
+ /**
26
+ * The referrer wallet address (from fCtx URL param)
27
+ */
28
+ referrerWallet?: Address;
29
+ /**
30
+ * The landing page URL (defaults to current page)
31
+ */
32
+ landingUrl?: string;
33
+ /**
34
+ * UTM parameters for marketing attribution
35
+ */
36
+ utmParams?: UtmParams;
37
+ };
38
+
39
+ /**
40
+ * Result from tracking an arrival event
41
+ * @category Tracking
42
+ */
43
+ export type TrackArrivalResult = {
44
+ success: boolean;
45
+ identityGroupId?: string;
46
+ touchpointId?: string;
47
+ error?: string;
48
+ };
49
+
50
+ /**
51
+ * Internal params passed to the trackArrival action
52
+ * Includes merchantId resolved from config or fetch
53
+ * @internal
54
+ */
55
+ export type TrackArrivalInternalParams = TrackArrivalParams & {
56
+ /**
57
+ * The merchant ID (UUID from dashboard)
58
+ */
59
+ merchantId: string;
60
+ };
@@ -0,0 +1,83 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { getBackendUrl } from "./backendUrl";
3
+
4
+ describe("getBackendUrl", () => {
5
+ const originalWindow = globalThis.window;
6
+
7
+ beforeEach(() => {
8
+ vi.stubGlobal("window", { ...originalWindow });
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.unstubAllGlobals();
13
+ });
14
+
15
+ describe("with explicit walletUrl", () => {
16
+ test("should return localhost backend for localhost:3000", () => {
17
+ expect(getBackendUrl("https://localhost:3000")).toBe(
18
+ "http://localhost:3030"
19
+ );
20
+ });
21
+
22
+ test("should return localhost backend for localhost:3010", () => {
23
+ expect(getBackendUrl("https://localhost:3010")).toBe(
24
+ "http://localhost:3030"
25
+ );
26
+ });
27
+
28
+ test("should return dev backend for wallet-dev.frak.id", () => {
29
+ expect(getBackendUrl("https://wallet-dev.frak.id")).toBe(
30
+ "https://backend.gcp-dev.frak.id"
31
+ );
32
+ });
33
+
34
+ test("should return dev backend for wallet.gcp-dev.frak.id", () => {
35
+ expect(getBackendUrl("https://wallet.gcp-dev.frak.id")).toBe(
36
+ "https://backend.gcp-dev.frak.id"
37
+ );
38
+ });
39
+
40
+ test("should return production backend for wallet.frak.id", () => {
41
+ expect(getBackendUrl("https://wallet.frak.id")).toBe(
42
+ "https://backend.frak.id"
43
+ );
44
+ });
45
+
46
+ test("should return production backend for unknown URLs", () => {
47
+ expect(getBackendUrl("https://some-other-url.com")).toBe(
48
+ "https://backend.frak.id"
49
+ );
50
+ });
51
+ });
52
+
53
+ describe("with FrakSetup global config", () => {
54
+ test("should derive from window.FrakSetup.client.config.walletUrl", () => {
55
+ vi.stubGlobal("window", {
56
+ FrakSetup: {
57
+ client: {
58
+ config: {
59
+ walletUrl: "https://wallet-dev.frak.id",
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ expect(getBackendUrl()).toBe("https://backend.gcp-dev.frak.id");
66
+ });
67
+
68
+ test("should fall back to production when FrakSetup has no walletUrl", () => {
69
+ vi.stubGlobal("window", {
70
+ FrakSetup: { client: { config: {} } },
71
+ });
72
+
73
+ expect(getBackendUrl()).toBe("https://backend.frak.id");
74
+ });
75
+ });
76
+
77
+ describe("fallback", () => {
78
+ test("should return production URL when no walletUrl and no FrakSetup", () => {
79
+ vi.stubGlobal("window", {});
80
+ expect(getBackendUrl()).toBe("https://backend.frak.id");
81
+ });
82
+ });
83
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Default production backend URL
3
+ */
4
+ const DEFAULT_BACKEND_URL = "https://backend.frak.id";
5
+
6
+ /**
7
+ * Check if wallet URL is local development (port 3000 or 3010)
8
+ */
9
+ function isLocalDevelopment(walletUrl: string): boolean {
10
+ return (
11
+ walletUrl.includes("localhost:3000") ||
12
+ walletUrl.includes("localhost:3010")
13
+ );
14
+ }
15
+
16
+ /**
17
+ * Derive backend URL from wallet URL
18
+ * Maps wallet URLs to their corresponding backend URLs
19
+ */
20
+ function deriveBackendUrl(walletUrl: string): string {
21
+ if (isLocalDevelopment(walletUrl)) {
22
+ return "http://localhost:3030";
23
+ }
24
+ // Dev environment
25
+ if (
26
+ walletUrl.includes("wallet-dev.frak.id") ||
27
+ walletUrl.includes("wallet.gcp-dev.frak.id")
28
+ ) {
29
+ return "https://backend.gcp-dev.frak.id";
30
+ }
31
+ // Production
32
+ return DEFAULT_BACKEND_URL;
33
+ }
34
+
35
+ /**
36
+ * Get the backend URL for API calls
37
+ * Tries to derive from SDK config, falls back to production
38
+ *
39
+ * @param walletUrl - Optional wallet URL to derive from (overrides global config)
40
+ */
41
+ export function getBackendUrl(walletUrl?: string): string {
42
+ // If explicit walletUrl provided, derive from it
43
+ if (walletUrl) {
44
+ return deriveBackendUrl(walletUrl);
45
+ }
46
+
47
+ // Try to get from global FrakSetup config
48
+ if (typeof window !== "undefined") {
49
+ const configWalletUrl = (
50
+ window as {
51
+ FrakSetup?: { client?: { config?: { walletUrl?: string } } };
52
+ }
53
+ ).FrakSetup?.client?.config?.walletUrl;
54
+
55
+ if (configWalletUrl) {
56
+ return deriveBackendUrl(configWalletUrl);
57
+ }
58
+ }
59
+
60
+ // Fallback to production
61
+ return DEFAULT_BACKEND_URL;
62
+ }
@@ -0,0 +1,41 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { getClientId } from "./clientId";
3
+
4
+ describe("clientId", () => {
5
+ beforeEach(() => {
6
+ // Clear localStorage before each test
7
+ localStorage.clear();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ describe("getClientId", () => {
15
+ it("should generate and store a new client ID when none exists", () => {
16
+ const clientId = getClientId();
17
+
18
+ expect(clientId).toBeDefined();
19
+ expect(clientId).toMatch(
20
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
21
+ );
22
+ expect(localStorage.getItem("frak-client-id")).toBe(clientId);
23
+ });
24
+
25
+ it("should return existing client ID from localStorage", () => {
26
+ const existingId = "existing-uuid-1234";
27
+ localStorage.setItem("frak-client-id", existingId);
28
+
29
+ const clientId = getClientId();
30
+
31
+ expect(clientId).toBe(existingId);
32
+ });
33
+
34
+ it("should generate consistent UUIDs", () => {
35
+ const id1 = getClientId();
36
+ const id2 = getClientId();
37
+
38
+ expect(id1).toBe(id2);
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Client ID utilities for anonymous tracking
3
+ * Generates and persists a UUID fingerprint for referral attribution
4
+ */
5
+
6
+ const CLIENT_ID_KEY = "frak-client-id";
7
+
8
+ /**
9
+ * Generate a UUID v4
10
+ * Uses crypto.randomUUID if available, otherwise falls back to a manual implementation
11
+ */
12
+ function generateUUID(): string {
13
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
14
+ return crypto.randomUUID();
15
+ }
16
+ // Fallback for older browsers
17
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
18
+ const r = (Math.random() * 16) | 0;
19
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
20
+ return v.toString(16);
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Get the client ID from localStorage, creating one if it doesn't exist
26
+ * @returns The client ID (UUID format)
27
+ */
28
+ export function getClientId(): string {
29
+ if (typeof window === "undefined" || !window.localStorage) {
30
+ // SSR or no localStorage - generate ephemeral ID
31
+ console.warn(
32
+ "[Frak SDK] No Window / localStorage available to save the clientId"
33
+ );
34
+ return generateUUID();
35
+ }
36
+
37
+ let clientId = localStorage.getItem(CLIENT_ID_KEY);
38
+ if (!clientId) {
39
+ clientId = generateUUID();
40
+ localStorage.setItem(CLIENT_ID_KEY, clientId);
41
+ }
42
+ return clientId;
43
+ }
@@ -7,7 +7,7 @@ import { vi } from "vitest";
7
7
 
8
8
  // Mock the frame-connector module - must be before imports
9
9
  vi.mock("@frak-labs/frame-connector", () => ({
10
- compressJson: vi.fn((data: unknown) => {
10
+ jsonEncode: vi.fn((data: unknown) => {
11
11
  // Simple mock: convert JSON to Uint8Array
12
12
  const jsonString = JSON.stringify(data);
13
13
  return new TextEncoder().encode(jsonString);
@@ -1,4 +1,4 @@
1
- import { compressJson } from "@frak-labs/frame-connector";
1
+ import { jsonEncode } from "@frak-labs/frame-connector";
2
2
  import { base64urlEncode } from "./b64";
3
3
 
4
4
  /**
@@ -7,5 +7,5 @@ import { base64urlEncode } from "./b64";
7
7
  * @ignore
8
8
  */
9
9
  export function compressJsonToB64(data: unknown): string {
10
- return base64urlEncode(compressJson(data));
10
+ return base64urlEncode(jsonEncode(data));
11
11
  }
@@ -7,15 +7,19 @@ import { vi } from "vitest";
7
7
 
8
8
  // Mock the frame-connector module - must be before imports
9
9
  vi.mock("@frak-labs/frame-connector", () => ({
10
- compressJson: vi.fn((data: unknown) => {
10
+ jsonEncode: vi.fn((data: unknown) => {
11
11
  // Simple mock: convert JSON to Uint8Array
12
12
  const jsonString = JSON.stringify(data);
13
13
  return new TextEncoder().encode(jsonString);
14
14
  }),
15
- decompressJson: vi.fn((data: Uint8Array) => {
15
+ jsonDecode: vi.fn((data: Uint8Array) => {
16
16
  // Simple mock: convert Uint8Array back to JSON
17
- const jsonString = new TextDecoder().decode(data);
18
- return JSON.parse(jsonString);
17
+ try {
18
+ const jsonString = new TextDecoder().decode(data);
19
+ return JSON.parse(jsonString);
20
+ } catch {
21
+ return null;
22
+ }
19
23
  }),
20
24
  }));
21
25
 
@@ -1,4 +1,4 @@
1
- import { decompressJson } from "@frak-labs/frame-connector";
1
+ import { jsonDecode } from "@frak-labs/frame-connector";
2
2
  import { base64urlDecode } from "./b64";
3
3
 
4
4
  /**
@@ -7,5 +7,5 @@ import { base64urlDecode } from "./b64";
7
7
  * @ignore
8
8
  */
9
9
  export function decompressJsonFromB64<T>(data: string): T | null {
10
- return decompressJson(base64urlDecode(data));
10
+ return jsonDecode<T>(base64urlDecode(data));
11
11
  }
@@ -1,10 +1,10 @@
1
1
  import { keccak256, toHex } from "viem";
2
2
 
3
3
  /**
4
- * Compute the product id from a domain
4
+ * Compute the legacy product id from a domain
5
5
  * @ignore
6
6
  */
7
- export function computeProductId({ domain }: { domain?: string } = {}) {
7
+ export function computeLegacyProductId({ domain }: { domain?: string } = {}) {
8
8
  const effectiveDomain = domain ?? window.location.host;
9
9
  const normalizedDomain = effectiveDomain.replace("www.", "");
10
10
  return keccak256(toHex(normalizedDomain));
@@ -2,3 +2,8 @@
2
2
  * The backup key for client side backup if needed
3
3
  */
4
4
  export const BACKUP_KEY = "nexus-wallet-backup";
5
+
6
+ /**
7
+ * Deep link scheme for Frak Wallet mobile app
8
+ */
9
+ export const DEEP_LINK_SCHEME = "frakwallet://";