@frak-labs/core-sdk 0.1.1 → 0.2.0-beta.7898df5b

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 (130) 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/computeLegacyProductId-CCAZvLa5.d.cts +537 -0
  12. package/dist/computeLegacyProductId-b5cUWdAm.d.ts +537 -0
  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-B0g7-807.d.cts} +173 -136
  18. package/dist/{openSso-DsKJ4y0j.d.ts → openSso-CMzwvaCa.d.ts} +173 -136
  19. package/dist/setupClient-BICl5fdX.js +13 -0
  20. package/dist/setupClient-nl8Dhh4V.cjs +13 -0
  21. package/dist/siweAuthenticate-BWmI2_TN.cjs +1 -0
  22. package/dist/{index-d8xS4ryI.d.ts → siweAuthenticate-CVigMOxz.d.cts} +113 -92
  23. package/dist/{index-C6FxkWPC.d.cts → siweAuthenticate-CnCZ7mok.d.ts} +113 -92
  24. package/dist/siweAuthenticate-zczqxm0a.js +1 -0
  25. package/dist/trackEvent-CeLFVzZn.js +1 -0
  26. package/dist/trackEvent-Ew5r5zfI.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 +117 -242
  37. package/src/actions/referral/processReferral.ts +134 -204
  38. package/src/actions/referral/referralInteraction.test.ts +4 -12
  39. package/src/actions/referral/referralInteraction.ts +3 -13
  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 +27 -16
  52. package/src/types/config.ts +6 -0
  53. package/src/types/context.ts +48 -6
  54. package/src/types/index.ts +15 -11
  55. package/src/types/lifecycle/client.ts +24 -1
  56. package/src/types/lifecycle/iframe.ts +6 -0
  57. package/src/types/rpc/displayModal.ts +2 -4
  58. package/src/types/rpc/embedded/index.ts +2 -2
  59. package/src/types/rpc/interaction.ts +31 -39
  60. package/src/types/rpc/merchantInformation.ts +77 -0
  61. package/src/types/rpc/modal/index.ts +0 -4
  62. package/src/types/rpc/modal/login.ts +5 -1
  63. package/src/types/rpc/walletStatus.ts +1 -7
  64. package/src/types/rpc.ts +22 -30
  65. package/src/types/tracking.ts +31 -0
  66. package/src/utils/FrakContext.test.ts +270 -186
  67. package/src/utils/FrakContext.ts +78 -56
  68. package/src/utils/backendUrl.test.ts +83 -0
  69. package/src/utils/backendUrl.ts +62 -0
  70. package/src/utils/clientId.test.ts +41 -0
  71. package/src/utils/clientId.ts +43 -0
  72. package/src/utils/compression/compress.test.ts +1 -1
  73. package/src/utils/compression/compress.ts +2 -2
  74. package/src/utils/compression/decompress.test.ts +8 -4
  75. package/src/utils/compression/decompress.ts +2 -2
  76. package/src/utils/{computeProductId.ts → computeLegacyProductId.ts} +2 -2
  77. package/src/utils/constants.ts +5 -0
  78. package/src/utils/deepLinkWithFallback.test.ts +243 -0
  79. package/src/utils/deepLinkWithFallback.ts +103 -0
  80. package/src/utils/formatAmount.ts +6 -0
  81. package/src/utils/iframeHelper.test.ts +18 -5
  82. package/src/utils/iframeHelper.ts +10 -3
  83. package/src/utils/index.ts +16 -1
  84. package/src/utils/merchantId.test.ts +653 -0
  85. package/src/utils/merchantId.ts +143 -0
  86. package/src/utils/sso.ts +18 -11
  87. package/src/utils/trackEvent.test.ts +23 -5
  88. package/src/utils/trackEvent.ts +13 -0
  89. package/cdn/bundle.iife.js +0 -14
  90. package/dist/actions-B5j-i1p0.cjs +0 -1
  91. package/dist/actions-q090Z0oR.js +0 -1
  92. package/dist/index-7OZ39x1U.d.ts +0 -195
  93. package/dist/index-CRsQWnTs.d.cts +0 -351
  94. package/dist/index-Ck1hudEi.d.ts +0 -351
  95. package/dist/index-zDq-VlKx.d.cts +0 -195
  96. package/dist/interaction-DMJ3ZfaF.d.cts +0 -45
  97. package/dist/interaction-KX1h9a7V.d.ts +0 -45
  98. package/dist/interactions-DnfM3oe0.js +0 -1
  99. package/dist/interactions-EIXhNLf6.cjs +0 -1
  100. package/dist/interactions.cjs +0 -1
  101. package/dist/interactions.d.cts +0 -2
  102. package/dist/interactions.d.ts +0 -2
  103. package/dist/interactions.js +0 -1
  104. package/dist/productTypes-BUkXJKZ7.cjs +0 -1
  105. package/dist/productTypes-CGb1MmBF.js +0 -1
  106. package/dist/src-1LQ4eLq5.js +0 -13
  107. package/dist/src-hW71KjPN.cjs +0 -13
  108. package/dist/trackEvent-CHnYa85W.js +0 -1
  109. package/dist/trackEvent-GuQm_1Nm.cjs +0 -1
  110. package/src/actions/getProductInformation.ts +0 -14
  111. package/src/actions/openSso.test.ts +0 -407
  112. package/src/actions/sendInteraction.test.ts +0 -219
  113. package/src/constants/interactionTypes.test.ts +0 -128
  114. package/src/constants/productTypes.test.ts +0 -130
  115. package/src/constants/productTypes.ts +0 -33
  116. package/src/interactions/index.ts +0 -5
  117. package/src/interactions/pressEncoder.test.ts +0 -215
  118. package/src/interactions/pressEncoder.ts +0 -53
  119. package/src/interactions/purchaseEncoder.test.ts +0 -291
  120. package/src/interactions/purchaseEncoder.ts +0 -99
  121. package/src/interactions/referralEncoder.test.ts +0 -170
  122. package/src/interactions/referralEncoder.ts +0 -47
  123. package/src/interactions/retailEncoder.test.ts +0 -107
  124. package/src/interactions/retailEncoder.ts +0 -37
  125. package/src/interactions/webshopEncoder.test.ts +0 -56
  126. package/src/interactions/webshopEncoder.ts +0 -30
  127. package/src/types/rpc/modal/openSession.ts +0 -25
  128. package/src/types/rpc/productInformation.ts +0 -59
  129. package/src/utils/computeProductId.test.ts +0 -80
  130. package/src/utils/sso.test.ts +0 -361
@@ -1,20 +1,39 @@
1
- import { type Address, bytesToHex, hexToBytes } from "viem";
2
- import type { FrakContext } from "../types";
1
+ import { type Address, bytesToHex, hexToBytes, isAddress } from "viem";
2
+ import type { FrakContext, FrakContextV1, FrakContextV2 } from "../types";
3
+ import { isV2Context } from "../types";
3
4
  import { base64urlDecode, base64urlEncode } from "./compression/b64";
5
+ import { compressJsonToB64 } from "./compression/compress";
6
+ import { decompressJsonFromB64 } from "./compression/decompress";
4
7
 
5
8
  /**
6
- * The context key
9
+ * URL parameter key for the Frak referral context
7
10
  */
8
11
  const contextKey = "fCtx";
9
12
 
10
13
  /**
11
- * Compress the current Frak context
12
- * @param context - The context to be compressed
13
- * @returns A compressed string containing the Frak context
14
+ * Compress a Frak context into a URL-safe string.
15
+ *
16
+ * - V2 contexts are serialized as compressed JSON (base64url).
17
+ * - V1 contexts encode the wallet address as raw bytes (base64url).
18
+ *
19
+ * @param context - The context to compress (V1 or V2)
20
+ * @returns A compressed base64url string, or undefined on failure
14
21
  */
15
- function compress(context?: Partial<FrakContext>): string | undefined {
16
- if (!context?.r) return;
22
+ function compress(context?: FrakContextV1 | FrakContextV2): string | undefined {
23
+ if (!context) return;
17
24
  try {
25
+ if (isV2Context(context)) {
26
+ // Runtime validation: all V2 fields must be present and truthy
27
+ if (!context.c || !context.m || !context.t) return undefined;
28
+ return compressJsonToB64({
29
+ v: 2,
30
+ c: context.c,
31
+ m: context.m,
32
+ t: context.t,
33
+ });
34
+ }
35
+
36
+ // V1 legacy: compress wallet address as raw bytes
18
37
  const bytes = hexToBytes(context.r);
19
38
  return base64urlEncode(bytes);
20
39
  } catch (e) {
@@ -24,15 +43,31 @@ function compress(context?: Partial<FrakContext>): string | undefined {
24
43
  }
25
44
 
26
45
  /**
27
- * Decompress the given Frak context
28
- * @param context - The raw context to be decompressed into a `FrakContext`
29
- * @returns The decompressed Frak context, or undefined if it fails
46
+ * Decompress a base64url string back into a Frak context.
47
+ *
48
+ * Attempts V2 JSON decompression first, then falls back to V1 raw bytes.
49
+ *
50
+ * @param context - The compressed context string
51
+ * @returns The decompressed FrakContext, or undefined on failure
30
52
  */
31
53
  function decompress(context?: string): FrakContext | undefined {
32
54
  if (!context || context.length === 0) return;
33
55
  try {
56
+ // Try V2 JSON first — V2 payloads are longer than V1's 20-byte address
57
+ const json = decompressJsonFromB64<FrakContextV2>(context);
58
+ if (json && typeof json === "object" && json.v === 2) {
59
+ if (json.c && json.m && json.t) {
60
+ return { v: 2, c: json.c, m: json.m, t: json.t };
61
+ }
62
+ return undefined;
63
+ }
64
+
65
+ // Fall back to V1: raw 20-byte address
34
66
  const bytes = base64urlDecode(context);
35
- return { r: bytesToHex(bytes, { size: 20 }) as Address };
67
+ const hex = bytesToHex(bytes, { size: 20 }) as Address;
68
+ if (isAddress(hex)) {
69
+ return { r: hex };
70
+ }
36
71
  } catch (e) {
37
72
  console.error("Error decompressing Frak context", { e, context });
38
73
  }
@@ -40,113 +75,100 @@ function decompress(context?: string): FrakContext | undefined {
40
75
  }
41
76
 
42
77
  /**
43
- * Parse the current URL into a Frak Context
78
+ * Parse a URL to extract the Frak referral context from the `fCtx` query parameter.
79
+ *
44
80
  * @param args
45
- * @param args.url - The url to parse
46
- * @returns The parsed Frak context
81
+ * @param args.url - The URL to parse
82
+ * @returns The parsed FrakContext, or null if absent
47
83
  */
48
- function parse({ url }: { url: string }) {
84
+ function parse({ url }: { url: string }): FrakContext | null | undefined {
49
85
  if (!url) return null;
50
86
 
51
- // Check if the url contain the frak context key
52
87
  const urlObj = new URL(url);
53
88
  const frakContext = urlObj.searchParams.get(contextKey);
54
89
  if (!frakContext) return null;
55
90
 
56
- // Decompress and return it
57
91
  return decompress(frakContext);
58
92
  }
59
93
 
60
94
  /**
61
- * Populate the current url with the given Frak context
95
+ * Add or replace the `fCtx` query parameter in a URL with the given context.
96
+ *
62
97
  * @param args
63
- * @param args.url - The url to update
64
- * @param args.context - The context to update
65
- * @returns The new url with the Frak context
98
+ * @param args.url - The URL to update
99
+ * @param args.context - The context to embed (V1 or V2)
100
+ * @returns The updated URL string, or null on failure
66
101
  */
67
102
  function update({
68
103
  url,
69
104
  context,
70
105
  }: {
71
106
  url?: string;
72
- context: Partial<FrakContext>;
73
- }) {
107
+ context: FrakContextV1 | FrakContextV2;
108
+ }): string | null {
74
109
  if (!url) return null;
75
110
 
76
- // Parse the current context
77
- const currentContext = parse({ url });
78
-
79
- // Merge the current context with the new context
80
- const mergedContext = currentContext
81
- ? { ...currentContext, ...context }
82
- : context;
83
-
84
- // If we don't have a referrer, early exit
85
- if (!mergedContext.r) return null;
86
-
87
- // Compress it
88
- const compressedContext = compress(mergedContext);
111
+ const compressedContext = compress(context);
89
112
  if (!compressedContext) return null;
90
113
 
91
- // Build the new url and return it
92
114
  const urlObj = new URL(url);
93
115
  urlObj.searchParams.set(contextKey, compressedContext);
94
116
  return urlObj.toString();
95
117
  }
96
118
 
97
119
  /**
98
- * Remove Frak context from current url
99
- * @param url - The url to update
100
- * @returns The new url without the Frak context
120
+ * Remove the `fCtx` query parameter from a URL.
121
+ *
122
+ * @param url - The URL to strip the context from
123
+ * @returns The cleaned URL string
101
124
  */
102
- function remove(url: string) {
125
+ function remove(url: string): string {
103
126
  const urlObj = new URL(url);
104
127
  urlObj.searchParams.delete(contextKey);
105
128
  return urlObj.toString();
106
129
  }
107
130
 
108
131
  /**
109
- * Replace the current url with the given Frak context
132
+ * Replace the current browser URL with an updated Frak context.
133
+ *
134
+ * - If `context` is non-null, embeds it via {@link update}.
135
+ * - If `context` is null, strips the context via {@link remove}.
136
+ *
110
137
  * @param args
111
- * @param args.url - The url to update
112
- * @param args.context - The context to update
138
+ * @param args.url - Base URL (defaults to `window.location.href`)
139
+ * @param args.context - Context to set, or null to remove
113
140
  */
114
141
  function replaceUrl({
115
142
  url: baseUrl,
116
143
  context,
117
144
  }: {
118
145
  url?: string;
119
- context: Partial<FrakContext> | null;
146
+ context: FrakContextV1 | FrakContextV2 | null;
120
147
  }) {
121
- // If no window here early exit
122
148
  if (!window.location?.href || typeof window === "undefined") {
123
149
  console.error("No window found, can't update context");
124
150
  return;
125
151
  }
126
152
 
127
- // If no url, try to use the current one
128
153
  const url = baseUrl ?? window.location.href;
129
154
 
130
- // Get our new url with the frak context
131
155
  let newUrl: string | null;
132
156
  if (context !== null) {
133
- newUrl = update({
134
- url,
135
- context,
136
- });
157
+ newUrl = update({ url, context });
137
158
  } else {
138
159
  newUrl = remove(url);
139
160
  }
140
161
 
141
- // If no new url, early exit
142
162
  if (!newUrl) return;
143
163
 
144
- // Update the url
145
164
  window.history.replaceState(null, "", newUrl.toString());
146
165
  }
147
166
 
148
167
  /**
149
- * Export our frak context
168
+ * Manager for Frak referral context in URLs.
169
+ *
170
+ * Handles compression, decompression, URL parsing, and browser history updates
171
+ * for both V1 (wallet address) and V2 (anonymous clientId) referral contexts.
150
172
  */
151
173
  export const FrakContextManager = {
152
174
  compress,
@@ -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://";