@frak-labs/core-sdk 0.2.0 → 0.2.1-beta.06c52c98
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.
- package/README.md +1 -2
- package/cdn/bundle.js +55 -3
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +2 -2
- package/dist/actions.d.ts +2 -2
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +4 -4
- package/dist/bundle.d.ts +4 -4
- package/dist/bundle.js +1 -1
- package/dist/{computeLegacyProductId-Raks6FXg.d.cts → computeLegacyProductId-BP-ciVsp.d.cts} +73 -88
- package/dist/{computeLegacyProductId-BkyJ4rEY.d.ts → computeLegacyProductId-DiJd7RNo.d.ts} +73 -88
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{openSso-BCJGchIb.d.cts → openSso-B8v3Vtnh.d.ts} +157 -52
- package/dist/{openSso-DG-_9CED.d.ts → openSso-n_B4LSuW.d.cts} +157 -52
- package/dist/setupClient-Dr_UYfTD.cjs +13 -0
- package/dist/setupClient-TuhDjVJx.js +13 -0
- package/dist/siweAuthenticate-0UPcUqI1.js +1 -0
- package/dist/{siweAuthenticate-Btem4QHs.d.ts → siweAuthenticate-CDCsp8EJ.d.ts} +35 -36
- package/dist/siweAuthenticate-CfQibjZR.cjs +1 -0
- package/dist/{siweAuthenticate-BH7Dn7nZ.d.cts → siweAuthenticate-yITE-iKh.d.cts} +35 -36
- package/dist/trackEvent-5j5kkOCj.js +1 -0
- package/dist/trackEvent-B2uom25e.cjs +1 -0
- package/package.json +8 -8
- package/src/actions/displayEmbeddedWallet.ts +6 -2
- package/src/actions/displayModal.ts +6 -2
- package/src/actions/ensureIdentity.ts +2 -2
- package/src/actions/referral/processReferral.test.ts +109 -125
- package/src/actions/referral/processReferral.ts +134 -180
- package/src/actions/referral/referralInteraction.test.ts +3 -5
- package/src/actions/referral/referralInteraction.ts +2 -7
- package/src/actions/trackPurchaseStatus.test.ts +32 -20
- package/src/actions/trackPurchaseStatus.ts +3 -5
- package/src/actions/wrapper/modalBuilder.test.ts +4 -2
- package/src/actions/wrapper/modalBuilder.ts +6 -8
- package/src/clients/createIFrameFrakClient.ts +146 -25
- package/src/clients/transports/iframeLifecycleManager.test.ts +0 -80
- package/src/clients/transports/iframeLifecycleManager.ts +0 -44
- package/src/index.ts +8 -3
- package/src/types/config.ts +10 -3
- package/src/types/context.ts +48 -6
- package/src/types/index.ts +8 -2
- package/src/types/lifecycle/client.ts +22 -27
- package/src/types/lifecycle/iframe.ts +0 -8
- package/src/types/resolvedConfig.ts +104 -0
- package/src/types/rpc/interaction.ts +9 -0
- package/src/types/rpc.ts +7 -5
- package/src/types/tracking.ts +5 -34
- package/src/utils/FrakContext.test.ts +270 -186
- package/src/utils/FrakContext.ts +78 -56
- package/src/utils/backendUrl.test.ts +2 -2
- package/src/utils/backendUrl.ts +1 -1
- package/src/utils/index.ts +1 -5
- package/src/utils/sdkConfigStore.test.ts +405 -0
- package/src/utils/sdkConfigStore.ts +277 -0
- package/src/utils/sso.ts +3 -7
- package/dist/setupClient-CQrMDGyZ.js +0 -13
- package/dist/setupClient-Ccv3XxwL.cjs +0 -13
- package/dist/siweAuthenticate-BJHbtty4.js +0 -1
- package/dist/siweAuthenticate-Cwj3HP0m.cjs +0 -1
- package/dist/trackEvent-M2RLTQ2p.js +0 -1
- package/dist/trackEvent-T_R9ER2S.cjs +0 -1
- package/src/utils/merchantId.test.ts +0 -653
- package/src/utils/merchantId.ts +0 -143
|
@@ -1,232 +1,186 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type Address, isAddressEqual } from "viem";
|
|
1
|
+
import { isAddressEqual } from "viem";
|
|
3
2
|
import type {
|
|
4
|
-
DisplayEmbeddedWalletParamsType,
|
|
5
3
|
FrakClient,
|
|
6
4
|
FrakContext,
|
|
5
|
+
FrakContextV2,
|
|
7
6
|
WalletStatusReturnType,
|
|
8
7
|
} from "../../types";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { isV1Context, isV2Context } from "../../types";
|
|
9
|
+
import { FrakContextManager, getClientId, trackEvent } from "../../utils";
|
|
11
10
|
import { sendInteraction } from "../sendInteraction";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* Options for the referral auto-interaction process.
|
|
14
|
+
*/
|
|
15
|
+
export type ProcessReferralOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* If true, always replace the URL with the current user's referral context
|
|
18
|
+
* so the next visitor gets referred by this user.
|
|
19
|
+
* @defaultValue false
|
|
20
|
+
*/
|
|
21
|
+
alwaysAppendUrl?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Merchant ID for building the current user's referral context.
|
|
24
|
+
* Required when `alwaysAppendUrl` is true and the incoming context is V1.
|
|
25
|
+
* For V2 contexts, the merchantId is already embedded in the context.
|
|
26
|
+
*/
|
|
27
|
+
merchantId?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The different states of the referral process.
|
|
15
32
|
* @inline
|
|
16
33
|
*/
|
|
17
34
|
type ReferralState =
|
|
18
35
|
| "idle"
|
|
19
36
|
| "processing"
|
|
20
37
|
| "success"
|
|
21
|
-
| "no-wallet"
|
|
22
|
-
| "error"
|
|
23
38
|
| "no-referrer"
|
|
24
39
|
| "self-referral";
|
|
25
40
|
|
|
26
41
|
/**
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
export type ProcessReferralOptions = {
|
|
30
|
-
/**
|
|
31
|
-
* If we want to always append the url with the frak context or not
|
|
32
|
-
* @defaultValue false
|
|
33
|
-
*/
|
|
34
|
-
alwaysAppendUrl?: boolean;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* This function handle all the heavy lifting of the referral interaction process
|
|
39
|
-
* 1. Check if the user has been referred or not (if not, early exit)
|
|
40
|
-
* 2. Then check if the user is logged in or not
|
|
41
|
-
* 2.1 If not logged in, try a soft login, if it fail, display a modal for the user to login
|
|
42
|
-
* 3. Check if that's not a self-referral (if yes, early exit)
|
|
43
|
-
* 4. Track the referral event
|
|
44
|
-
* 5. Update the current url with the right data
|
|
45
|
-
* 6. Return the resulting referral state
|
|
46
|
-
*
|
|
47
|
-
* If any error occurs during the process, the function will catch it and return an error state
|
|
42
|
+
* Track an arrival event if the context version is recognized.
|
|
43
|
+
* Sends both tracking analytics and the arrival interaction RPC.
|
|
48
44
|
*
|
|
49
|
-
* @
|
|
50
|
-
* @param args
|
|
51
|
-
* @param args.walletStatus - The current user wallet status
|
|
52
|
-
* @param args.frakContext - The current frak context
|
|
53
|
-
* @param args.modalConfig - The modal configuration to display if the user is not logged in
|
|
54
|
-
* @param args.options - Some options for the referral interaction
|
|
55
|
-
* @returns A promise with the resulting referral state
|
|
56
|
-
*
|
|
57
|
-
* @see {@link displayModal} for more details about the displayed modal
|
|
58
|
-
* @see {@link @frak-labs/core-sdk!ModalStepTypes} for more details on each modal steps types
|
|
45
|
+
* @returns true if the context was valid and tracked, false otherwise
|
|
59
46
|
*/
|
|
60
|
-
|
|
47
|
+
function trackArrivalIfValid(
|
|
61
48
|
client: FrakClient,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
) {
|
|
74
|
-
// Early exit if we don't have any referral informations
|
|
75
|
-
if (!frakContext?.r) {
|
|
76
|
-
return "no-referrer";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// If we got a context, log an event
|
|
80
|
-
trackEvent(client, "user_referred_started", {
|
|
81
|
-
properties: {
|
|
82
|
-
referrer: frakContext?.r,
|
|
83
|
-
walletStatus: walletStatus?.key,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
sendInteraction(client, {
|
|
88
|
-
type: "arrival",
|
|
89
|
-
referrerWallet: frakContext.r,
|
|
90
|
-
landingUrl:
|
|
91
|
-
typeof window !== "undefined" ? window.location.href : undefined,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Helper to fetch a fresh wallet status
|
|
95
|
-
let walletRequest = false;
|
|
96
|
-
async function getFreshWalletStatus() {
|
|
97
|
-
if (walletRequest) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
walletRequest = true;
|
|
101
|
-
return ensureWalletConnected(client, {
|
|
102
|
-
modalConfig: {
|
|
103
|
-
...modalConfig,
|
|
104
|
-
loggedIn: {
|
|
105
|
-
action: {
|
|
106
|
-
key: "referred",
|
|
107
|
-
},
|
|
108
|
-
},
|
|
49
|
+
frakContext: FrakContext,
|
|
50
|
+
walletStatus?: WalletStatusReturnType
|
|
51
|
+
): boolean {
|
|
52
|
+
const landingUrl =
|
|
53
|
+
typeof window !== "undefined" ? window.location.href : undefined;
|
|
54
|
+
|
|
55
|
+
if (isV2Context(frakContext)) {
|
|
56
|
+
trackEvent(client, "user_referred_started", {
|
|
57
|
+
properties: {
|
|
58
|
+
referrerClientId: frakContext.c,
|
|
59
|
+
walletStatus: walletStatus?.key,
|
|
109
60
|
},
|
|
110
|
-
walletStatus,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
// Do the core processing logic
|
|
116
|
-
const { status, currentWallet } = await processReferralLogic({
|
|
117
|
-
initialWalletStatus: walletStatus,
|
|
118
|
-
getFreshWalletStatus,
|
|
119
|
-
// We can enforce this type cause of the condition at the start
|
|
120
|
-
frakContext: frakContext as Pick<FrakContext, "r">,
|
|
121
61
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
62
|
+
sendInteraction(client, {
|
|
63
|
+
type: "arrival",
|
|
64
|
+
referrerClientId: frakContext.c,
|
|
65
|
+
referrerMerchantId: frakContext.m,
|
|
66
|
+
referralTimestamp: frakContext.t,
|
|
67
|
+
landingUrl,
|
|
127
68
|
});
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
128
71
|
|
|
129
|
-
|
|
130
|
-
trackEvent(client, "
|
|
72
|
+
if (isV1Context(frakContext)) {
|
|
73
|
+
trackEvent(client, "user_referred_started", {
|
|
131
74
|
properties: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
wallet: currentWallet,
|
|
75
|
+
referrer: frakContext.r,
|
|
76
|
+
walletStatus: walletStatus?.key,
|
|
135
77
|
},
|
|
136
78
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Track the error event
|
|
143
|
-
trackEvent(client, "user_referred_error", {
|
|
144
|
-
properties: {
|
|
145
|
-
referrer: frakContext?.r,
|
|
146
|
-
error:
|
|
147
|
-
error instanceof FrakRpcError
|
|
148
|
-
? `[${error.code}] ${error.name} - ${error.message}`
|
|
149
|
-
: error instanceof Error
|
|
150
|
-
? error.message
|
|
151
|
-
: "undefined",
|
|
152
|
-
},
|
|
79
|
+
sendInteraction(client, {
|
|
80
|
+
type: "arrival",
|
|
81
|
+
referrerWallet: frakContext.r,
|
|
82
|
+
landingUrl,
|
|
153
83
|
});
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
154
86
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
url: window.location?.href,
|
|
158
|
-
context: options?.alwaysAppendUrl
|
|
159
|
-
? { r: walletStatus?.wallet }
|
|
160
|
-
: null,
|
|
161
|
-
});
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
162
89
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Build a V2 context representing the current user for URL replacement.
|
|
92
|
+
* @returns A V2 context, or null if clientId or merchantId is unavailable
|
|
93
|
+
*/
|
|
94
|
+
function buildCurrentUserContext(merchantId: string): FrakContextV2 | null {
|
|
95
|
+
const clientId = getClientId();
|
|
96
|
+
if (!clientId) return null;
|
|
97
|
+
return {
|
|
98
|
+
v: 2,
|
|
99
|
+
c: clientId,
|
|
100
|
+
m: merchantId,
|
|
101
|
+
t: Math.floor(Date.now() / 1000),
|
|
102
|
+
};
|
|
166
103
|
}
|
|
167
104
|
|
|
168
105
|
/**
|
|
169
|
-
*
|
|
106
|
+
* Client-side self-referral preflight check.
|
|
107
|
+
* Prevents unnecessary backend round-trips for obvious self-referrals.
|
|
170
108
|
*/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
getFreshWalletStatus: () => Promise<Address | undefined>;
|
|
178
|
-
frakContext: Pick<FrakContext, "r">;
|
|
179
|
-
}) {
|
|
180
|
-
// Get the current wallet, without auto displaying the modal
|
|
181
|
-
let currentWallet = initialWalletStatus?.wallet;
|
|
182
|
-
|
|
183
|
-
// If we don't have a current wallet, display the modal to log in
|
|
184
|
-
if (!currentWallet) {
|
|
185
|
-
currentWallet = await getFreshWalletStatus();
|
|
109
|
+
function isSelfReferral(
|
|
110
|
+
frakContext: FrakContext,
|
|
111
|
+
walletStatus?: WalletStatusReturnType
|
|
112
|
+
): boolean {
|
|
113
|
+
if (isV2Context(frakContext)) {
|
|
114
|
+
return getClientId() === frakContext.c;
|
|
186
115
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (currentWallet && isAddressEqual(frakContext.r, currentWallet)) {
|
|
190
|
-
return { status: "self-referral", currentWallet } as const;
|
|
116
|
+
if (isV1Context(frakContext) && walletStatus?.wallet) {
|
|
117
|
+
return isAddressEqual(frakContext.r, walletStatus.wallet);
|
|
191
118
|
}
|
|
192
|
-
|
|
193
|
-
return { status: "success", currentWallet } as const;
|
|
119
|
+
return false;
|
|
194
120
|
}
|
|
195
121
|
|
|
196
122
|
/**
|
|
197
|
-
*
|
|
123
|
+
* Handle the full referral interaction flow:
|
|
124
|
+
*
|
|
125
|
+
* 1. Check if the user has been referred (if not, early exit)
|
|
126
|
+
* 2. Preflight self-referral check (if yes, early exit)
|
|
127
|
+
* 3. Track the arrival event
|
|
128
|
+
* 4. Replace the current URL with the user's own referral context
|
|
129
|
+
* 5. Return the resulting referral state
|
|
130
|
+
*
|
|
131
|
+
* @param client - The current Frak Client
|
|
132
|
+
* @param args
|
|
133
|
+
* @param args.walletStatus - The current user wallet status
|
|
134
|
+
* @param args.frakContext - The referral context parsed from the URL
|
|
135
|
+
* @param args.options - Options for URL replacement and merchant context
|
|
136
|
+
* @returns The referral state
|
|
137
|
+
*
|
|
138
|
+
* @see {@link @frak-labs/core-sdk!ModalStepTypes} for modal step types
|
|
198
139
|
*/
|
|
199
|
-
|
|
140
|
+
export function processReferral(
|
|
200
141
|
client: FrakClient,
|
|
201
142
|
{
|
|
202
|
-
modalConfig,
|
|
203
143
|
walletStatus,
|
|
144
|
+
frakContext,
|
|
145
|
+
options,
|
|
204
146
|
}: {
|
|
205
|
-
modalConfig?: DisplayEmbeddedWalletParamsType;
|
|
206
147
|
walletStatus?: WalletStatusReturnType;
|
|
148
|
+
frakContext?: FrakContext | null;
|
|
149
|
+
options?: ProcessReferralOptions;
|
|
207
150
|
}
|
|
208
|
-
) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const result = await displayEmbeddedWallet(client, modalConfig ?? {});
|
|
212
|
-
return result?.wallet ?? undefined;
|
|
151
|
+
): ReferralState {
|
|
152
|
+
if (!frakContext) {
|
|
153
|
+
return "no-referrer";
|
|
213
154
|
}
|
|
214
155
|
|
|
215
|
-
|
|
216
|
-
|
|
156
|
+
if (isSelfReferral(frakContext, walletStatus)) {
|
|
157
|
+
return "self-referral";
|
|
158
|
+
}
|
|
217
159
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
* @param error
|
|
221
|
-
*/
|
|
222
|
-
function mapErrorToState(error: unknown): ReferralState {
|
|
223
|
-
if (error instanceof FrakRpcError) {
|
|
224
|
-
switch (error.code) {
|
|
225
|
-
case RpcErrorCodes.walletNotConnected:
|
|
226
|
-
return "no-wallet";
|
|
227
|
-
default:
|
|
228
|
-
return "error";
|
|
229
|
-
}
|
|
160
|
+
if (!trackArrivalIfValid(client, frakContext, walletStatus)) {
|
|
161
|
+
return "no-referrer";
|
|
230
162
|
}
|
|
231
|
-
|
|
163
|
+
|
|
164
|
+
// V2 context embeds merchantId; V1 falls back to options
|
|
165
|
+
const contextMerchantId = isV2Context(frakContext)
|
|
166
|
+
? frakContext.m
|
|
167
|
+
: options?.merchantId;
|
|
168
|
+
|
|
169
|
+
const replaceContext =
|
|
170
|
+
options?.alwaysAppendUrl && contextMerchantId
|
|
171
|
+
? buildCurrentUserContext(contextMerchantId)
|
|
172
|
+
: null;
|
|
173
|
+
|
|
174
|
+
FrakContextManager.replaceUrl({
|
|
175
|
+
url: window.location?.href,
|
|
176
|
+
context: replaceContext,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
trackEvent(client, "user_referred_completed", {
|
|
180
|
+
properties: {
|
|
181
|
+
status: "success",
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return "success";
|
|
232
186
|
}
|
|
@@ -69,7 +69,6 @@ describe("referralInteraction", () => {
|
|
|
69
69
|
|
|
70
70
|
const mockContext = { r: "0xreferrer" as Hex };
|
|
71
71
|
const mockWalletStatus = { wallet: "0x123" as Hex };
|
|
72
|
-
const mockModalConfig = { type: "login" };
|
|
73
72
|
const mockOptions = { alwaysAppendUrl: true };
|
|
74
73
|
|
|
75
74
|
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext as any);
|
|
@@ -77,14 +76,12 @@ describe("referralInteraction", () => {
|
|
|
77
76
|
vi.mocked(processReferral).mockResolvedValue("success");
|
|
78
77
|
|
|
79
78
|
await referralInteraction(mockClient, {
|
|
80
|
-
modalConfig: mockModalConfig as any,
|
|
81
79
|
options: mockOptions,
|
|
82
80
|
});
|
|
83
81
|
|
|
84
82
|
expect(processReferral).toHaveBeenCalledWith(mockClient, {
|
|
85
83
|
walletStatus: mockWalletStatus,
|
|
86
84
|
frakContext: mockContext,
|
|
87
|
-
modalConfig: mockModalConfig,
|
|
88
85
|
options: mockOptions,
|
|
89
86
|
});
|
|
90
87
|
});
|
|
@@ -110,7 +107,9 @@ describe("referralInteraction", () => {
|
|
|
110
107
|
|
|
111
108
|
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
112
109
|
vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
|
|
113
|
-
vi.mocked(processReferral).
|
|
110
|
+
vi.mocked(processReferral).mockImplementation(() => {
|
|
111
|
+
throw new Error("Test error");
|
|
112
|
+
});
|
|
114
113
|
|
|
115
114
|
const consoleSpy = vi
|
|
116
115
|
.spyOn(console, "warn")
|
|
@@ -139,7 +138,6 @@ describe("referralInteraction", () => {
|
|
|
139
138
|
expect(processReferral).toHaveBeenCalledWith(
|
|
140
139
|
mockClient,
|
|
141
140
|
expect.objectContaining({
|
|
142
|
-
modalConfig: undefined,
|
|
143
141
|
options: undefined,
|
|
144
142
|
})
|
|
145
143
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FrakClient } from "../../types";
|
|
2
2
|
import { FrakContextManager } from "../../utils";
|
|
3
3
|
import { watchWalletStatus } from "../index";
|
|
4
4
|
import {
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
* Function used to handle referral interactions
|
|
11
11
|
* @param client - The current Frak Client
|
|
12
12
|
* @param args
|
|
13
|
-
* @param args.modalConfig - The modal configuration to display if the user is not logged in
|
|
14
13
|
* @param args.options - Some options for the referral interaction
|
|
15
14
|
*
|
|
16
15
|
* @returns A promise with the resulting referral state, or undefined in case of an error
|
|
@@ -18,15 +17,12 @@ import {
|
|
|
18
17
|
* @description This function will automatically handle the referral interaction process
|
|
19
18
|
*
|
|
20
19
|
* @see {@link processReferral} for more details on the automatic referral handling process
|
|
21
|
-
* @see {@link @frak-labs/core-sdk!ModalStepTypes} for more details on each modal steps types
|
|
22
20
|
*/
|
|
23
21
|
export async function referralInteraction(
|
|
24
22
|
client: FrakClient,
|
|
25
23
|
{
|
|
26
|
-
modalConfig,
|
|
27
24
|
options,
|
|
28
25
|
}: {
|
|
29
|
-
modalConfig?: DisplayEmbeddedWalletParamsType;
|
|
30
26
|
options?: ProcessReferralOptions;
|
|
31
27
|
} = {}
|
|
32
28
|
) {
|
|
@@ -39,10 +35,9 @@ export async function referralInteraction(
|
|
|
39
35
|
const currentWalletStatus = await watchWalletStatus(client);
|
|
40
36
|
|
|
41
37
|
try {
|
|
42
|
-
return
|
|
38
|
+
return processReferral(client, {
|
|
43
39
|
walletStatus: currentWalletStatus,
|
|
44
40
|
frakContext,
|
|
45
|
-
modalConfig,
|
|
46
41
|
options,
|
|
47
42
|
});
|
|
48
43
|
} catch (error) {
|
|
@@ -11,12 +11,14 @@ vi.mock("../utils/clientId", () => ({
|
|
|
11
11
|
getClientId: vi.fn().mockReturnValue("test-client-id"),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
-
vi.mock("../utils/
|
|
15
|
-
|
|
14
|
+
vi.mock("../utils/sdkConfigStore", () => ({
|
|
15
|
+
sdkConfigStore: {
|
|
16
|
+
resolveMerchantId: vi.fn().mockResolvedValue(undefined),
|
|
17
|
+
},
|
|
16
18
|
}));
|
|
17
19
|
|
|
18
20
|
import { getClientId } from "../utils/clientId";
|
|
19
|
-
import {
|
|
21
|
+
import { sdkConfigStore } from "../utils/sdkConfigStore";
|
|
20
22
|
import { trackPurchaseStatus } from "./trackPurchaseStatus";
|
|
21
23
|
|
|
22
24
|
describe.sequential("trackPurchaseStatus", () => {
|
|
@@ -100,7 +102,9 @@ describe.sequential("trackPurchaseStatus", () => {
|
|
|
100
102
|
});
|
|
101
103
|
|
|
102
104
|
vi.mocked(getClientId).mockReturnValue("test-client-id");
|
|
103
|
-
vi.mocked(
|
|
105
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
|
|
106
|
+
undefined
|
|
107
|
+
);
|
|
104
108
|
|
|
105
109
|
fetchSpy = vi.fn().mockResolvedValue({
|
|
106
110
|
ok: true,
|
|
@@ -228,12 +232,15 @@ describe.sequential("trackPurchaseStatus", () => {
|
|
|
228
232
|
test("should resolve merchantId from explicit param first", async () => {
|
|
229
233
|
setupStorage({
|
|
230
234
|
interactionToken: "token-123",
|
|
231
|
-
merchantId:
|
|
235
|
+
merchantId: null,
|
|
232
236
|
clientId: "test-client-id",
|
|
233
237
|
});
|
|
234
|
-
vi.mocked(
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
|
|
239
|
+
"fetched-merchant-id"
|
|
240
|
+
);
|
|
241
|
+
const merchantLookupCallsBefore = vi.mocked(
|
|
242
|
+
sdkConfigStore.resolveMerchantId
|
|
243
|
+
).mock.calls.length;
|
|
237
244
|
|
|
238
245
|
await trackPurchaseStatus({
|
|
239
246
|
customerId: "cust-1",
|
|
@@ -253,19 +260,20 @@ describe.sequential("trackPurchaseStatus", () => {
|
|
|
253
260
|
merchantId: "explicit-merchant-id",
|
|
254
261
|
})
|
|
255
262
|
);
|
|
256
|
-
expect(
|
|
257
|
-
|
|
258
|
-
);
|
|
263
|
+
expect(
|
|
264
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mock.calls.length
|
|
265
|
+
).toBe(merchantLookupCallsBefore);
|
|
259
266
|
});
|
|
260
267
|
|
|
261
268
|
test("should fall back to sessionStorage for merchantId", async () => {
|
|
269
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
|
|
270
|
+
"session-merchant-id"
|
|
271
|
+
);
|
|
262
272
|
setupStorage({
|
|
263
273
|
interactionToken: "token-123",
|
|
264
|
-
merchantId:
|
|
274
|
+
merchantId: null,
|
|
265
275
|
clientId: "test-client-id",
|
|
266
276
|
});
|
|
267
|
-
const merchantLookupCallsBefore =
|
|
268
|
-
vi.mocked(fetchMerchantId).mock.calls.length;
|
|
269
277
|
|
|
270
278
|
await trackPurchaseStatus({
|
|
271
279
|
customerId: "cust-1",
|
|
@@ -284,18 +292,20 @@ describe.sequential("trackPurchaseStatus", () => {
|
|
|
284
292
|
merchantId: "session-merchant-id",
|
|
285
293
|
})
|
|
286
294
|
);
|
|
287
|
-
expect(
|
|
288
|
-
|
|
289
|
-
);
|
|
295
|
+
expect(
|
|
296
|
+
vi.mocked(sdkConfigStore.resolveMerchantId)
|
|
297
|
+
).toHaveBeenCalled();
|
|
290
298
|
});
|
|
291
299
|
|
|
292
|
-
test("should fall back to
|
|
300
|
+
test("should fall back to resolveMerchantId when no explicit merchantId", async () => {
|
|
293
301
|
setupStorage({
|
|
294
302
|
interactionToken: "token-123",
|
|
295
303
|
merchantId: null,
|
|
296
304
|
clientId: "test-client-id",
|
|
297
305
|
});
|
|
298
|
-
vi.mocked(
|
|
306
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
|
|
307
|
+
"fetched-merchant-id"
|
|
308
|
+
);
|
|
299
309
|
|
|
300
310
|
await trackPurchaseStatus({
|
|
301
311
|
customerId: "cust-1",
|
|
@@ -322,7 +332,9 @@ describe.sequential("trackPurchaseStatus", () => {
|
|
|
322
332
|
merchantId: null,
|
|
323
333
|
clientId: "test-client-id",
|
|
324
334
|
});
|
|
325
|
-
vi.mocked(
|
|
335
|
+
vi.mocked(sdkConfigStore.resolveMerchantId).mockResolvedValue(
|
|
336
|
+
undefined
|
|
337
|
+
);
|
|
326
338
|
const callCountBefore = getTrackingRequests().length;
|
|
327
339
|
|
|
328
340
|
await trackPurchaseStatus({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getBackendUrl } from "../utils/backendUrl";
|
|
2
2
|
import { getClientId } from "../utils/clientId";
|
|
3
|
-
import {
|
|
3
|
+
import { sdkConfigStore } from "../utils/sdkConfigStore";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Function used to track the status of a purchase
|
|
@@ -26,7 +26,7 @@ import { fetchMerchantId } from "../utils/merchantId";
|
|
|
26
26
|
* }
|
|
27
27
|
*
|
|
28
28
|
* @remarks
|
|
29
|
-
* - Merchant id is resolved in this order: explicit `args.merchantId`, `
|
|
29
|
+
* - Merchant id is resolved in this order: explicit `args.merchantId`, then `sdkConfigStore.resolveMerchantId()` (config store → sessionStorage → backend fetch).
|
|
30
30
|
* - This function supports anonymous users and will use the `x-frak-client-id` header when available.
|
|
31
31
|
* - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
|
|
32
32
|
* - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
|
|
@@ -52,10 +52,8 @@ export async function trackPurchaseStatus(args: {
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const merchantIdFromStorage =
|
|
56
|
-
window.sessionStorage.getItem("frak-merchant-id");
|
|
57
55
|
const merchantId =
|
|
58
|
-
args.merchantId ??
|
|
56
|
+
args.merchantId ?? (await sdkConfigStore.resolveMerchantId());
|
|
59
57
|
|
|
60
58
|
if (!merchantId) {
|
|
61
59
|
console.warn("[Frak] No merchant id found, skipping purchase check");
|
|
@@ -196,7 +196,8 @@ describe("modalBuilder", () => {
|
|
|
196
196
|
|
|
197
197
|
expect(displayModal).toHaveBeenCalledWith(
|
|
198
198
|
mockClient,
|
|
199
|
-
builder.params
|
|
199
|
+
builder.params,
|
|
200
|
+
undefined
|
|
200
201
|
);
|
|
201
202
|
});
|
|
202
203
|
|
|
@@ -216,7 +217,8 @@ describe("modalBuilder", () => {
|
|
|
216
217
|
mockClient,
|
|
217
218
|
expect.objectContaining({
|
|
218
219
|
metadata: { header: { title: "Overridden" } },
|
|
219
|
-
})
|
|
220
|
+
}),
|
|
221
|
+
undefined
|
|
220
222
|
);
|
|
221
223
|
});
|
|
222
224
|
|
|
@@ -46,11 +46,13 @@ export type ModalStepBuilder<
|
|
|
46
46
|
/**
|
|
47
47
|
* Display the modal
|
|
48
48
|
* @param metadataOverride - Function returning optional metadata to override the current modal metadata
|
|
49
|
+
* @param placement - Optional placement ID to associate with this modal display
|
|
49
50
|
*/
|
|
50
51
|
display: (
|
|
51
52
|
metadataOverride?: (
|
|
52
53
|
current?: ModalRpcMetadata
|
|
53
|
-
) => ModalRpcMetadata | undefined
|
|
54
|
+
) => ModalRpcMetadata | undefined,
|
|
55
|
+
placement?: string
|
|
54
56
|
) => Promise<ModalRpcStepsResultType<Steps>>;
|
|
55
57
|
};
|
|
56
58
|
|
|
@@ -177,27 +179,23 @@ function modalStepsBuilder<CurrentSteps extends ModalStepTypes[]>(
|
|
|
177
179
|
);
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
// Function to display it
|
|
181
182
|
async function display(
|
|
182
183
|
metadataOverride?: (
|
|
183
184
|
current?: ModalRpcMetadata
|
|
184
|
-
) => ModalRpcMetadata | undefined
|
|
185
|
+
) => ModalRpcMetadata | undefined,
|
|
186
|
+
placement?: string
|
|
185
187
|
) {
|
|
186
|
-
// If we have a metadata override, apply it
|
|
187
188
|
if (metadataOverride) {
|
|
188
189
|
params.metadata = metadataOverride(params.metadata ?? {});
|
|
189
190
|
}
|
|
190
|
-
return await displayModal(client, params);
|
|
191
|
+
return await displayModal(client, params, placement);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
return {
|
|
194
|
-
// Access current modal params
|
|
195
195
|
params,
|
|
196
|
-
// Function to add new steps
|
|
197
196
|
sendTx,
|
|
198
197
|
reward,
|
|
199
198
|
sharing,
|
|
200
|
-
// Display the modal
|
|
201
199
|
display,
|
|
202
200
|
};
|
|
203
201
|
}
|