@tomo-inc/embedded-wallet-providers 0.0.21 → 0.0.23

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/dist/index.cjs CHANGED
@@ -287,15 +287,7 @@ var EmbeddedWallet = class _EmbeddedWallet {
287
287
  if (!config) {
288
288
  throw new Error("config is not initialized");
289
289
  }
290
- const cacheKey = config.clientId + "-cube-oidc-token";
291
- if (oidcToken) {
292
- walletUtils.cache.set(cacheKey, oidcToken, false);
293
- }
294
- this.oidcToken = oidcToken || walletUtils.cache.get(cacheKey) || "";
295
- console.log("embedded wallet login with oidcToken:", config, this.oidcToken);
296
- if (!this.oidcToken) {
297
- console.warn("oidcToken is not found");
298
- }
290
+ this.oidcToken = oidcToken || "";
299
291
  return new Promise((resolve, reject) => {
300
292
  const receiveResponse = (event) => {
301
293
  var _a, _b;
@@ -345,7 +337,7 @@ var EmbeddedWallet = class _EmbeddedWallet {
345
337
  dappOrigin: window.location.origin,
346
338
  tomoStage: stage,
347
339
  tomoClientId: clientId,
348
- oidcToken: this.oidcToken || "",
340
+ oidcToken: this.oidcToken,
349
341
  logo: logo || "",
350
342
  name: name || ""
351
343
  });
@@ -388,6 +380,8 @@ var EmbeddedWallet = class _EmbeddedWallet {
388
380
  async logout() {
389
381
  if (this.walletIframe) {
390
382
  this.walletIframe.src = `${this.walletOrigin}#logout=true`;
383
+ const config = this.config;
384
+ config && this.init(config);
391
385
  }
392
386
  window.removeEventListener("message", this.walletCloseHandler);
393
387
  window.removeEventListener("message", this.logoutListener);
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TronProvider, SolanaProvider, DogecoinProvider, BitcoinProvider, EvmProvider } from '@tomo-inc/inject-providers';
2
- import { ChainTypeEnum, ProviderProtocol, ProviderStandard, cache } from '@tomo-inc/wallet-utils';
2
+ import { ChainTypeEnum, ProviderProtocol, ProviderStandard } from '@tomo-inc/wallet-utils';
3
3
  import { OidcAuth } from '@tomo-inc/oidc-auth';
4
4
 
5
5
  var __defProp = Object.defineProperty;
@@ -285,15 +285,7 @@ var EmbeddedWallet = class _EmbeddedWallet {
285
285
  if (!config) {
286
286
  throw new Error("config is not initialized");
287
287
  }
288
- const cacheKey = config.clientId + "-cube-oidc-token";
289
- if (oidcToken) {
290
- cache.set(cacheKey, oidcToken, false);
291
- }
292
- this.oidcToken = oidcToken || cache.get(cacheKey) || "";
293
- console.log("embedded wallet login with oidcToken:", config, this.oidcToken);
294
- if (!this.oidcToken) {
295
- console.warn("oidcToken is not found");
296
- }
288
+ this.oidcToken = oidcToken || "";
297
289
  return new Promise((resolve, reject) => {
298
290
  const receiveResponse = (event) => {
299
291
  var _a, _b;
@@ -343,7 +335,7 @@ var EmbeddedWallet = class _EmbeddedWallet {
343
335
  dappOrigin: window.location.origin,
344
336
  tomoStage: stage,
345
337
  tomoClientId: clientId,
346
- oidcToken: this.oidcToken || "",
338
+ oidcToken: this.oidcToken,
347
339
  logo: logo || "",
348
340
  name: name || ""
349
341
  });
@@ -386,6 +378,8 @@ var EmbeddedWallet = class _EmbeddedWallet {
386
378
  async logout() {
387
379
  if (this.walletIframe) {
388
380
  this.walletIframe.src = `${this.walletOrigin}#logout=true`;
381
+ const config = this.config;
382
+ config && this.init(config);
389
383
  }
390
384
  window.removeEventListener("message", this.walletCloseHandler);
391
385
  window.removeEventListener("message", this.logoutListener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomo-inc/embedded-wallet-providers",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "author": "tomo.inc",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -16,8 +16,8 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@tomo-inc/oidc-auth": "0.0.16",
20
- "@tomo-inc/wallet-utils": "0.0.18",
19
+ "@tomo-inc/oidc-auth": "0.0.17",
20
+ "@tomo-inc/wallet-utils": "0.0.19",
21
21
  "@tomo-inc/inject-providers": "0.0.16"
22
22
  },
23
23
  "devDependencies": {
@@ -28,7 +28,12 @@
28
28
  "tsx": "^4.19.2",
29
29
  "typescript": "^5.0.0",
30
30
  "@vitest/browser": "^3.2.4",
31
+ "@vitest/coverage-v8": "^3.2.4",
31
32
  "playwright": "^1.44.1",
32
33
  "vitest": "^3.2.4"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format esm,cjs --dts --treeshake",
37
+ "test": "vitest run"
33
38
  }
34
39
  }
package/project.json CHANGED
@@ -51,7 +51,7 @@
51
51
  "executor": "nx:run-commands",
52
52
  "outputs": ["{projectRoot}/coverage"],
53
53
  "options": {
54
- "command": "vitest run",
54
+ "command": "vitest run --coverage",
55
55
  "cwd": "packages/embedded-wallet-providers"
56
56
  }
57
57
  },
@@ -0,0 +1,600 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { EmbeddedWallet } from "../embedded-wallet";
3
+
4
+ // Mocks are defined in setup.ts to prevent @okxweb3/crypto-lib module initialization
5
+
6
+ describe("EmbeddedWallet", () => {
7
+ let embeddedWallet: EmbeddedWallet;
8
+ let mockIframe: HTMLIFrameElement;
9
+ let originalGetElementById: typeof document.getElementById;
10
+ let originalCreateElement: typeof document.createElement;
11
+ let originalAppendChild: typeof document.body.appendChild;
12
+ let messageHandlers: Array<(event: MessageEvent) => void>;
13
+
14
+ beforeEach(() => {
15
+ embeddedWallet = EmbeddedWallet.getInstance();
16
+ vi.clearAllMocks();
17
+
18
+ // Mock iframe
19
+ mockIframe = {
20
+ id: "",
21
+ src: "",
22
+ style: {
23
+ cssText: "",
24
+ width: "",
25
+ height: "",
26
+ backgroundColor: "",
27
+ },
28
+ allow: "",
29
+ closed: false,
30
+ setAttribute: vi.fn(),
31
+ contentWindow: {
32
+ postMessage: vi.fn(),
33
+ } as any,
34
+ } as any;
35
+
36
+ // Mock document methods
37
+ originalGetElementById = document.getElementById;
38
+ originalCreateElement = document.createElement;
39
+ originalAppendChild = document.body.appendChild;
40
+
41
+ document.getElementById = vi.fn().mockReturnValue(null);
42
+ document.createElement = vi.fn((tagName: string) => {
43
+ if (tagName === "iframe") {
44
+ return mockIframe;
45
+ }
46
+ return originalCreateElement.call(document, tagName);
47
+ });
48
+ document.body.appendChild = vi.fn();
49
+
50
+ // Mock message event listeners
51
+ messageHandlers = [];
52
+ window.addEventListener = vi.fn((event: string, handler: any) => {
53
+ if (event === "message") {
54
+ messageHandlers.push(handler);
55
+ }
56
+ });
57
+ window.removeEventListener = vi.fn();
58
+
59
+ // Mock window.location
60
+ Object.defineProperty(window, "location", {
61
+ value: {
62
+ origin: "https://test.com",
63
+ },
64
+ writable: true,
65
+ });
66
+ });
67
+
68
+ afterEach(() => {
69
+ document.getElementById = originalGetElementById;
70
+ document.createElement = originalCreateElement;
71
+ document.body.appendChild = originalAppendChild;
72
+ });
73
+
74
+ describe("getInstance", () => {
75
+ it("should return singleton instance", () => {
76
+ const instance1 = EmbeddedWallet.getInstance();
77
+ const instance2 = EmbeddedWallet.getInstance();
78
+ expect(instance1).toBe(instance2);
79
+ });
80
+ });
81
+
82
+ describe("init", () => {
83
+ it("should initialize with valid config", async () => {
84
+ const config = {
85
+ clientId: "test-client-id",
86
+ walletBaseUrl: "https://wallet.tomo.inc",
87
+ name: "Test Wallet",
88
+ logo: "https://example.com/logo.png",
89
+ tomoStage: "dev" as const,
90
+ xClientId: "x-client-id",
91
+ googleClientId: "google-client-id",
92
+ };
93
+
94
+ // Mock login response
95
+ setTimeout(() => {
96
+ const handler = messageHandlers[0];
97
+ if (handler) {
98
+ handler({
99
+ origin: "https://wallet.tomo.inc",
100
+ data: {
101
+ type: "wallet-ready",
102
+ data: {
103
+ isAvailable: true,
104
+ connectedInfo: {
105
+ evmProvider: {
106
+ connected: true,
107
+ address: ["0x123"],
108
+ },
109
+ },
110
+ },
111
+ },
112
+ } as MessageEvent);
113
+ }
114
+ }, 10);
115
+
116
+ const result = await embeddedWallet.init(config);
117
+
118
+ expect(result.isAvailable).toBe(true);
119
+ expect(embeddedWallet.config).toEqual(config);
120
+ expect(embeddedWallet.walletOrigin).toBe("https://wallet.tomo.inc");
121
+ });
122
+
123
+ it("should throw error if clientId is missing", async () => {
124
+ const config = {
125
+ walletBaseUrl: "https://wallet.tomo.inc",
126
+ name: "Test",
127
+ logo: "",
128
+ tomoStage: "dev" as const,
129
+ xClientId: "x",
130
+ googleClientId: "g",
131
+ } as any;
132
+
133
+ await expect(embeddedWallet.init(config)).rejects.toThrow("clientId is required.");
134
+ });
135
+
136
+ it("should throw error if walletBaseUrl is missing", async () => {
137
+ const config = {
138
+ clientId: "test",
139
+ name: "Test",
140
+ logo: "",
141
+ tomoStage: "dev" as const,
142
+ xClientId: "x",
143
+ googleClientId: "g",
144
+ } as any;
145
+
146
+ await expect(embeddedWallet.init(config)).rejects.toThrow("walletBaseUrl is required.");
147
+ });
148
+
149
+ it("should build rdns correctly when walletBaseUrl has port", async () => {
150
+ const config = {
151
+ clientId: "port-client",
152
+ walletBaseUrl: "https://wallet.example.com:8080",
153
+ name: "Test",
154
+ logo: "",
155
+ tomoStage: "dev" as const,
156
+ xClientId: "x",
157
+ googleClientId: "g",
158
+ };
159
+
160
+ setTimeout(() => {
161
+ const handler = messageHandlers[0];
162
+ if (handler) {
163
+ handler({
164
+ origin: "https://wallet.example.com:8080",
165
+ data: {
166
+ type: "wallet-ready",
167
+ data: { isAvailable: true, connectedInfo: null },
168
+ },
169
+ } as MessageEvent);
170
+ }
171
+ }, 10);
172
+
173
+ await embeddedWallet.init(config);
174
+ expect(embeddedWallet.walletOrigin).toBe("https://wallet.example.com:8080");
175
+ expect(embeddedWallet.info?.uuid).toContain("8080");
176
+ });
177
+ });
178
+
179
+ describe("setWalletZIndex", () => {
180
+ it("should set mask z-index", () => {
181
+ embeddedWallet.setWalletZIndex(9999);
182
+ expect(embeddedWallet.maskZIndex).toBe(9999);
183
+ });
184
+ });
185
+
186
+ describe("popup", () => {
187
+ it("should show wallet iframe", () => {
188
+ embeddedWallet.walletIframe = mockIframe;
189
+ embeddedWallet.popup();
190
+
191
+ expect(mockIframe.style.cssText).toContain("position: fixed");
192
+ expect(mockIframe.style.cssText).toContain("z-index: 999");
193
+ });
194
+
195
+ it("should do nothing if walletIframe is null", () => {
196
+ embeddedWallet.walletIframe = null;
197
+ expect(() => embeddedWallet.popup()).not.toThrow();
198
+ });
199
+ });
200
+
201
+ describe("close", () => {
202
+ it("should hide wallet iframe", () => {
203
+ embeddedWallet.walletIframe = mockIframe;
204
+ embeddedWallet.close();
205
+
206
+ expect(mockIframe.style.width).toBe("0");
207
+ expect(mockIframe.style.height).toBe("0");
208
+ });
209
+
210
+ it("should do nothing if walletIframe is null", () => {
211
+ embeddedWallet.walletIframe = null;
212
+ expect(() => embeddedWallet.close()).not.toThrow();
213
+ });
214
+ });
215
+
216
+ describe("request", () => {
217
+ it("should send request to wallet iframe", async () => {
218
+ embeddedWallet.walletIframe = mockIframe;
219
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
220
+
221
+ // Mock response
222
+ setTimeout(() => {
223
+ const handler = messageHandlers[0];
224
+ if (handler) {
225
+ handler({
226
+ origin: "https://wallet.tomo.inc",
227
+ data: {
228
+ type: "wallet-response",
229
+ data: { result: "success" },
230
+ },
231
+ } as MessageEvent);
232
+ }
233
+ }, 10);
234
+
235
+ const result = await embeddedWallet.request("token", { test: "params" });
236
+
237
+ expect(mockIframe.contentWindow?.postMessage).toHaveBeenCalled();
238
+ expect(result).toEqual({ result: "success" });
239
+ });
240
+ });
241
+
242
+ describe("open", () => {
243
+ it("should open wallet page and show popup", () => {
244
+ embeddedWallet.walletIframe = mockIframe;
245
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
246
+
247
+ embeddedWallet.open("wallet", { test: "params" });
248
+
249
+ expect(mockIframe.contentWindow?.postMessage).toHaveBeenCalled();
250
+ expect(mockIframe.style.cssText).toContain("position: fixed");
251
+ });
252
+ });
253
+
254
+ describe("onLoginStatusChanged", () => {
255
+ it("should set login status callback", () => {
256
+ const callback = vi.fn();
257
+ embeddedWallet.onLoginStatusChanged(callback);
258
+
259
+ expect(embeddedWallet.loginStatusCallback).toBe(callback);
260
+ expect(window.addEventListener).toHaveBeenCalled();
261
+ });
262
+
263
+ it("should call loginStatusCallback with logout when logout message received", () => {
264
+ embeddedWallet.walletIframe = mockIframe;
265
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
266
+ const callback = vi.fn();
267
+ let capturedHandler: ((e: MessageEvent) => void) | null = null;
268
+ window.addEventListener = vi.fn((_event: string, handler: any) => {
269
+ messageHandlers.push(handler);
270
+ capturedHandler = handler;
271
+ });
272
+
273
+ embeddedWallet.onLoginStatusChanged(callback);
274
+
275
+ expect(capturedHandler).not.toBeNull();
276
+ capturedHandler!({
277
+ origin: "https://wallet.tomo.inc",
278
+ data: { type: "wallet-response", method: "logout" },
279
+ } as MessageEvent);
280
+
281
+ expect(callback).toHaveBeenCalledWith({ type: "logout" });
282
+ });
283
+ });
284
+
285
+ describe("themeChange", () => {
286
+ it("should persist theme and send theme-change request", async () => {
287
+ embeddedWallet.walletIframe = mockIframe;
288
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
289
+
290
+ embeddedWallet.themeChange({ mode: "dark" });
291
+
292
+ expect(embeddedWallet["themeConfig"]).toEqual({ mode: "dark" });
293
+ expect(mockIframe.contentWindow?.postMessage).toHaveBeenCalledWith(
294
+ expect.objectContaining({
295
+ type: "wallet-request",
296
+ data: { method: "theme-change", params: { mode: "dark" } },
297
+ }),
298
+ "https://wallet.tomo.inc",
299
+ );
300
+ });
301
+
302
+ it("should set themeConfig and call request with theme-change", () => {
303
+ embeddedWallet.walletIframe = mockIframe;
304
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
305
+ const requestSpy = vi.spyOn(embeddedWallet, "request").mockResolvedValue(undefined);
306
+
307
+ embeddedWallet.themeChange({ primary: "#000" });
308
+
309
+ expect(embeddedWallet["themeConfig"]).toEqual({ primary: "#000" });
310
+ expect(requestSpy).toHaveBeenCalledWith("theme-change", { primary: "#000" });
311
+ requestSpy.mockRestore();
312
+ });
313
+ });
314
+
315
+ describe("logout", () => {
316
+ it("should set iframe src to logout and remove listeners", async () => {
317
+ embeddedWallet.walletIframe = mockIframe;
318
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
319
+
320
+ const result = await embeddedWallet.logout();
321
+
322
+ expect(result).toBe(true);
323
+ expect(mockIframe.src).toBe("https://wallet.tomo.inc#logout=true");
324
+ expect(window.removeEventListener).toHaveBeenCalled();
325
+ });
326
+
327
+ });
328
+
329
+ describe("login", () => {
330
+ it("should throw when config is not initialized", async () => {
331
+ embeddedWallet.config = null;
332
+ await expect(embeddedWallet.login()).rejects.toThrow("config is not initialized");
333
+ });
334
+
335
+ it("should throw when walletBaseUrl or clientId is missing in config", async () => {
336
+ embeddedWallet.config = { clientId: "c", walletBaseUrl: "" } as any;
337
+ document.getElementById = vi.fn().mockReturnValue(null);
338
+ document.body.appendChild = vi.fn();
339
+
340
+ await expect(embeddedWallet.login()).rejects.toThrow("walletBaseUrl + clientId is required");
341
+ });
342
+
343
+ it("should reject when appendChild throws", async () => {
344
+ embeddedWallet.config = {
345
+ clientId: "test-id",
346
+ walletBaseUrl: "https://wallet.tomo.inc",
347
+ name: "Test",
348
+ logo: "",
349
+ stage: "dev",
350
+ xClientId: "x",
351
+ googleClientId: "g",
352
+ } as any;
353
+ document.getElementById = vi.fn().mockReturnValue(null);
354
+ document.body.appendChild = vi.fn().mockImplementation(() => {
355
+ throw new Error("append failed");
356
+ });
357
+
358
+ await expect(embeddedWallet.login()).rejects.toThrow("append failed");
359
+ expect(window.removeEventListener).toHaveBeenCalled();
360
+ });
361
+
362
+ it("should call loginStatusCallback when wallet-ready received", async () => {
363
+ const config = {
364
+ clientId: "test-client-id",
365
+ walletBaseUrl: "https://wallet.tomo.inc",
366
+ name: "Test",
367
+ logo: "",
368
+ tomoStage: "dev" as const,
369
+ xClientId: "x",
370
+ googleClientId: "g",
371
+ };
372
+ embeddedWallet.loginStatusCallback = vi.fn();
373
+
374
+ setTimeout(() => {
375
+ const handler = messageHandlers[0];
376
+ if (handler) {
377
+ handler({
378
+ origin: "https://wallet.tomo.inc",
379
+ data: {
380
+ type: "wallet-ready",
381
+ data: { isAvailable: true, connectedInfo: null },
382
+ },
383
+ } as MessageEvent);
384
+ }
385
+ }, 10);
386
+
387
+ await embeddedWallet.init(config);
388
+
389
+ expect(embeddedWallet.loginStatusCallback).toHaveBeenCalledWith({ type: "login" });
390
+ });
391
+
392
+ it("should cache oidcToken when login is called with token", async () => {
393
+ const config = {
394
+ clientId: "cache-client",
395
+ walletBaseUrl: "https://wallet.tomo.inc",
396
+ name: "Test",
397
+ logo: "",
398
+ tomoStage: "dev" as const,
399
+ xClientId: "x",
400
+ googleClientId: "g",
401
+ };
402
+ setTimeout(() => {
403
+ const handler = messageHandlers[0];
404
+ if (handler) {
405
+ handler({
406
+ origin: "https://wallet.tomo.inc",
407
+ data: {
408
+ type: "wallet-ready",
409
+ data: { isAvailable: true, connectedInfo: null },
410
+ },
411
+ } as MessageEvent);
412
+ }
413
+ }, 10);
414
+ await embeddedWallet.init(config);
415
+
416
+ const lenBefore = messageHandlers.length;
417
+ const loginPromise = embeddedWallet.login("cached-oidc-token");
418
+ expect(embeddedWallet["oidcToken"]).toBe("cached-oidc-token");
419
+
420
+ setTimeout(() => {
421
+ const handler = messageHandlers[lenBefore];
422
+ if (handler) {
423
+ handler({
424
+ origin: "https://wallet.tomo.inc",
425
+ data: {
426
+ type: "wallet-ready",
427
+ data: { isAvailable: true, connectedInfo: null },
428
+ },
429
+ } as MessageEvent);
430
+ }
431
+ }, 50);
432
+ await loginPromise;
433
+ }, 8000);
434
+
435
+ it("should remove existing iframe when login called with oidcToken", async () => {
436
+ const existingIframe = {
437
+ id: "existing-client",
438
+ remove: vi.fn(),
439
+ style: {},
440
+ setAttribute: vi.fn(),
441
+ allow: "",
442
+ contentWindow: { postMessage: vi.fn() },
443
+ } as any;
444
+ document.getElementById = vi.fn().mockReturnValue(existingIframe);
445
+ document.body.appendChild = vi.fn();
446
+
447
+ embeddedWallet.config = {
448
+ clientId: "existing-client",
449
+ walletBaseUrl: "https://wallet.tomo.inc",
450
+ name: "Test",
451
+ logo: "",
452
+ stage: "dev",
453
+ xClientId: "x",
454
+ googleClientId: "g",
455
+ } as any;
456
+
457
+ setTimeout(() => {
458
+ const handler = messageHandlers[0];
459
+ if (handler) {
460
+ handler({
461
+ origin: "https://wallet.tomo.inc",
462
+ data: {
463
+ type: "wallet-ready",
464
+ data: { isAvailable: true, connectedInfo: null },
465
+ },
466
+ } as MessageEvent);
467
+ }
468
+ }, 15);
469
+
470
+ await embeddedWallet.login("token-to-recreate-iframe");
471
+
472
+ expect(existingIframe.remove).toHaveBeenCalled();
473
+ }, 8000);
474
+
475
+ it("should close wallet when wallet-close message received during login", async () => {
476
+ const config = {
477
+ clientId: "close-test",
478
+ walletBaseUrl: "https://wallet.tomo.inc",
479
+ name: "Test",
480
+ logo: "",
481
+ tomoStage: "dev" as const,
482
+ xClientId: "x",
483
+ googleClientId: "g",
484
+ };
485
+
486
+ setTimeout(() => {
487
+ const handler = messageHandlers[0];
488
+ if (handler) {
489
+ handler({
490
+ origin: "https://wallet.tomo.inc",
491
+ data: { type: "wallet-close" },
492
+ } as MessageEvent);
493
+ }
494
+ }, 5);
495
+ setTimeout(() => {
496
+ const handler = messageHandlers[0];
497
+ if (handler) {
498
+ handler({
499
+ origin: "https://wallet.tomo.inc",
500
+ data: {
501
+ type: "wallet-ready",
502
+ data: { isAvailable: false, connectedInfo: null },
503
+ },
504
+ } as MessageEvent);
505
+ }
506
+ }, 15);
507
+
508
+ await embeddedWallet.init(config);
509
+ expect(embeddedWallet.walletIframe?.style.width).toBe("0");
510
+ }, 8000);
511
+
512
+ it("should close wallet when walletCloseHandler receives wallet-close message", async () => {
513
+ const config = {
514
+ clientId: "handler-close-test",
515
+ walletBaseUrl: "https://wallet.tomo.inc",
516
+ name: "Test",
517
+ logo: "",
518
+ tomoStage: "dev" as const,
519
+ xClientId: "x",
520
+ googleClientId: "g",
521
+ };
522
+ setTimeout(() => {
523
+ const handler = messageHandlers[0];
524
+ if (handler) {
525
+ handler({
526
+ origin: "https://wallet.tomo.inc",
527
+ data: {
528
+ type: "wallet-ready",
529
+ data: { isAvailable: true, connectedInfo: null },
530
+ },
531
+ } as MessageEvent);
532
+ }
533
+ }, 10);
534
+ await embeddedWallet.init(config);
535
+
536
+ embeddedWallet.walletIframe = mockIframe;
537
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
538
+ const walletCloseHandler = (embeddedWallet as any).walletCloseHandler as (ev: { origin: string; data: any }) => void;
539
+ walletCloseHandler({
540
+ origin: "https://wallet.tomo.inc",
541
+ data: { type: "wallet-close" },
542
+ });
543
+
544
+ expect(mockIframe.style.width).toBe("0");
545
+ }, 8000);
546
+ });
547
+
548
+ describe("loginByEmail", () => {
549
+ it("should request emailLogin and resolve with oidcToken", async () => {
550
+ embeddedWallet.walletIframe = mockIframe;
551
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
552
+
553
+ const loginPromise = embeddedWallet.loginByEmail({ email: "a@b.com" });
554
+
555
+ await new Promise((r) => setTimeout(r, 25));
556
+ const responseHandler = messageHandlers[1];
557
+ if (responseHandler) {
558
+ responseHandler({
559
+ origin: "https://wallet.tomo.inc",
560
+ data: { type: "wallet-response", data: { oidcToken: "email-oidc-token" } },
561
+ } as MessageEvent);
562
+ }
563
+
564
+ const result = await loginPromise;
565
+ expect(result).toBe("email-oidc-token");
566
+ }, 8000);
567
+
568
+ it("should reject when request fails", async () => {
569
+ embeddedWallet.walletIframe = mockIframe;
570
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
571
+ vi.spyOn(embeddedWallet, "request").mockRejectedValueOnce(new Error("network error"));
572
+
573
+ await expect(embeddedWallet.loginByEmail({ email: "a@b.com" })).rejects.toThrow("network error");
574
+ });
575
+
576
+ it("should resolve with empty string when wallet-close received", async () => {
577
+ embeddedWallet.walletIframe = mockIframe;
578
+ embeddedWallet.walletOrigin = "https://wallet.tomo.inc";
579
+
580
+ const handlers: Array<(e: MessageEvent) => void> = [];
581
+ window.addEventListener = vi.fn((_event: string, handler: any) => {
582
+ handlers.push(handler);
583
+ });
584
+
585
+ const resultPromise = embeddedWallet.loginByEmail({ email: "a@b.com" });
586
+
587
+ await new Promise((r) => setTimeout(r, 25));
588
+ const closeHandler = handlers[0];
589
+ if (closeHandler) {
590
+ closeHandler({
591
+ origin: "https://wallet.tomo.inc",
592
+ data: { type: "wallet-close" },
593
+ } as MessageEvent);
594
+ }
595
+
596
+ const result = await resultPromise;
597
+ expect(result).toBe("");
598
+ }, 8000);
599
+ });
600
+ });
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { sendRequest, onResponse, notPorivderAPIs } from "../hub";
3
+
4
+ // Mock EmbeddedWallet
5
+ vi.mock("../embedded-wallet", () => ({
6
+ EmbeddedWallet: {
7
+ getInstance: vi.fn().mockReturnValue({
8
+ walletOrigin: "https://wallet.tomo.inc",
9
+ walletIframe: {
10
+ contentWindow: {
11
+ postMessage: vi.fn(),
12
+ },
13
+ },
14
+ popup: vi.fn(),
15
+ close: vi.fn(),
16
+ }),
17
+ },
18
+ }));
19
+
20
+ describe("hub", () => {
21
+ beforeEach(() => {
22
+ window.addEventListener = vi.fn();
23
+ window.removeEventListener = vi.fn();
24
+ });
25
+ describe("notPorivderAPIs", () => {
26
+ it("should have correct API flags", () => {
27
+ expect(notPorivderAPIs.keepAlive).toBe(true);
28
+ expect(notPorivderAPIs.wallet_getProviderState).toBe(true);
29
+ expect(notPorivderAPIs.wallet_sendDomainMetadata).toBe(true);
30
+ });
31
+ });
32
+
33
+ describe("sendRequest", () => {
34
+ it("should throw error if chainType is empty", async () => {
35
+ await expect(sendRequest("", { method: "test" })).rejects.toThrow(
36
+ "chainType or method is not allowed to be empty",
37
+ );
38
+ });
39
+
40
+ it("should throw error if method is empty", async () => {
41
+ await expect(sendRequest("evm", { method: "" })).rejects.toThrow(
42
+ "chainType or method is not allowed to be empty",
43
+ );
44
+ });
45
+
46
+ it("should return early for notProviderAPIs", async () => {
47
+ const result = await sendRequest("evm", { method: "keepAlive" });
48
+ expect(result).toBeUndefined();
49
+ });
50
+
51
+ it("should send request to wallet iframe", async () => {
52
+ const { EmbeddedWallet } = await import("../embedded-wallet");
53
+ const mockInstance = EmbeddedWallet.getInstance();
54
+
55
+ await sendRequest("evm", { method: "eth_requestAccounts", params: {} });
56
+
57
+ expect(mockInstance.walletIframe.contentWindow?.postMessage).toHaveBeenCalled();
58
+ });
59
+
60
+ it("should return early when walletOrigin or walletIframe is not set", async () => {
61
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
62
+ const { EmbeddedWallet } = await import("../embedded-wallet");
63
+ vi.mocked(EmbeddedWallet.getInstance).mockReturnValueOnce({
64
+ walletOrigin: "",
65
+ walletIframe: null,
66
+ popup: vi.fn(),
67
+ close: vi.fn(),
68
+ } as any);
69
+
70
+ const result = await sendRequest("evm", { method: "eth_requestAccounts", params: {} });
71
+
72
+ expect(result).toBeUndefined();
73
+ expect(consoleSpy).toHaveBeenCalled();
74
+ consoleSpy.mockRestore();
75
+ });
76
+ });
77
+
78
+ describe("onResponse", () => {
79
+ it("should resolve with success response", async () => {
80
+ let messageHandler: ((e: { origin: string; data: any }) => void) | null = null;
81
+ window.addEventListener = vi.fn((event: string, handler: any) => {
82
+ if (event === "message") messageHandler = handler;
83
+ });
84
+
85
+ const promise = onResponse({ method: "test" });
86
+
87
+ await new Promise((r) => setTimeout(r, 5));
88
+ if (messageHandler) {
89
+ messageHandler({
90
+ origin: "https://wallet.tomo.inc",
91
+ data: {
92
+ type: "dapp-response",
93
+ method: "test",
94
+ success: true,
95
+ data: { result: "success" },
96
+ },
97
+ });
98
+ }
99
+
100
+ const result = await promise;
101
+ expect(result).toBeDefined();
102
+ });
103
+
104
+ it("should reject with error response", async () => {
105
+ let messageHandler: ((e: { origin: string; data: any }) => void) | null = null;
106
+ window.addEventListener = vi.fn((event: string, handler: any) => {
107
+ if (event === "message") messageHandler = handler;
108
+ });
109
+
110
+ const promise = onResponse({ method: "test" });
111
+
112
+ await new Promise((r) => setTimeout(r, 5));
113
+ if (messageHandler) {
114
+ messageHandler({
115
+ origin: "https://wallet.tomo.inc",
116
+ data: {
117
+ type: "dapp-response",
118
+ method: "test",
119
+ success: false,
120
+ data: { error: "test error" },
121
+ },
122
+ });
123
+ }
124
+
125
+ await expect(promise).rejects.toEqual({ error: "test error" });
126
+ });
127
+
128
+ it("should ignore non-dapp-response messages", async () => {
129
+ let messageHandler: ((e: { origin: string; data: any }) => void) | null = null;
130
+ window.addEventListener = vi.fn((event: string, handler: any) => {
131
+ if (event === "message") messageHandler = handler;
132
+ });
133
+
134
+ const promise = onResponse({ method: "test" });
135
+ let resolved = false;
136
+ promise.then(() => {
137
+ resolved = true;
138
+ });
139
+
140
+ await new Promise((r) => setTimeout(r, 5));
141
+ if (messageHandler) {
142
+ messageHandler({
143
+ origin: "https://wallet.tomo.inc",
144
+ data: { type: "other-message", method: "test" },
145
+ });
146
+ }
147
+
148
+ await new Promise((r) => setTimeout(r, 20));
149
+ expect(resolved).toBe(false);
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ describe("embedded-wallet-providers", () => {
4
+ it("should export EmbeddedWallet", async () => {
5
+ const module = await import("../index");
6
+ expect(module.EmbeddedWallet).toBeDefined();
7
+ expect(typeof module.EmbeddedWallet.getInstance).toBe("function");
8
+ });
9
+
10
+ it("should return same instance when getting EmbeddedWallet from index", async () => {
11
+ const { EmbeddedWallet } = await import("../index");
12
+ const a = EmbeddedWallet.getInstance();
13
+ const b = EmbeddedWallet.getInstance();
14
+ expect(a).toBe(b);
15
+ });
16
+
17
+ it("should export InitConfig type", async () => {
18
+ const module = await import("../index");
19
+ expect(module).toBeDefined();
20
+ });
21
+
22
+ it("should export EmailLoginResult type", async () => {
23
+ const module = await import("../index");
24
+ expect(module).toBeDefined();
25
+ });
26
+ });
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ dappPopups,
4
+ evmDappPopups,
5
+ dogeDappPopups,
6
+ solanaDappPopups,
7
+ tronDappPopups,
8
+ btcDappPopups,
9
+ shopDappPopups,
10
+ } from "../relay-route";
11
+ import { ChainTypeEnum } from "@tomo-inc/wallet-utils";
12
+
13
+ describe("relay-route", () => {
14
+ describe("dappPopups", () => {
15
+ it("should have popups for all chain types", () => {
16
+ expect(dappPopups[ChainTypeEnum.EVM]).toBeDefined();
17
+ expect(dappPopups[ChainTypeEnum.DOGECOIN]).toBeDefined();
18
+ expect(dappPopups[ChainTypeEnum.SOLANA]).toBeDefined();
19
+ expect(dappPopups[ChainTypeEnum.TRON]).toBeDefined();
20
+ expect(dappPopups[ChainTypeEnum.BITCOIN]).toBeDefined();
21
+ expect(dappPopups.shop).toBeDefined();
22
+ });
23
+ });
24
+
25
+ describe("evmDappPopups", () => {
26
+ it("should have correct EVM popup routes", () => {
27
+ expect(evmDappPopups.connect).toBe("/dapp/connect");
28
+ expect(evmDappPopups.eth_requestAccounts).toBe("/dapp/connect");
29
+ expect(evmDappPopups.wallet_addEthereumChain).toBe("/dapp-evm/network-add");
30
+ expect(evmDappPopups.personal_sign).toBe("/dapp-evm/message-sign");
31
+ expect(evmDappPopups.eth_sendTransaction).toBe("/dapp-evm/tx-send");
32
+ });
33
+ });
34
+
35
+ describe("dogeDappPopups", () => {
36
+ it("should have correct Dogecoin popup routes", () => {
37
+ expect(dogeDappPopups.connect).toBe("/dapp/connect");
38
+ expect(dogeDappPopups.signMessage).toBe("/dapp-doge/message-sign");
39
+ expect(dogeDappPopups.requestTransaction).toBe("/dapp-doge/tx-send");
40
+ });
41
+ });
42
+
43
+ describe("solanaDappPopups", () => {
44
+ it("should have correct Solana popup routes", () => {
45
+ expect(solanaDappPopups.connect).toBe("/dapp/connect");
46
+ expect(solanaDappPopups.signMessage).toBe("/dapp-solana/message-sign");
47
+ expect(solanaDappPopups.signTransaction).toBe("/dapp-solana/tx-send");
48
+ });
49
+ });
50
+
51
+ describe("tronDappPopups", () => {
52
+ it("should have correct Tron popup routes", () => {
53
+ expect(tronDappPopups.connect).toBe("/dapp/connect");
54
+ expect(tronDappPopups.signMessage).toBe("/dapp-tron/message-sign");
55
+ expect(tronDappPopups.sendTransaction).toBe("/dapp-tron/tx-send");
56
+ });
57
+ });
58
+
59
+ describe("btcDappPopups", () => {
60
+ it("should have correct Bitcoin popup routes", () => {
61
+ expect(btcDappPopups.connect).toBe("/dapp/connect");
62
+ expect(btcDappPopups.signMessage).toBe("/dapp-btc/message-sign");
63
+ expect(btcDappPopups.sendBitcoin).toBe("/dapp-btc/tx-send");
64
+ });
65
+ });
66
+
67
+ describe("shopDappPopups", () => {
68
+ it("should have correct shop popup routes", () => {
69
+ expect(shopDappPopups.connect).toBe("/dapp-shop/dialog");
70
+ expect(shopDappPopups.welcomeDialog).toBe("/dapp-shop/dialog");
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,60 @@
1
+ // Setup file to mock problematic modules before any tests run
2
+ // ALL mocks must be here to prevent module-level code execution
3
+ import { vi } from "vitest";
4
+
5
+ // Mock @okxweb3/crypto-lib FIRST - this is critical to prevent module-level execution
6
+ vi.mock("@okxweb3/crypto-lib", () => ({}));
7
+ vi.mock("@okxweb3/crypto-lib/dist/signutil/schnorr/stark", () => ({}));
8
+ vi.mock("@okxweb3/crypto-lib/dist/signutil/schnorr", () => ({}));
9
+ vi.mock("@okxweb3/crypto-lib/dist/signutil", () => ({}));
10
+
11
+ // Mock @noble/hashes to prevent module-level initialization errors
12
+ vi.mock("@noble/hashes", () => ({
13
+ sha256: vi.fn(),
14
+ sha512: vi.fn(),
15
+ ripemd160: vi.fn(),
16
+ hmac: vi.fn(),
17
+ pbkdf2: vi.fn(),
18
+ scrypt: vi.fn(),
19
+ utils: {
20
+ bytesToHex: vi.fn(),
21
+ hexToBytes: vi.fn(),
22
+ toBytes: vi.fn((input: any) => {
23
+ if (input instanceof Uint8Array) return input;
24
+ if (typeof input === "string") return new TextEncoder().encode(input);
25
+ return new Uint8Array(0);
26
+ }),
27
+ },
28
+ default: {},
29
+ }));
30
+
31
+ // Mock @tomo-inc/inject-providers to prevent crypto-lib initialization
32
+ vi.mock("@tomo-inc/inject-providers", () => ({
33
+ BitcoinProvider: class MockBitcoinProvider {
34
+ constructor() {}
35
+ },
36
+ BtcProvider: class MockBtcProvider {
37
+ constructor() {}
38
+ },
39
+ DogecoinProvider: class MockDogecoinProvider {
40
+ constructor() {}
41
+ },
42
+ EvmProvider: class MockEvmProvider {
43
+ setConnectedStatus = vi.fn();
44
+ constructor() {}
45
+ },
46
+ SolanaProvider: class MockSolanaProvider {
47
+ constructor() {}
48
+ },
49
+ TronProvider: class MockTronProvider {
50
+ constructor() {}
51
+ },
52
+ }));
53
+
54
+ // Mock @tomo-inc/oidc-auth
55
+ vi.mock("@tomo-inc/oidc-auth", () => ({
56
+ OidcAuth: vi.fn().mockReturnValue({
57
+ loginByGoogle: vi.fn().mockResolvedValue("google-token"),
58
+ loginByX: vi.fn().mockResolvedValue("x-token"),
59
+ }),
60
+ }));
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { isMobile, openWindow } from "../utils";
3
+
4
+ describe("utils", () => {
5
+ describe("isMobile", () => {
6
+ it("should detect mobile devices", () => {
7
+ Object.defineProperty(navigator, "userAgent", {
8
+ value: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)",
9
+ writable: true,
10
+ configurable: true,
11
+ });
12
+ expect(isMobile()).toBe(true);
13
+ });
14
+
15
+ it("should return false for desktop", () => {
16
+ Object.defineProperty(navigator, "userAgent", {
17
+ value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
18
+ writable: true,
19
+ configurable: true,
20
+ });
21
+ expect(isMobile()).toBe(false);
22
+ });
23
+ });
24
+
25
+ describe("openWindow", () => {
26
+ it("should open window with default dimensions", () => {
27
+ const mockWindow = { closed: false } as Window;
28
+ window.open = vi.fn().mockReturnValue(mockWindow);
29
+
30
+ Object.defineProperty(window, "innerHeight", { value: 800, writable: true });
31
+ Object.defineProperty(window, "innerWidth", { value: 1200, writable: true });
32
+ Object.defineProperty(window, "screenY", { value: 0, writable: true });
33
+ Object.defineProperty(window, "screenX", { value: 0, writable: true });
34
+
35
+ const result = openWindow({ url: "https://example.com" });
36
+ expect(result).toBe(mockWindow);
37
+ expect(window.open).toHaveBeenCalled();
38
+ });
39
+
40
+ it("should return null when window.open is blocked", () => {
41
+ window.open = vi.fn().mockReturnValue(null);
42
+ Object.defineProperty(window, "innerHeight", { value: 800, writable: true });
43
+ Object.defineProperty(window, "innerWidth", { value: 1200, writable: true });
44
+ Object.defineProperty(window, "screenY", { value: 0, writable: true });
45
+ Object.defineProperty(window, "screenX", { value: 0, writable: true });
46
+
47
+ const result = openWindow({ url: "https://example.com" });
48
+ expect(result).toBeNull();
49
+ });
50
+
51
+ it("should return null when window.open throws", () => {
52
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
53
+ window.open = vi.fn().mockImplementation(() => {
54
+ throw new Error("popup blocked");
55
+ });
56
+ Object.defineProperty(window, "innerHeight", { value: 800, writable: true });
57
+ Object.defineProperty(window, "innerWidth", { value: 1200, writable: true });
58
+ Object.defineProperty(window, "screenY", { value: 0, writable: true });
59
+ Object.defineProperty(window, "screenX", { value: 0, writable: true });
60
+
61
+ const result = openWindow({ url: "https://example.com" });
62
+ expect(result).toBeNull();
63
+ consoleSpy.mockRestore();
64
+ });
65
+ });
66
+ });
@@ -8,7 +8,7 @@ import {
8
8
  import { onResponse, sendRequest } from "./hub";
9
9
 
10
10
  import { OidcAuth } from "@tomo-inc/oidc-auth";
11
- import { ChainTypeEnum, ProviderProtocol, ProviderStandard, cache } from "@tomo-inc/wallet-utils";
11
+ import { ChainTypeEnum, ProviderProtocol, ProviderStandard } from "@tomo-inc/wallet-utils";
12
12
  import {
13
13
  EmbeddedWalletConfig,
14
14
  EmbeddedWalletConnectors,
@@ -151,17 +151,18 @@ export class EmbeddedWallet {
151
151
  if (!config) {
152
152
  throw new Error("config is not initialized");
153
153
  }
154
+ this.oidcToken = oidcToken || "";
154
155
 
155
156
  // cache oidcToken for second login(2nd reason unknown)
156
- const cacheKey = config.clientId + "-cube-oidc-token";
157
- if (oidcToken) {
158
- cache.set(cacheKey, oidcToken, false);
159
- }
160
- this.oidcToken = oidcToken || cache.get(cacheKey) || "";
161
- console.log("embedded wallet login with oidcToken:", config, this.oidcToken);
162
- if (!this.oidcToken) {
163
- console.warn("oidcToken is not found");
164
- }
157
+ // const cacheKey = config.clientId + "-cube-oidc-token";
158
+ // if (oidcToken) {
159
+ // cache.set(cacheKey, oidcToken, false);
160
+ // }
161
+ // this.oidcToken = oidcToken || cache.get(cacheKey) || "";
162
+ // console.log("embedded wallet login with oidcToken:", config, this.oidcToken);
163
+ // if (!this.oidcToken) {
164
+ // console.warn("oidcToken is not found");
165
+ // }
165
166
 
166
167
  return new Promise((resolve, reject) => {
167
168
  const receiveResponse = (event: MessageEvent) => {
@@ -217,7 +218,7 @@ export class EmbeddedWallet {
217
218
  dappOrigin: window.location.origin,
218
219
  tomoStage: stage,
219
220
  tomoClientId: clientId,
220
- oidcToken: this.oidcToken || "",
221
+ oidcToken: this.oidcToken,
221
222
  logo: logo || "",
222
223
  name: name || "",
223
224
  });
@@ -252,7 +253,8 @@ export class EmbeddedWallet {
252
253
  if (!this.walletIframe) {
253
254
  return;
254
255
  }
255
- const baseCssText = "position: fixed; top: 0; left: 0; border: none; width: 100%; height: 100%; background: transparent; background-color: transparent;";
256
+ const baseCssText =
257
+ "position: fixed; top: 0; left: 0; border: none; width: 100%; height: 100%; background: transparent; background-color: transparent;";
256
258
  const maskZIndex = this.maskZIndex;
257
259
  this.walletIframe.style.cssText = `${baseCssText} z-index: ${maskZIndex};`;
258
260
  this.walletIframe.setAttribute("allowTransparency", "true");
@@ -273,6 +275,8 @@ export class EmbeddedWallet {
273
275
  public async logout(): Promise<boolean> {
274
276
  if (this.walletIframe) {
275
277
  this.walletIframe.src = `${this.walletOrigin}#logout=true`;
278
+ const config = this.config;
279
+ config && this.init(config);
276
280
  }
277
281
  window.removeEventListener("message", this.walletCloseHandler);
278
282
  window.removeEventListener("message", this.logoutListener);
package/src/hub.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EmbeddedWallet } from "embedded-wallet";
1
+ import { EmbeddedWallet } from "./embedded-wallet";
2
2
  import { dappPopups } from "./relay-route";
3
3
 
4
4
  export const notPorivderAPIs = {
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "jsdom",
7
+ setupFiles: ["./src/__tests__/setup.ts"],
8
+ coverage: {
9
+ provider: "v8",
10
+ reporter: ["text", "json", "html"],
11
+ exclude: ["node_modules/", "dist/", "**/*.config.*", "**/__tests__/**", "**/*.test.ts", "**/*.spec.ts"],
12
+ },
13
+ },
14
+ });