@frak-labs/core-sdk 0.1.0-beta.afa252b0 → 0.1.0-beta.b0bd1f8a
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/cdn/bundle.iife.js +14 -0
- package/dist/actions-CEEObPYc.js +1 -0
- package/dist/actions-DbQhWYx8.cjs +1 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +3 -1481
- package/dist/actions.d.ts +3 -1481
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -13
- package/dist/bundle.d.cts +6 -2087
- package/dist/bundle.d.ts +6 -2087
- package/dist/bundle.js +1 -13
- package/dist/index-7OZ39x1U.d.ts +195 -0
- package/dist/index-C6FxkWPC.d.cts +511 -0
- package/dist/index-UFX7xCg3.d.ts +351 -0
- package/dist/index-d8xS4ryI.d.ts +511 -0
- package/dist/index-p4FqSp8z.d.cts +351 -0
- package/dist/index-zDq-VlKx.d.cts +195 -0
- package/dist/index.cjs +1 -13
- package/dist/index.d.cts +4 -1387
- package/dist/index.d.ts +4 -1387
- package/dist/index.js +1 -13
- package/dist/interaction-DMJ3ZfaF.d.cts +45 -0
- package/dist/interaction-KX1h9a7V.d.ts +45 -0
- package/dist/interactions-DnfM3oe0.js +1 -0
- package/dist/interactions-EIXhNLf6.cjs +1 -0
- package/dist/interactions.cjs +1 -1
- package/dist/interactions.d.cts +2 -182
- package/dist/interactions.d.ts +2 -182
- package/dist/interactions.js +1 -1
- package/dist/openSso-D--Airj6.d.cts +1018 -0
- package/dist/openSso-DsKJ4y0j.d.ts +1018 -0
- package/dist/productTypes-BUkXJKZ7.cjs +1 -0
- package/dist/productTypes-CGb1MmBF.js +1 -0
- package/dist/src-B_xO0AR6.cjs +13 -0
- package/dist/src-D2d52OZa.js +13 -0
- package/dist/trackEvent-CHnYa85W.js +1 -0
- package/dist/trackEvent-GuQm_1Nm.cjs +1 -0
- package/package.json +23 -18
- package/src/actions/displayEmbeddedWallet.test.ts +194 -0
- package/src/actions/displayEmbeddedWallet.ts +20 -0
- package/src/actions/displayModal.test.ts +387 -0
- package/src/actions/displayModal.ts +131 -0
- package/src/actions/getProductInformation.test.ts +133 -0
- package/src/actions/getProductInformation.ts +14 -0
- package/src/actions/index.ts +29 -0
- package/src/actions/openSso.test.ts +407 -0
- package/src/actions/openSso.ts +116 -0
- package/src/actions/prepareSso.test.ts +223 -0
- package/src/actions/prepareSso.ts +48 -0
- package/src/actions/referral/processReferral.ts +230 -0
- package/src/actions/referral/referralInteraction.ts +57 -0
- package/src/actions/sendInteraction.test.ts +219 -0
- package/src/actions/sendInteraction.ts +32 -0
- package/src/actions/trackPurchaseStatus.test.ts +287 -0
- package/src/actions/trackPurchaseStatus.ts +53 -0
- package/src/actions/watchWalletStatus.test.ts +372 -0
- package/src/actions/watchWalletStatus.ts +94 -0
- package/src/actions/wrapper/modalBuilder.ts +212 -0
- package/src/actions/wrapper/sendTransaction.ts +62 -0
- package/src/actions/wrapper/siweAuthenticate.ts +94 -0
- package/src/bundle.ts +3 -0
- package/src/clients/DebugInfo.ts +182 -0
- package/src/clients/createIFrameFrakClient.ts +287 -0
- package/src/clients/index.ts +3 -0
- package/src/clients/setupClient.test.ts +343 -0
- package/src/clients/setupClient.ts +73 -0
- package/src/clients/transports/iframeLifecycleManager.test.ts +399 -0
- package/src/clients/transports/iframeLifecycleManager.ts +90 -0
- package/src/constants/interactionTypes.ts +44 -0
- package/src/constants/locales.ts +14 -0
- package/src/constants/productTypes.ts +33 -0
- package/src/index.ts +101 -0
- package/src/interactions/index.ts +5 -0
- package/src/interactions/pressEncoder.test.ts +215 -0
- package/src/interactions/pressEncoder.ts +53 -0
- package/src/interactions/purchaseEncoder.test.ts +291 -0
- package/src/interactions/purchaseEncoder.ts +99 -0
- package/src/interactions/referralEncoder.test.ts +170 -0
- package/src/interactions/referralEncoder.ts +47 -0
- package/src/interactions/retailEncoder.test.ts +107 -0
- package/src/interactions/retailEncoder.ts +37 -0
- package/src/interactions/webshopEncoder.test.ts +56 -0
- package/src/interactions/webshopEncoder.ts +30 -0
- package/src/types/client.ts +14 -0
- package/src/types/compression.ts +22 -0
- package/src/types/config.ts +111 -0
- package/src/types/context.ts +13 -0
- package/src/types/index.ts +71 -0
- package/src/types/lifecycle/client.ts +46 -0
- package/src/types/lifecycle/iframe.ts +35 -0
- package/src/types/lifecycle/index.ts +2 -0
- package/src/types/rpc/displayModal.ts +84 -0
- package/src/types/rpc/embedded/index.ts +68 -0
- package/src/types/rpc/embedded/loggedIn.ts +55 -0
- package/src/types/rpc/embedded/loggedOut.ts +28 -0
- package/src/types/rpc/interaction.ts +43 -0
- package/src/types/rpc/modal/final.ts +46 -0
- package/src/types/rpc/modal/generic.ts +46 -0
- package/src/types/rpc/modal/index.ts +20 -0
- package/src/types/rpc/modal/login.ts +32 -0
- package/src/types/rpc/modal/openSession.ts +25 -0
- package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
- package/src/types/rpc/modal/transaction.ts +33 -0
- package/src/types/rpc/productInformation.ts +59 -0
- package/src/types/rpc/sso.ts +80 -0
- package/src/types/rpc/walletStatus.ts +35 -0
- package/src/types/rpc.ts +158 -0
- package/src/types/transport.ts +34 -0
- package/src/utils/FrakContext.test.ts +407 -0
- package/src/utils/FrakContext.ts +158 -0
- package/src/utils/compression/b64.test.ts +181 -0
- package/src/utils/compression/b64.ts +29 -0
- package/src/utils/compression/compress.test.ts +123 -0
- package/src/utils/compression/compress.ts +11 -0
- package/src/utils/compression/decompress.test.ts +145 -0
- package/src/utils/compression/decompress.ts +11 -0
- package/src/utils/compression/index.ts +3 -0
- package/src/utils/computeProductId.test.ts +80 -0
- package/src/utils/computeProductId.ts +11 -0
- package/src/utils/constants.test.ts +23 -0
- package/src/utils/constants.ts +4 -0
- package/src/utils/formatAmount.test.ts +113 -0
- package/src/utils/formatAmount.ts +18 -0
- package/src/utils/getCurrencyAmountKey.test.ts +44 -0
- package/src/utils/getCurrencyAmountKey.ts +15 -0
- package/src/utils/getSupportedCurrency.test.ts +51 -0
- package/src/utils/getSupportedCurrency.ts +14 -0
- package/src/utils/getSupportedLocale.test.ts +64 -0
- package/src/utils/getSupportedLocale.ts +16 -0
- package/src/utils/iframeHelper.test.ts +450 -0
- package/src/utils/iframeHelper.ts +143 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/sso.test.ts +361 -0
- package/src/utils/sso.ts +119 -0
- package/src/utils/ssoUrlListener.ts +60 -0
- package/src/utils/trackEvent.test.ts +162 -0
- package/src/utils/trackEvent.ts +26 -0
- package/cdn/bundle.js +0 -19
- package/cdn/bundle.js.LICENSE.txt +0 -10
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SSO URL generation utility
|
|
3
|
+
* Tests generateSsoUrl and compressed parameter formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
// Mock compression functions
|
|
9
|
+
vi.mock("./compression/compress", () => ({
|
|
10
|
+
compressJsonToB64: vi.fn((data: unknown) => {
|
|
11
|
+
// Return base64url-like string for testing
|
|
12
|
+
return `compressed_${JSON.stringify(data)}`;
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import type { Hex } from "viem";
|
|
17
|
+
import { describe, expect, it } from "../../tests/vitest-fixtures";
|
|
18
|
+
import type { PrepareSsoParamsType } from "../types";
|
|
19
|
+
import { compressJsonToB64 } from "./compression/compress";
|
|
20
|
+
import { generateSsoUrl } from "./sso";
|
|
21
|
+
|
|
22
|
+
describe("generateSsoUrl", () => {
|
|
23
|
+
const mockProductId =
|
|
24
|
+
"0x1234567890123456789012345678901234567890123456789012345678901234" as Hex;
|
|
25
|
+
const walletUrl = "https://wallet.frak.id";
|
|
26
|
+
|
|
27
|
+
describe("basic URL generation", () => {
|
|
28
|
+
it("should generate SSO URL with basic params", () => {
|
|
29
|
+
const params: PrepareSsoParamsType = {};
|
|
30
|
+
const name = "Test App";
|
|
31
|
+
|
|
32
|
+
const result = generateSsoUrl(
|
|
33
|
+
walletUrl,
|
|
34
|
+
params,
|
|
35
|
+
mockProductId,
|
|
36
|
+
name
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result).toContain("https://wallet.frak.id/sso");
|
|
40
|
+
expect(result).toContain("?p=");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should include compressed parameters in URL", () => {
|
|
44
|
+
const params: PrepareSsoParamsType = {
|
|
45
|
+
directExit: true,
|
|
46
|
+
};
|
|
47
|
+
const name = "My App";
|
|
48
|
+
|
|
49
|
+
const result = generateSsoUrl(
|
|
50
|
+
walletUrl,
|
|
51
|
+
params,
|
|
52
|
+
mockProductId,
|
|
53
|
+
name
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(result).toContain("?p=compressed_");
|
|
57
|
+
expect(compressJsonToB64).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should set /sso pathname", () => {
|
|
61
|
+
const params: PrepareSsoParamsType = {};
|
|
62
|
+
const name = "App";
|
|
63
|
+
|
|
64
|
+
const result = generateSsoUrl(
|
|
65
|
+
walletUrl,
|
|
66
|
+
params,
|
|
67
|
+
mockProductId,
|
|
68
|
+
name
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const url = new URL(result);
|
|
72
|
+
expect(url.pathname).toBe("/sso");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should preserve wallet base URL", () => {
|
|
76
|
+
const customWalletUrl = "https://custom-wallet.com";
|
|
77
|
+
const params: PrepareSsoParamsType = {};
|
|
78
|
+
const name = "App";
|
|
79
|
+
|
|
80
|
+
const result = generateSsoUrl(
|
|
81
|
+
customWalletUrl,
|
|
82
|
+
params,
|
|
83
|
+
mockProductId,
|
|
84
|
+
name
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(result).toContain("https://custom-wallet.com/sso");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("parameter compression format", () => {
|
|
92
|
+
it("should compress params with name and productId", () => {
|
|
93
|
+
const params: PrepareSsoParamsType = {};
|
|
94
|
+
const name = "Test App";
|
|
95
|
+
|
|
96
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
97
|
+
|
|
98
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
p: mockProductId,
|
|
101
|
+
m: expect.objectContaining({
|
|
102
|
+
n: name,
|
|
103
|
+
}),
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should include redirectUrl in compressed params", () => {
|
|
109
|
+
const params: PrepareSsoParamsType = {
|
|
110
|
+
redirectUrl: "https://example.com/callback",
|
|
111
|
+
};
|
|
112
|
+
const name = "App";
|
|
113
|
+
|
|
114
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
115
|
+
|
|
116
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
r: "https://example.com/callback",
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should include directExit in compressed params", () => {
|
|
124
|
+
const params: PrepareSsoParamsType = {
|
|
125
|
+
directExit: true,
|
|
126
|
+
};
|
|
127
|
+
const name = "App";
|
|
128
|
+
|
|
129
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
130
|
+
|
|
131
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
d: true,
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should include lang in compressed params", () => {
|
|
139
|
+
const params: PrepareSsoParamsType = {
|
|
140
|
+
lang: "fr",
|
|
141
|
+
};
|
|
142
|
+
const name = "App";
|
|
143
|
+
|
|
144
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
145
|
+
|
|
146
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({
|
|
148
|
+
l: "fr",
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should include custom CSS when provided", () => {
|
|
154
|
+
const params: PrepareSsoParamsType = {};
|
|
155
|
+
const name = "App";
|
|
156
|
+
const css = "body { color: red; }";
|
|
157
|
+
|
|
158
|
+
generateSsoUrl(walletUrl, params, mockProductId, name, css);
|
|
159
|
+
|
|
160
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
161
|
+
expect.objectContaining({
|
|
162
|
+
m: expect.objectContaining({
|
|
163
|
+
css: "body { color: red; }",
|
|
164
|
+
}),
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should include metadata logoUrl", () => {
|
|
170
|
+
const params: PrepareSsoParamsType = {
|
|
171
|
+
metadata: {
|
|
172
|
+
logoUrl: "https://example.com/logo.png",
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const name = "App";
|
|
176
|
+
|
|
177
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
178
|
+
|
|
179
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
m: expect.objectContaining({
|
|
182
|
+
l: "https://example.com/logo.png",
|
|
183
|
+
}),
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should include metadata homepageLink", () => {
|
|
189
|
+
const params: PrepareSsoParamsType = {
|
|
190
|
+
metadata: {
|
|
191
|
+
homepageLink: "https://example.com",
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const name = "App";
|
|
195
|
+
|
|
196
|
+
generateSsoUrl(walletUrl, params, mockProductId, name);
|
|
197
|
+
|
|
198
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({
|
|
200
|
+
m: expect.objectContaining({
|
|
201
|
+
h: "https://example.com",
|
|
202
|
+
}),
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should include all metadata fields together", () => {
|
|
208
|
+
const params: PrepareSsoParamsType = {
|
|
209
|
+
metadata: {
|
|
210
|
+
logoUrl: "https://example.com/logo.png",
|
|
211
|
+
homepageLink: "https://example.com",
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const name = "Full App";
|
|
215
|
+
const css = "body { background: blue; }";
|
|
216
|
+
|
|
217
|
+
generateSsoUrl(walletUrl, params, mockProductId, name, css);
|
|
218
|
+
|
|
219
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
m: {
|
|
222
|
+
n: "Full App",
|
|
223
|
+
css: "body { background: blue; }",
|
|
224
|
+
l: "https://example.com/logo.png",
|
|
225
|
+
h: "https://example.com",
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("full parameter combinations", () => {
|
|
233
|
+
it("should handle all parameters together", () => {
|
|
234
|
+
const params: PrepareSsoParamsType = {
|
|
235
|
+
redirectUrl: "https://example.com/callback",
|
|
236
|
+
directExit: false,
|
|
237
|
+
lang: "fr",
|
|
238
|
+
metadata: {
|
|
239
|
+
logoUrl: "https://example.com/logo.png",
|
|
240
|
+
homepageLink: "https://example.com/home",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
const name = "Complete App";
|
|
244
|
+
const css = "body { margin: 0; }";
|
|
245
|
+
|
|
246
|
+
const result = generateSsoUrl(
|
|
247
|
+
walletUrl,
|
|
248
|
+
params,
|
|
249
|
+
mockProductId,
|
|
250
|
+
name,
|
|
251
|
+
css
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
expect(result).toContain("https://wallet.frak.id/sso");
|
|
255
|
+
expect(result).toContain("?p=");
|
|
256
|
+
expect(compressJsonToB64).toHaveBeenCalledWith({
|
|
257
|
+
r: "https://example.com/callback",
|
|
258
|
+
d: false,
|
|
259
|
+
l: "fr",
|
|
260
|
+
p: mockProductId,
|
|
261
|
+
m: {
|
|
262
|
+
n: "Complete App",
|
|
263
|
+
css: "body { margin: 0; }",
|
|
264
|
+
l: "https://example.com/logo.png",
|
|
265
|
+
h: "https://example.com/home",
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should handle minimal parameters", () => {
|
|
271
|
+
const params: PrepareSsoParamsType = {};
|
|
272
|
+
const name = "Minimal App";
|
|
273
|
+
|
|
274
|
+
const result = generateSsoUrl(
|
|
275
|
+
walletUrl,
|
|
276
|
+
params,
|
|
277
|
+
mockProductId,
|
|
278
|
+
name
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(result).toBeDefined();
|
|
282
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
283
|
+
expect.objectContaining({
|
|
284
|
+
p: mockProductId,
|
|
285
|
+
m: expect.objectContaining({
|
|
286
|
+
n: "Minimal App",
|
|
287
|
+
}),
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("edge cases", () => {
|
|
294
|
+
it("should handle empty name", () => {
|
|
295
|
+
const params: PrepareSsoParamsType = {};
|
|
296
|
+
const name = "";
|
|
297
|
+
|
|
298
|
+
const result = generateSsoUrl(
|
|
299
|
+
walletUrl,
|
|
300
|
+
params,
|
|
301
|
+
mockProductId,
|
|
302
|
+
name
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
expect(result).toBeDefined();
|
|
306
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
307
|
+
expect.objectContaining({
|
|
308
|
+
m: expect.objectContaining({
|
|
309
|
+
n: "",
|
|
310
|
+
}),
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should handle undefined optional parameters", () => {
|
|
316
|
+
const params: PrepareSsoParamsType = {};
|
|
317
|
+
const name = "App";
|
|
318
|
+
|
|
319
|
+
generateSsoUrl(walletUrl, params, mockProductId, name, undefined);
|
|
320
|
+
|
|
321
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
322
|
+
expect.objectContaining({
|
|
323
|
+
m: expect.objectContaining({
|
|
324
|
+
n: "App",
|
|
325
|
+
css: undefined,
|
|
326
|
+
}),
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should handle wallet URL with trailing slash", () => {
|
|
332
|
+
const walletUrlWithSlash = "https://wallet.frak.id/";
|
|
333
|
+
const params: PrepareSsoParamsType = {};
|
|
334
|
+
const name = "App";
|
|
335
|
+
|
|
336
|
+
const result = generateSsoUrl(
|
|
337
|
+
walletUrlWithSlash,
|
|
338
|
+
params,
|
|
339
|
+
mockProductId,
|
|
340
|
+
name
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(result).toContain("https://wallet.frak.id/sso");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("should handle different product IDs", () => {
|
|
347
|
+
const differentProductId =
|
|
348
|
+
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdabcdefabcdefabcdefabcd" as Hex;
|
|
349
|
+
const params: PrepareSsoParamsType = {};
|
|
350
|
+
const name = "App";
|
|
351
|
+
|
|
352
|
+
generateSsoUrl(walletUrl, params, differentProductId, name);
|
|
353
|
+
|
|
354
|
+
expect(compressJsonToB64).toHaveBeenCalledWith(
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
p: differentProductId,
|
|
357
|
+
})
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
package/src/utils/sso.ts
ADDED
|
@@ -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,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for trackEvent utility function
|
|
3
|
+
* Tests OpenPanel event tracking wrapper
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
it,
|
|
12
|
+
vi,
|
|
13
|
+
} from "../../tests/vitest-fixtures";
|
|
14
|
+
import type { FrakClient } from "../types";
|
|
15
|
+
import { trackEvent } from "./trackEvent";
|
|
16
|
+
|
|
17
|
+
describe("trackEvent", () => {
|
|
18
|
+
let mockClient: FrakClient;
|
|
19
|
+
let consoleDebugSpy: any;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Create mock client
|
|
23
|
+
mockClient = {
|
|
24
|
+
openPanel: {
|
|
25
|
+
track: vi.fn(),
|
|
26
|
+
},
|
|
27
|
+
} as unknown as FrakClient;
|
|
28
|
+
|
|
29
|
+
// Spy on console.debug
|
|
30
|
+
consoleDebugSpy = vi
|
|
31
|
+
.spyOn(console, "debug")
|
|
32
|
+
.mockImplementation(() => {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
consoleDebugSpy.mockRestore();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("success cases", () => {
|
|
40
|
+
it("should track event with client", () => {
|
|
41
|
+
trackEvent(mockClient, "share_button_clicked");
|
|
42
|
+
|
|
43
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
44
|
+
"share_button_clicked",
|
|
45
|
+
{}
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should track event with props", () => {
|
|
50
|
+
const props = { userId: "123", page: "home" };
|
|
51
|
+
trackEvent(mockClient, "wallet_button_clicked", props);
|
|
52
|
+
|
|
53
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
54
|
+
"wallet_button_clicked",
|
|
55
|
+
props
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should track share_modal_error event", () => {
|
|
60
|
+
const props = { error: "Network error" };
|
|
61
|
+
trackEvent(mockClient, "share_modal_error", props);
|
|
62
|
+
|
|
63
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
64
|
+
"share_modal_error",
|
|
65
|
+
props
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should track user_referred event", () => {
|
|
70
|
+
trackEvent(mockClient, "user_referred");
|
|
71
|
+
|
|
72
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
73
|
+
"user_referred",
|
|
74
|
+
{}
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("without client", () => {
|
|
80
|
+
it("should not throw when client is undefined", () => {
|
|
81
|
+
expect(() => {
|
|
82
|
+
trackEvent(undefined, "share_button_clicked");
|
|
83
|
+
}).not.toThrow();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should log debug message when client is undefined", () => {
|
|
87
|
+
trackEvent(undefined, "wallet_button_clicked");
|
|
88
|
+
|
|
89
|
+
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
|
90
|
+
"[Frak] No client provided, skipping event tracking"
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should not call track when client is undefined", () => {
|
|
95
|
+
const trackMock = vi.fn();
|
|
96
|
+
const undefinedClient = undefined;
|
|
97
|
+
|
|
98
|
+
trackEvent(undefinedClient, "share_button_clicked");
|
|
99
|
+
|
|
100
|
+
expect(trackMock).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("error handling", () => {
|
|
105
|
+
it("should catch and log errors from track()", () => {
|
|
106
|
+
const error = new Error("Track failed");
|
|
107
|
+
mockClient.openPanel = {
|
|
108
|
+
track: vi.fn().mockImplementation(() => {
|
|
109
|
+
throw error;
|
|
110
|
+
}),
|
|
111
|
+
} as any;
|
|
112
|
+
|
|
113
|
+
expect(() => {
|
|
114
|
+
trackEvent(mockClient, "share_button_clicked");
|
|
115
|
+
}).not.toThrow();
|
|
116
|
+
|
|
117
|
+
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
|
118
|
+
"[Frak] Failed to track event:",
|
|
119
|
+
"share_button_clicked",
|
|
120
|
+
error
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should not throw when openPanel is undefined", () => {
|
|
125
|
+
const clientWithoutPanel = {} as FrakClient;
|
|
126
|
+
|
|
127
|
+
expect(() => {
|
|
128
|
+
trackEvent(clientWithoutPanel, "wallet_button_clicked");
|
|
129
|
+
}).not.toThrow();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("edge cases", () => {
|
|
134
|
+
it("should handle empty props object", () => {
|
|
135
|
+
trackEvent(mockClient, "share_button_clicked", {});
|
|
136
|
+
|
|
137
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
138
|
+
"share_button_clicked",
|
|
139
|
+
{}
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should handle complex props object", () => {
|
|
144
|
+
const complexProps = {
|
|
145
|
+
userId: "123",
|
|
146
|
+
metadata: {
|
|
147
|
+
page: "home",
|
|
148
|
+
section: "header",
|
|
149
|
+
},
|
|
150
|
+
tags: ["tag1", "tag2"],
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
trackEvent(mockClient, "user_referred", complexProps);
|
|
155
|
+
|
|
156
|
+
expect(mockClient.openPanel?.track).toHaveBeenCalledWith(
|
|
157
|
+
"user_referred",
|
|
158
|
+
complexProps
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|