@frak-labs/core-sdk 0.1.0-beta.6e0d8026 → 0.1.0-beta.8d103039

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 (72) hide show
  1. package/package.json +3 -2
  2. package/src/actions/displayEmbeddedWallet.ts +20 -0
  3. package/src/actions/displayModal.ts +131 -0
  4. package/src/actions/getProductInformation.ts +14 -0
  5. package/src/actions/index.ts +29 -0
  6. package/src/actions/openSso.ts +116 -0
  7. package/src/actions/prepareSso.ts +48 -0
  8. package/src/actions/referral/processReferral.ts +230 -0
  9. package/src/actions/referral/referralInteraction.ts +57 -0
  10. package/src/actions/sendInteraction.ts +32 -0
  11. package/src/actions/trackPurchaseStatus.ts +53 -0
  12. package/src/actions/watchWalletStatus.ts +94 -0
  13. package/src/actions/wrapper/modalBuilder.ts +212 -0
  14. package/src/actions/wrapper/sendTransaction.ts +62 -0
  15. package/src/actions/wrapper/siweAuthenticate.ts +94 -0
  16. package/src/bundle.ts +3 -0
  17. package/src/clients/DebugInfo.ts +182 -0
  18. package/src/clients/createIFrameFrakClient.ts +287 -0
  19. package/src/clients/index.ts +3 -0
  20. package/src/clients/setupClient.ts +71 -0
  21. package/src/clients/transports/iframeLifecycleManager.ts +88 -0
  22. package/src/constants/interactionTypes.ts +44 -0
  23. package/src/constants/locales.ts +14 -0
  24. package/src/constants/productTypes.ts +33 -0
  25. package/src/index.ts +103 -0
  26. package/src/interactions/index.ts +5 -0
  27. package/src/interactions/pressEncoder.ts +53 -0
  28. package/src/interactions/purchaseEncoder.ts +94 -0
  29. package/src/interactions/referralEncoder.ts +47 -0
  30. package/src/interactions/retailEncoder.ts +37 -0
  31. package/src/interactions/webshopEncoder.ts +30 -0
  32. package/src/types/client.ts +14 -0
  33. package/src/types/compression.ts +22 -0
  34. package/src/types/config.ts +111 -0
  35. package/src/types/context.ts +13 -0
  36. package/src/types/index.ts +70 -0
  37. package/src/types/lifecycle/client.ts +46 -0
  38. package/src/types/lifecycle/iframe.ts +35 -0
  39. package/src/types/lifecycle/index.ts +2 -0
  40. package/src/types/rpc/displayModal.ts +84 -0
  41. package/src/types/rpc/embedded/index.ts +68 -0
  42. package/src/types/rpc/embedded/loggedIn.ts +55 -0
  43. package/src/types/rpc/embedded/loggedOut.ts +28 -0
  44. package/src/types/rpc/interaction.ts +43 -0
  45. package/src/types/rpc/modal/final.ts +46 -0
  46. package/src/types/rpc/modal/generic.ts +46 -0
  47. package/src/types/rpc/modal/index.ts +20 -0
  48. package/src/types/rpc/modal/login.ts +32 -0
  49. package/src/types/rpc/modal/openSession.ts +25 -0
  50. package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
  51. package/src/types/rpc/modal/transaction.ts +33 -0
  52. package/src/types/rpc/productInformation.ts +59 -0
  53. package/src/types/rpc/sso.ts +80 -0
  54. package/src/types/rpc/walletStatus.ts +35 -0
  55. package/src/types/rpc.ts +158 -0
  56. package/src/types/transport.ts +34 -0
  57. package/src/utils/FrakContext.ts +152 -0
  58. package/src/utils/compression/b64.ts +29 -0
  59. package/src/utils/compression/compress.ts +11 -0
  60. package/src/utils/compression/decompress.ts +11 -0
  61. package/src/utils/compression/index.ts +3 -0
  62. package/src/utils/computeProductId.ts +11 -0
  63. package/src/utils/constants.ts +4 -0
  64. package/src/utils/formatAmount.ts +18 -0
  65. package/src/utils/getCurrencyAmountKey.ts +15 -0
  66. package/src/utils/getSupportedCurrency.ts +14 -0
  67. package/src/utils/getSupportedLocale.ts +16 -0
  68. package/src/utils/iframeHelper.ts +142 -0
  69. package/src/utils/index.ts +21 -0
  70. package/src/utils/sso.ts +119 -0
  71. package/src/utils/ssoUrlListener.ts +60 -0
  72. package/src/utils/trackEvent.ts +26 -0
@@ -0,0 +1,142 @@
1
+ import type { FrakWalletSdkConfig } from "../types";
2
+
3
+ /**
4
+ * Base props for the iframe
5
+ * @ignore
6
+ */
7
+ export const baseIframeProps = {
8
+ id: "frak-wallet",
9
+ name: "frak-wallet",
10
+ title: "Frak Wallet",
11
+ allow: "publickey-credentials-get *; clipboard-write; web-share *",
12
+ style: {
13
+ width: "0",
14
+ height: "0",
15
+ border: "0",
16
+ position: "absolute",
17
+ zIndex: 2000001,
18
+ top: "-1000px",
19
+ left: "-1000px",
20
+ colorScheme: "auto",
21
+ },
22
+ };
23
+
24
+ /**
25
+ * Create the Frak iframe
26
+ * @param args
27
+ * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions.
28
+ * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`.
29
+ */
30
+ export function createIframe({
31
+ walletBaseUrl,
32
+ config,
33
+ }: { walletBaseUrl?: string; config?: FrakWalletSdkConfig }): Promise<
34
+ HTMLIFrameElement | undefined
35
+ > {
36
+ // Check if the iframe is already created
37
+ const alreadyCreatedIFrame = document.querySelector("#frak-wallet");
38
+
39
+ // If the iframe is already created, remove it
40
+ if (alreadyCreatedIFrame) {
41
+ alreadyCreatedIFrame.remove();
42
+ }
43
+
44
+ const iframe = document.createElement("iframe");
45
+
46
+ // Set the base iframe props
47
+ iframe.id = baseIframeProps.id;
48
+ iframe.name = baseIframeProps.name;
49
+ iframe.allow = baseIframeProps.allow;
50
+ iframe.style.zIndex = baseIframeProps.style.zIndex.toString();
51
+
52
+ changeIframeVisibility({ iframe, isVisible: false });
53
+ document.body.appendChild(iframe);
54
+
55
+ return new Promise((resolve) => {
56
+ iframe?.addEventListener("load", () => resolve(iframe));
57
+ iframe.src = `${config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id"}/listener`;
58
+ });
59
+ }
60
+ /**
61
+ * Change the visibility of the given iframe
62
+ * @ignore
63
+ */
64
+ export function changeIframeVisibility({
65
+ iframe,
66
+ isVisible,
67
+ }: {
68
+ iframe: HTMLIFrameElement;
69
+ isVisible: boolean;
70
+ }) {
71
+ if (!isVisible) {
72
+ iframe.style.width = "0";
73
+ iframe.style.height = "0";
74
+ iframe.style.border = "0";
75
+ iframe.style.position = "fixed";
76
+ iframe.style.top = "-1000px";
77
+ iframe.style.left = "-1000px";
78
+ return;
79
+ }
80
+
81
+ iframe.style.position = "fixed";
82
+ iframe.style.top = "0";
83
+ iframe.style.left = "0";
84
+ iframe.style.width = "100%";
85
+ iframe.style.height = "100%";
86
+ iframe.style.pointerEvents = "auto";
87
+ }
88
+
89
+ /**
90
+ * Find an iframe within window.opener by pathname
91
+ *
92
+ * When a popup is opened via window.open from an iframe, window.opener points to
93
+ * the parent window, not the iframe itself. This utility searches through all frames
94
+ * in window.opener to find an iframe matching the specified pathname.
95
+ *
96
+ * @param pathname - The pathname to search for (default: "/listener")
97
+ * @returns The matching iframe window, or null if not found
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * // Find the default /listener iframe
102
+ * const listenerIframe = findIframeInOpener();
103
+ *
104
+ * // Find a custom iframe
105
+ * const customIframe = findIframeInOpener("/my-custom-iframe");
106
+ * ```
107
+ */
108
+ export function findIframeInOpener(pathname = "/listener"): Window | null {
109
+ if (!window.opener) return null;
110
+
111
+ const frameCheck = (frame: Window) => {
112
+ try {
113
+ return (
114
+ frame.location.origin === window.location.origin &&
115
+ frame.location.pathname === pathname
116
+ );
117
+ } catch {
118
+ // Cross-origin frame, skip
119
+ return false;
120
+ }
121
+ };
122
+
123
+ // Check if the openner window is the right one
124
+ if (frameCheck(window.opener)) return window.opener;
125
+
126
+ // Search through frames in window.opener
127
+ try {
128
+ const frames = window.opener.frames;
129
+ for (let i = 0; i < frames.length; i++) {
130
+ if (frameCheck(frames[i])) {
131
+ return frames[i];
132
+ }
133
+ }
134
+ return null;
135
+ } catch (error) {
136
+ console.error(
137
+ `[findIframeInOpener] Error finding iframe with pathname ${pathname}:`,
138
+ error
139
+ );
140
+ return null;
141
+ }
142
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ createIframe,
3
+ baseIframeProps,
4
+ findIframeInOpener,
5
+ } from "./iframeHelper";
6
+ export { compressJsonToB64 } from "./compression/compress";
7
+ export { decompressJsonFromB64 } from "./compression/decompress";
8
+ export { base64urlDecode, base64urlEncode } from "./compression/b64";
9
+ export { FrakContextManager } from "./FrakContext";
10
+ export { getSupportedCurrency } from "./getSupportedCurrency";
11
+ export { getSupportedLocale } from "./getSupportedLocale";
12
+ export { getCurrencyAmountKey } from "./getCurrencyAmountKey";
13
+ export { formatAmount } from "./formatAmount";
14
+ export { trackEvent } from "./trackEvent";
15
+ export { Deferred } from "@frak-labs/frame-connector";
16
+ export {
17
+ generateSsoUrl,
18
+ type CompressedSsoData,
19
+ type FullSsoParams,
20
+ type AppSpecificSsoMetadata,
21
+ } from "./sso";
@@ -0,0 +1,119 @@
1
+ import type { Hex } from "viem";
2
+ import type { PrepareSsoParamsType, SsoMetadata } from "../types";
3
+ import { compressJsonToB64 } from "./compression/compress";
4
+
5
+ export type AppSpecificSsoMetadata = SsoMetadata & {
6
+ name: string;
7
+ css?: string;
8
+ };
9
+
10
+ /**
11
+ * The full SSO params that will be used for compression
12
+ */
13
+ export type FullSsoParams = Omit<PrepareSsoParamsType, "metadata"> & {
14
+ metadata: AppSpecificSsoMetadata;
15
+ productId: Hex;
16
+ };
17
+
18
+ /**
19
+ * Generate SSO URL with compressed parameters
20
+ * This mirrors the wallet's getOpenSsoLink() function
21
+ *
22
+ * @param walletUrl - Base wallet URL (e.g., "https://wallet.frak.id")
23
+ * @param params - SSO parameters
24
+ * @param productId - Product identifier
25
+ * @param name - Application name
26
+ * @param css - Optional custom CSS
27
+ * @returns Complete SSO URL ready to open in popup or redirect
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const ssoUrl = generateSsoUrl(
32
+ * "https://wallet.frak.id",
33
+ * { metadata: { logoUrl: "..." }, directExit: true },
34
+ * "0x123...",
35
+ * "My App"
36
+ * );
37
+ * // Returns: https://wallet.frak.id/sso?p=<compressed_base64>
38
+ * ```
39
+ */
40
+ export function generateSsoUrl(
41
+ walletUrl: string,
42
+ params: PrepareSsoParamsType,
43
+ productId: Hex,
44
+ name: string,
45
+ css?: string
46
+ ): string {
47
+ // Build full params with app-specific metadata
48
+ const fullParams: FullSsoParams = {
49
+ redirectUrl: params.redirectUrl,
50
+ directExit: params.directExit,
51
+ lang: params.lang,
52
+ productId,
53
+ metadata: {
54
+ name,
55
+ css,
56
+ logoUrl: params.metadata?.logoUrl,
57
+ homepageLink: params.metadata?.homepageLink,
58
+ },
59
+ };
60
+
61
+ // Compress params to minimal format
62
+ const compressedParam = ssoParamsToCompressed(fullParams);
63
+
64
+ // Encode to base64url
65
+ const compressedString = compressJsonToB64(compressedParam);
66
+
67
+ // Build URL matching wallet's expected format: /sso?p=<compressed>
68
+ const ssoUrl = new URL(walletUrl);
69
+ ssoUrl.pathname = "/sso";
70
+ ssoUrl.searchParams.set("p", compressedString);
71
+
72
+ return ssoUrl.toString();
73
+ }
74
+
75
+ /**
76
+ * Map full sso params to compressed sso params
77
+ * @param params
78
+ */
79
+ function ssoParamsToCompressed(params: FullSsoParams) {
80
+ return {
81
+ r: params.redirectUrl,
82
+ d: params.directExit,
83
+ l: params.lang,
84
+ p: params.productId,
85
+ m: {
86
+ n: params.metadata?.name,
87
+ css: params.metadata?.css,
88
+ l: params.metadata?.logoUrl,
89
+ h: params.metadata?.homepageLink,
90
+ },
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Type of compressed the sso data
96
+ */
97
+ export type CompressedSsoData = {
98
+ // Potential id from backend
99
+ id?: Hex;
100
+ // redirect url
101
+ r?: string;
102
+ // direct exit
103
+ d?: boolean;
104
+ // language
105
+ l?: "en" | "fr";
106
+ // product id
107
+ p: Hex;
108
+ // metadata
109
+ m: {
110
+ // product name
111
+ n: string;
112
+ // custom css
113
+ css?: string;
114
+ // logo
115
+ l?: string;
116
+ // home page link
117
+ h?: string;
118
+ };
119
+ };
@@ -0,0 +1,60 @@
1
+ import type { RpcClient } from "@frak-labs/frame-connector";
2
+ import type { FrakLifecycleEvent } from "../types";
3
+ import type { IFrameRpcSchema } from "../types/rpc";
4
+
5
+ /**
6
+ * Listen for SSO redirect with compressed data in URL
7
+ * Forwards compressed data to iframe via lifecycle event
8
+ * Cleans URL immediately after detection
9
+ *
10
+ * Performance: One-shot URL check, no polling, no re-renders
11
+ *
12
+ * @param rpcClient - RPC client instance to send lifecycle events
13
+ * @param waitForConnection - Promise that resolves when iframe is connected
14
+ */
15
+ export function setupSsoUrlListener(
16
+ rpcClient: RpcClient<IFrameRpcSchema, FrakLifecycleEvent>,
17
+ waitForConnection: Promise<boolean>
18
+ ): void {
19
+ if (typeof window === "undefined") {
20
+ return;
21
+ }
22
+
23
+ // One-shot URL check - no need for MutationObserver or polling
24
+ const url = new URL(window.location.href);
25
+ const compressedSso = url.searchParams.get("sso");
26
+
27
+ // Early return if no SSO parameter
28
+ if (!compressedSso) {
29
+ return;
30
+ }
31
+
32
+ // Forward compressed data directly to iframe (no decompression on SDK side)
33
+ // Iframe will decompress and process
34
+ waitForConnection
35
+ .then(() => {
36
+ // Send lifecycle event with compressed string
37
+ // This is a one-way notification, no response expected
38
+ rpcClient.sendLifecycle({
39
+ clientLifecycle: "sso-redirect-complete",
40
+ data: { compressed: compressedSso },
41
+ });
42
+
43
+ console.log(
44
+ "[SSO URL Listener] Forwarded compressed SSO data to iframe"
45
+ );
46
+ })
47
+ .catch((error) => {
48
+ console.error(
49
+ "[SSO URL Listener] Failed to forward SSO data:",
50
+ error
51
+ );
52
+ });
53
+
54
+ // Clean URL immediately to prevent exposure in browser history
55
+ // Use replaceState to avoid navigation/re-render
56
+ url.searchParams.delete("sso");
57
+ window.history.replaceState({}, "", url.toString());
58
+
59
+ console.log("[SSO URL Listener] SSO parameter detected and URL cleaned");
60
+ }
@@ -0,0 +1,26 @@
1
+ import type { FrakClient } from "../types";
2
+
3
+ export type FrakEvent =
4
+ | "share_button_clicked"
5
+ | "wallet_button_clicked"
6
+ | "share_modal_error"
7
+ | "user_referred";
8
+
9
+ type EventProps = Record<string, unknown>;
10
+
11
+ export function trackEvent(
12
+ client: FrakClient | undefined,
13
+ event: FrakEvent,
14
+ props: EventProps = {}
15
+ ): void {
16
+ if (!client) {
17
+ console.debug("[Frak] No client provided, skipping event tracking");
18
+ return;
19
+ }
20
+
21
+ try {
22
+ client.openPanel?.track(event, props);
23
+ } catch (e) {
24
+ console.debug("[Frak] Failed to track event:", event, e);
25
+ }
26
+ }