@zerohash-sdk/crypto-account-link-react 1.2.6 → 1.3.1

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.js CHANGED
@@ -1,43 +1,68 @@
1
- import m, { useRef as x, useEffect as f } from "react";
2
- const b = {
1
+ import k, { useRef as O, useEffect as h } from "react";
2
+ const m = (t) => {
3
+ if (!t || typeof t != "string")
4
+ return null;
5
+ const e = t.split(".");
6
+ if (e.length < 2)
7
+ return null;
8
+ try {
9
+ const r = e[1].replace(/-/g, "+").replace(/_/g, "/"), u = r + "===".slice(0, (4 - r.length % 4) % 4), i = typeof atob < "u" ? atob(u) : Buffer.from(u, "base64").toString("utf-8"), c = JSON.parse(i)?.payload?.region;
10
+ if (typeof c != "string")
11
+ return null;
12
+ const o = c.toLowerCase();
13
+ return o === "us" || o === "eu" ? o : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }, b = (t, e) => m(e) !== "eu" ? t : t === "cert" ? "eu-cert" : t === "prod" ? "eu-prod" : t;
18
+ var y;
19
+ (function(t) {
20
+ t.NETWORK_ERROR = "network_error", t.AUTH_ERROR = "auth_error", t.NOT_FOUND_ERROR = "not_found_error", t.VALIDATION_ERROR = "validation_error", t.SERVER_ERROR = "server_error", t.CLIENT_ERROR = "client_error", t.UNKNOWN_ERROR = "unknown_error";
21
+ })(y || (y = {}));
22
+ const a = {
3
23
  local: "http://localhost:5173/crypto-account-link-web/index.js",
4
24
  dev: "https://connect-sdk.dev.0hash.com/crypto-account-link-web/index.js",
5
25
  cert: "https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js",
6
- prod: "https://sdk.connect.xyz/crypto-account-link-web/index.js"
7
- }, I = "zerohash-crypto-account-link-script", y = "zerohash-crypto-account-link", R = (e = "prod") => b[e], w = ({
8
- jwt: e,
9
- env: o = "prod",
26
+ prod: "https://sdk.connect.xyz/crypto-account-link-web/index.js",
27
+ "eu-cert": "https://sdk-cdn.cert.zerohash.eu/crypto-account-link-web/index.js",
28
+ "eu-prod": "https://sdk-cdn.zerohash.eu/crypto-account-link-web/index.js"
29
+ }, E = "zerohash-crypto-account-link-script", _ = "zerohash-crypto-account-link", N = (t = "prod", e) => {
30
+ const r = b(t, e);
31
+ return a[r] ?? a[t] ?? a.prod;
32
+ }, T = ({
33
+ jwt: t,
34
+ env: e = "prod",
10
35
  theme: r,
11
- isPayouts: h = !1,
12
- onCompleted: n,
13
- onError: s,
14
- onClose: i,
15
- onLoaded: p,
16
- onExternalAccountCreated: d,
17
- onEvent: l,
18
- ...k
36
+ isPayouts: u = !1,
37
+ onCompleted: i,
38
+ onError: l,
39
+ onClose: c,
40
+ onLoaded: o,
41
+ onExternalAccountCreated: p,
42
+ onEvent: d,
43
+ ...g
19
44
  }) => {
20
- const u = x(null);
21
- return f(() => {
22
- const t = u.current;
23
- t && (r !== void 0 && (t.theme = r), n && (t.onCompleted = n), s && (t.onError = s), i && (t.onClose = i), p && (t.onLoaded = p), d && (t.onExternalAccountCreated = d), l && (t.onEvent = l));
24
- }, [r, n, s, i, p, d, l]), f(() => {
25
- const t = R(o), a = `${I}-${o}`;
26
- if (document.getElementById(a))
45
+ const f = O(null);
46
+ return h(() => {
47
+ const n = f.current;
48
+ n && (r !== void 0 && (n.theme = r), i && (n.onCompleted = i), l && (n.onError = l), c && (n.onClose = c), o && (n.onLoaded = o), p && (n.onExternalAccountCreated = p), d && (n.onEvent = d));
49
+ }, [r, i, l, c, o, p, d]), h(() => {
50
+ const n = N(e, t), R = `${E}-${e}`;
51
+ if (document.getElementById(R))
27
52
  return;
28
- const c = document.createElement("script");
29
- c.id = a, c.src = t, c.type = "module", c.async = !0, c.onerror = () => {
30
- console.error(`Failed to load the script for ${y} from ${o} environment.`);
31
- }, document.head.appendChild(c);
32
- }, [o]), m.createElement(y, {
33
- ref: u,
34
- jwt: e,
35
- env: o,
53
+ const s = document.createElement("script");
54
+ s.id = R, s.src = n, s.type = "module", s.async = !0, s.onerror = () => {
55
+ console.error(`Failed to load the script for ${_} from ${e} environment.`);
56
+ }, document.head.appendChild(s);
57
+ }, [e, t]), k.createElement(_, {
58
+ ref: f,
59
+ jwt: t,
60
+ env: e,
36
61
  theme: r,
37
- "is-payouts": h,
38
- ...k
62
+ "is-payouts": u,
63
+ ...g
39
64
  });
40
65
  };
41
66
  export {
42
- w as CryptoAccountLink
67
+ T as CryptoAccountLink
43
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerohash-sdk/crypto-account-link-react",
3
- "version": "1.2.6",
3
+ "version": "1.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -2,9 +2,111 @@ import { render } from '@testing-library/react';
2
2
 
3
3
  import { CryptoAccountLink } from './crypto-account-link-react';
4
4
 
5
+ // Build a fake JWT whose `payload.region` claim matches the value passed in.
6
+ const makeJwt = (region: 'us' | 'eu' | null): string => {
7
+ const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
8
+ const body = btoa(JSON.stringify(region === null ? { sub: 'test' } : { payload: { region } }));
9
+ return `${header}.${body}.sig`;
10
+ };
11
+
5
12
  describe('CryptoAccountLink', () => {
13
+ afterEach(() => {
14
+ document.head.innerHTML = '';
15
+ });
16
+
6
17
  it('should render successfully', () => {
7
18
  const { baseElement } = render(<CryptoAccountLink jwt="test-token" />);
8
19
  expect(baseElement).toBeTruthy();
9
20
  });
21
+
22
+ describe('script loading', () => {
23
+ it('loads the dev CDN script for dev env', () => {
24
+ render(
25
+ <CryptoAccountLink
26
+ jwt="test-jwt"
27
+ env="dev"
28
+ />
29
+ );
30
+ const script = document.head.querySelector('script');
31
+ expect(script?.src).toBe('https://connect-sdk.dev.0hash.com/crypto-account-link-web/index.js');
32
+ });
33
+
34
+ it('loads the cert CDN script for cert env', () => {
35
+ render(
36
+ <CryptoAccountLink
37
+ jwt="test-jwt"
38
+ env="cert"
39
+ />
40
+ );
41
+ const script = document.head.querySelector('script');
42
+ expect(script?.src).toBe('https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js');
43
+ });
44
+
45
+ it('loads the production CDN script for prod env', () => {
46
+ render(<CryptoAccountLink jwt="test-jwt" />);
47
+ const script = document.head.querySelector('script');
48
+ expect(script?.src).toBe('https://sdk.connect.xyz/crypto-account-link-web/index.js');
49
+ });
50
+
51
+ it('loads the EU cert CDN script when JWT region is "eu" and env is "cert"', () => {
52
+ render(
53
+ <CryptoAccountLink
54
+ jwt={makeJwt('eu')}
55
+ env="cert"
56
+ />
57
+ );
58
+ const script = document.head.querySelector('script');
59
+ expect(script?.src).toBe('https://sdk-cdn.cert.zerohash.eu/crypto-account-link-web/index.js');
60
+ });
61
+
62
+ it('loads the EU prod CDN script when JWT region is "eu" and env is "prod"', () => {
63
+ render(<CryptoAccountLink jwt={makeJwt('eu')} />);
64
+ const script = document.head.querySelector('script');
65
+ expect(script?.src).toBe('https://sdk-cdn.zerohash.eu/crypto-account-link-web/index.js');
66
+ });
67
+
68
+ it('keeps US partners on the original cert host when JWT region is "us"', () => {
69
+ render(
70
+ <CryptoAccountLink
71
+ jwt={makeJwt('us')}
72
+ env="cert"
73
+ />
74
+ );
75
+ const script = document.head.querySelector('script');
76
+ expect(script?.src).toBe('https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js');
77
+ });
78
+
79
+ it('falls back to the US host when the JWT has no region claim', () => {
80
+ render(
81
+ <CryptoAccountLink
82
+ jwt={makeJwt(null)}
83
+ env="cert"
84
+ />
85
+ );
86
+ const script = document.head.querySelector('script');
87
+ expect(script?.src).toBe('https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js');
88
+ });
89
+
90
+ it('falls back to the US host when the JWT is malformed', () => {
91
+ render(
92
+ <CryptoAccountLink
93
+ jwt="not-a-jwt"
94
+ env="cert"
95
+ />
96
+ );
97
+ const script = document.head.querySelector('script');
98
+ expect(script?.src).toBe('https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js');
99
+ });
100
+
101
+ it('falls back to the prod connect.xyz host when env is an unknown string', () => {
102
+ render(
103
+ <CryptoAccountLink
104
+ jwt="test-jwt"
105
+ env={'staging' as unknown as 'prod'}
106
+ />
107
+ );
108
+ const script = document.head.querySelector('script');
109
+ expect(script?.src).toBe('https://sdk.connect.xyz/crypto-account-link-web/index.js');
110
+ });
111
+ });
10
112
  });
@@ -3,13 +3,21 @@
3
3
  CryptoAccountLinkEvent,
4
4
  ErrorPayload,
5
5
  } from '@zerohash/callbacks';
6
+ import { resolveEnvByRegion } from '@zerohash/js-sdk-base';
6
7
  import React, { useEffect, useRef } from 'react';
7
8
 
9
+ // US partners (and JWTs without a region claim) stay on the original
10
+ // connect.xyz CDN — that host has no Cloudflare Rocket Loader, which was the
11
+ // root cause of the SRE-18031 blank-screen incident. EU partners
12
+ // (`region: 'eu'` in the JWT payload) are routed to the EU CDN. The two will
13
+ // be unified once Rocket Loader is dropped on the .com host.
8
14
  const SCRIPT_URLS = {
9
15
  local: 'http://localhost:5173/crypto-account-link-web/index.js',
10
16
  dev: 'https://connect-sdk.dev.0hash.com/crypto-account-link-web/index.js',
11
17
  cert: 'https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js',
12
18
  prod: 'https://sdk.connect.xyz/crypto-account-link-web/index.js',
19
+ 'eu-cert': 'https://sdk-cdn.cert.zerohash.eu/crypto-account-link-web/index.js',
20
+ 'eu-prod': 'https://sdk-cdn.zerohash.eu/crypto-account-link-web/index.js',
13
21
  } as const;
14
22
 
15
23
  const SCRIPT_ID = 'zerohash-crypto-account-link-script';
@@ -34,8 +42,12 @@ type Environment = 'local' | 'dev' | 'cert' | 'prod';
34
42
  */
35
43
  type Theme = 'auto' | 'light' | 'dark';
36
44
 
37
- const getScriptUrl = (env: Environment = 'prod'): string => {
38
- return SCRIPT_URLS[env];
45
+ const getScriptUrl = (env: Environment = 'prod', jwt?: string): string => {
46
+ const effectiveEnv = resolveEnvByRegion(env, jwt) as keyof typeof SCRIPT_URLS;
47
+ // Final fallback to prod guarantees script.src is never undefined — even if
48
+ // a partner passes a garbage env at runtime (TS won't catch it from JS
49
+ // consumers). Matches the IFRAME_URLS pattern in crypto-account-link-web.
50
+ return SCRIPT_URLS[effectiveEnv] ?? SCRIPT_URLS[env] ?? SCRIPT_URLS['prod'];
39
51
  };
40
52
 
41
53
  interface CryptoAccountLinkProps {
@@ -305,7 +317,7 @@ export const CryptoAccountLink: React.FC<CryptoAccountLinkProps> = ({
305
317
 
306
318
  // Load connect script to render the web component.
307
319
  useEffect(() => {
308
- const scriptUrl = getScriptUrl(env);
320
+ const scriptUrl = getScriptUrl(env, jwt);
309
321
  const scriptId = `${SCRIPT_ID}-${env}`;
310
322
 
311
323
  if (document.getElementById(scriptId)) {
@@ -326,7 +338,7 @@ export const CryptoAccountLink: React.FC<CryptoAccountLinkProps> = ({
326
338
  // Note: We don't return a cleanup function to remove the script.
327
339
  // The script defines the custom element globally for the entire application,
328
340
  // so it should remain even if all component instances are unmounted.
329
- }, [env]); // Re-run if environment changes
341
+ }, [env, jwt]); // Re-run if environment or JWT (region) changes
330
342
 
331
343
  return React.createElement(WEB_COMPONENT_TAG, {
332
344
  ref,