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

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,68 +1,244 @@
1
- import k, { useRef as O, useEffect as h } from "react";
2
- const m = (t) => {
3
- if (!t || typeof t != "string")
1
+ import { jsxs as A, jsx as h } from "react/jsx-runtime";
2
+ import U, { useEffect as B, useState as T, useCallback as W, useRef as M } from "react";
3
+ const _ = {
4
+ evergreen: "#00381D",
5
+ green: "#CCFFD0",
6
+ offBlack: "#1C1B1C",
7
+ grayLight: "#EBEBE9"
8
+ }, D = ({ onRetry: e }) => A("div", { role: "alert", style: {
9
+ display: "flex",
10
+ flexDirection: "column",
11
+ alignItems: "center",
12
+ gap: "8px",
13
+ maxWidth: "360px",
14
+ margin: "0 auto",
15
+ padding: "32px 24px",
16
+ textAlign: "center",
17
+ fontFamily: "inherit",
18
+ color: _.offBlack,
19
+ background: "#FFFFFF",
20
+ border: `1px solid ${_.grayLight}`,
21
+ borderRadius: "12px"
22
+ }, children: [h("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: h("path", { d: "M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z", stroke: _.evergreen, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }), h("p", { style: { margin: 0, fontSize: "16px", fontWeight: 600 }, children: "Something went wrong" }), h("p", { style: { margin: 0, fontSize: "14px", opacity: 0.75 }, children: "We couldn't load this content. Please check your internet connection and try again." }), h("button", { type: "button", onClick: e, style: {
23
+ marginTop: "8px",
24
+ padding: "8px 20px",
25
+ fontSize: "14px",
26
+ fontWeight: 600,
27
+ fontFamily: "inherit",
28
+ color: _.green,
29
+ background: _.evergreen,
30
+ border: "none",
31
+ borderRadius: "8px",
32
+ cursor: "pointer"
33
+ }, children: "Try again" })] }), V = (e, t) => {
34
+ B(() => {
35
+ const o = e.current;
36
+ if (o)
37
+ for (const [n, r] of Object.entries(t))
38
+ r !== void 0 && (o[n] = r);
39
+ });
40
+ }, P = {
41
+ dev: "https://grafana-faro-collector.dev.0hash.com/collect",
42
+ cert: "https://grafana-faro-collector.cert.zerohash.com/collect",
43
+ prod: "https://grafana-faro-collector.zerohash.com/collect",
44
+ "eu-cert": "https://grafana-faro-collector.cert.zerohash.eu/collect",
45
+ "eu-prod": "https://grafana-faro-collector.zerohash.eu/collect",
46
+ sandbox: "https://grafana-faro-collector.cert.zerohash.com/collect",
47
+ production: "https://grafana-faro-collector.zerohash.com/collect"
48
+ }, J = (e) => {
49
+ if (!e || typeof e != "string")
4
50
  return null;
5
- const e = t.split(".");
6
- if (e.length < 2)
51
+ const t = e.split(".");
52
+ if (t.length < 2)
7
53
  return null;
8
54
  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")
55
+ const o = t[1].replace(/-/g, "+").replace(/_/g, "/"), n = o + "===".slice(0, (4 - o.length % 4) % 4), r = typeof atob < "u" ? atob(n) : Buffer.from(n, "base64").toString("utf-8"), a = JSON.parse(r)?.payload?.region;
56
+ if (typeof a != "string")
11
57
  return null;
12
- const o = c.toLowerCase();
13
- return o === "us" || o === "eu" ? o : null;
58
+ const c = a.toLowerCase();
59
+ return c === "us" || c === "eu" ? c : null;
14
60
  } catch {
15
61
  return null;
16
62
  }
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 = {
63
+ }, I = (e, t) => J(t) !== "eu" ? e : e === "cert" ? "eu-cert" : e === "prod" ? "eu-prod" : e, $ = (e) => {
64
+ if (!e || typeof e != "string")
65
+ return {};
66
+ const t = e.split(".");
67
+ if (t.length < 2)
68
+ return {};
69
+ try {
70
+ const o = t[1].replace(/-/g, "+").replace(/_/g, "/"), n = o + "===".slice(0, (4 - o.length % 4) % 4), r = typeof atob < "u" ? atob(n) : Buffer.from(n, "base64").toString("utf-8"), i = JSON.parse(r), a = i?.payload ?? {}, c = (s) => typeof s == "string" && s.length > 0 ? s : void 0;
71
+ return {
72
+ participantCode: c(a.participant_code),
73
+ platformName: c(a.platform_name),
74
+ platformCode: c(i.platform_code),
75
+ region: c(a.region)
76
+ };
77
+ } catch {
78
+ return {};
79
+ }
80
+ }, L = (e, t, o) => {
81
+ const n = I(t, o);
82
+ return e[n] ?? e[t] ?? e.prod;
83
+ }, K = (e, t, o) => {
84
+ const n = I(t, o);
85
+ return e[n] ?? e[t];
86
+ }, v = () => {
87
+ }, H = () => {
88
+ const e = new Uint8Array(8);
89
+ return globalThis.crypto.getRandomValues(e), Array.from(e, (t) => t.toString(16).padStart(2, "0")).join("");
90
+ }, G = (e, t) => {
91
+ const o = H(), n = globalThis.__ZH_WEB_SDK_VERSION__, r = {};
92
+ e.claims.participantCode && (r.participant_code = e.claims.participantCode), e.claims.platformName && (r.platform_name = e.claims.platformName), e.claims.platformCode && (r.platform_code = e.claims.platformCode), e.claims.region && (r.region = e.claims.region), n && (r.zh_web_sdk_version = n);
93
+ const i = {
94
+ meta: {
95
+ app: { name: e.appName, version: t ?? "unknown", environment: e.env },
96
+ session: { id: o, attributes: r },
97
+ browser: typeof navigator < "u" ? { userAgent: navigator.userAgent } : void 0,
98
+ page: typeof window < "u" ? { url: window.location.origin } : void 0
99
+ },
100
+ logs: [
101
+ {
102
+ message: `Failed to load the script for ${e.webComponentTag} from ${e.env} environment.`,
103
+ level: "error",
104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
105
+ context: {
106
+ web_component: e.webComponentTag,
107
+ script_url: e.scriptUrl,
108
+ reason: e.reason,
109
+ tried_fallback: String(e.triedFallback),
110
+ elapsed_ms: String(e.elapsedMs)
111
+ }
112
+ }
113
+ ]
114
+ };
115
+ return { sessionId: o, payload: i };
116
+ }, Z = (e, t, o) => {
117
+ if (!e || typeof fetch > "u")
118
+ return;
119
+ const { sessionId: n, payload: r } = G(t, o);
120
+ try {
121
+ fetch(e, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/json", "x-faro-session-id": n },
124
+ body: JSON.stringify(r),
125
+ keepalive: !0
126
+ }).catch(() => {
127
+ });
128
+ } catch {
129
+ }
130
+ }, q = (e) => {
131
+ const { webComponentTag: t, appName: o, appVersion: n, scriptUrls: r, fallbackScriptUrls: i, collectorUrls: a, env: c, jwt: s, timeoutMs: m = 15e3, onLoad: y, onError: u } = e;
132
+ if (typeof document > "u")
133
+ return v;
134
+ const d = `${t}-script-${c}`;
135
+ if (customElements.get(t) || document.getElementById(d))
136
+ return v;
137
+ const b = $(s), k = e.report ?? ((p) => Z(a && K(a, c, s), p, n));
138
+ let f = !1, C = 0, x;
139
+ const R = () => {
140
+ x !== void 0 && clearTimeout(x);
141
+ }, S = (p, g, l) => {
142
+ if (f)
143
+ return;
144
+ const j = Math.round(performance.now() - C), N = {
145
+ webComponentTag: t,
146
+ appName: o,
147
+ env: c,
148
+ scriptUrl: g,
149
+ reason: p,
150
+ triedFallback: l,
151
+ elapsedMs: j,
152
+ claims: b
153
+ };
154
+ k(N);
155
+ const w = l ? void 0 : L(i ?? {}, c, s);
156
+ if (w && w !== g) {
157
+ O(w, !0);
158
+ return;
159
+ }
160
+ f = !0, R(), document.getElementById(d)?.remove(), u?.(N);
161
+ }, O = (p, g) => {
162
+ R(), C = performance.now();
163
+ const l = document.createElement("script");
164
+ l.id = d, l.src = p, l.type = "module", l.async = !0, l.onload = () => {
165
+ setTimeout(() => {
166
+ f || (customElements.get(t) ? (f = !0, R(), y?.()) : S("not-defined", p, g));
167
+ }, 0);
168
+ }, l.onerror = () => S("network", p, g), x = setTimeout(() => S("timeout", p, g), m), document.getElementById(d)?.remove(), document.head.appendChild(l);
169
+ }, E = L(r, c, s);
170
+ return E ? (O(E, !1), () => {
171
+ f = !0, R();
172
+ }) : v;
173
+ };
174
+ var z;
175
+ (function(e) {
176
+ e.NETWORK_ERROR = "network_error", e.AUTH_ERROR = "auth_error", e.NOT_FOUND_ERROR = "not_found_error", e.VALIDATION_ERROR = "validation_error", e.SERVER_ERROR = "server_error", e.CLIENT_ERROR = "client_error", e.UNKNOWN_ERROR = "unknown_error";
177
+ })(z || (z = {}));
178
+ const Q = (e) => {
179
+ const { webComponentTag: t, appName: o, appVersion: n, scriptUrls: r, fallbackScriptUrls: i, collectorUrls: a, env: c, jwt: s, timeoutMs: m } = e, [y, u] = T(!1), [d, b] = T(0);
180
+ B(() => (u(!1), q({
181
+ webComponentTag: t,
182
+ appName: o ?? t,
183
+ appVersion: n,
184
+ scriptUrls: r,
185
+ fallbackScriptUrls: i,
186
+ collectorUrls: a ?? P,
187
+ env: c,
188
+ jwt: s,
189
+ timeoutMs: m,
190
+ onError: () => u(!0)
191
+ })), [
192
+ t,
193
+ o,
194
+ n,
195
+ r,
196
+ i,
197
+ a,
198
+ c,
199
+ s,
200
+ m,
201
+ d
202
+ ]);
203
+ const k = W(() => b((f) => f + 1), []);
204
+ return { loadFailed: y, retry: k };
205
+ }, X = {
23
206
  local: "http://localhost:5173/crypto-account-link-web/index.js",
24
207
  dev: "https://connect-sdk.dev.0hash.com/crypto-account-link-web/index.js",
25
208
  cert: "https://sdk.sandbox.connect.xyz/crypto-account-link-web/index.js",
26
209
  prod: "https://sdk.connect.xyz/crypto-account-link-web/index.js",
27
210
  "eu-cert": "https://sdk-cdn.cert.zerohash.eu/crypto-account-link-web/index.js",
28
211
  "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",
35
- theme: r,
36
- isPayouts: u = !1,
37
- onCompleted: i,
38
- onError: l,
39
- onClose: c,
40
- onLoaded: o,
41
- onExternalAccountCreated: p,
42
- onEvent: d,
43
- ...g
212
+ }, F = "zerohash-crypto-account-link", te = ({
213
+ jwt: e,
214
+ env: t = "prod",
215
+ theme: o,
216
+ isPayouts: n = !1,
217
+ onCompleted: r,
218
+ onError: i,
219
+ onClose: a,
220
+ onLoaded: c,
221
+ onExternalAccountCreated: s,
222
+ onEvent: m,
223
+ ...y
44
224
  }) => {
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))
52
- return;
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,
61
- theme: r,
62
- "is-payouts": u,
63
- ...g
225
+ const u = M(null);
226
+ V(u, { theme: o, onCompleted: r, onError: i, onClose: a, onLoaded: c, onExternalAccountCreated: s, onEvent: m });
227
+ const { loadFailed: d, retry: b } = Q({
228
+ webComponentTag: F,
229
+ scriptUrls: X,
230
+ env: t,
231
+ jwt: e
232
+ });
233
+ return d ? /* @__PURE__ */ h(D, { onRetry: b }) : U.createElement(F, {
234
+ ref: u,
235
+ jwt: e,
236
+ env: t,
237
+ theme: o,
238
+ "is-payouts": n,
239
+ ...y
64
240
  });
65
241
  };
66
242
  export {
67
- T as CryptoAccountLink
243
+ te as CryptoAccountLink
68
244
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerohash-sdk/crypto-account-link-react",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -3,8 +3,8 @@
3
3
  CryptoAccountLinkEvent,
4
4
  ErrorPayload,
5
5
  } from '@zerohash/callbacks';
6
- import { resolveEnvByRegion } from '@zerohash/js-sdk-base';
7
- import React, { useEffect, useRef } from 'react';
6
+ import { ScriptLoadFallback, useSyncWebComponentProps, useWebComponentScript } from '@zerohash/react-sdk-base';
7
+ import React, { useRef } from 'react';
8
8
 
9
9
  // US partners (and JWTs without a region claim) stay on the original
10
10
  // connect.xyz CDN — that host has no Cloudflare Rocket Loader, which was the
@@ -20,7 +20,6 @@ const SCRIPT_URLS = {
20
20
  'eu-prod': 'https://sdk-cdn.zerohash.eu/crypto-account-link-web/index.js',
21
21
  } as const;
22
22
 
23
- const SCRIPT_ID = 'zerohash-crypto-account-link-script';
24
23
  const WEB_COMPONENT_TAG = 'zerohash-crypto-account-link';
25
24
 
26
25
  /**
@@ -42,14 +41,6 @@ type Environment = 'local' | 'dev' | 'cert' | 'prod';
42
41
  */
43
42
  type Theme = 'auto' | 'light' | 'dark';
44
43
 
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'];
51
- };
52
-
53
44
  interface CryptoAccountLinkProps {
54
45
  /**
55
46
  * JWT token used for authentication with the Connect Crypto Account Link service.
@@ -302,43 +293,23 @@ export const CryptoAccountLink: React.FC<CryptoAccountLinkProps> = ({
302
293
  }) => {
303
294
  const ref = useRef<CryptoAccountLinkElement | null>(null);
304
295
 
305
- useEffect(() => {
306
- const element = ref.current;
307
- if (!element) return;
308
-
309
- if (theme !== undefined) element.theme = theme;
310
- if (onCompleted) element.onCompleted = onCompleted;
311
- if (onError) element.onError = onError;
312
- if (onClose) element.onClose = onClose;
313
- if (onLoaded) element.onLoaded = onLoaded;
314
- if (onExternalAccountCreated) element.onExternalAccountCreated = onExternalAccountCreated;
315
- if (onEvent) element.onEvent = onEvent;
316
- }, [theme, onCompleted, onError, onClose, onLoaded, onExternalAccountCreated, onEvent]);
317
-
318
- // Load connect script to render the web component.
319
- useEffect(() => {
320
- const scriptUrl = getScriptUrl(env, jwt);
321
- const scriptId = `${SCRIPT_ID}-${env}`;
322
-
323
- if (document.getElementById(scriptId)) {
324
- return; // Script already loaded, do nothing.
325
- }
296
+ // Mirror props/callbacks onto the element (re-attached if the node remounts,
297
+ // e.g. after a successful "Try again" retry).
298
+ useSyncWebComponentProps(ref, { theme, onCompleted, onError, onClose, onLoaded, onExternalAccountCreated, onEvent });
326
299
 
327
- const script = document.createElement('script');
328
- script.id = scriptId;
329
- script.src = scriptUrl;
330
- script.type = 'module';
331
- script.async = true;
332
- script.onerror = () => {
333
- console.error(`Failed to load the script for ${WEB_COMPONENT_TAG} from ${env} environment.`);
334
- };
335
-
336
- document.head.appendChild(script);
300
+ // Load the connect script that registers the web component. The shared hook
301
+ // injects it, reports failures to Faro (this wrapper has no Faro of its own),
302
+ // and surfaces a final failure as a visible fallback instead of a blank screen.
303
+ const { loadFailed, retry } = useWebComponentScript({
304
+ webComponentTag: WEB_COMPONENT_TAG,
305
+ scriptUrls: SCRIPT_URLS,
306
+ env,
307
+ jwt,
308
+ });
337
309
 
338
- // Note: We don't return a cleanup function to remove the script.
339
- // The script defines the custom element globally for the entire application,
340
- // so it should remain even if all component instances are unmounted.
341
- }, [env, jwt]); // Re-run if environment or JWT (region) changes
310
+ if (loadFailed) {
311
+ return <ScriptLoadFallback onRetry={retry} />;
312
+ }
342
313
 
343
314
  return React.createElement(WEB_COMPONENT_TAG, {
344
315
  ref,