@zoreal/oauth2-react 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -67
- package/dist/index.cjs +200 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +199 -102
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,22 +89,15 @@ import { ZorealOAuthProvider } from '@zoreal/oauth2-react';
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
```tsx
|
|
92
|
-
// ZorealSignIn.tsx — your own button,
|
|
93
|
-
import {
|
|
94
|
-
import { useZorealLogin, ZorealBusyRing, ZorealMark } from '@zoreal/oauth2-react';
|
|
92
|
+
// ZorealSignIn.tsx — your own button, styled your way.
|
|
93
|
+
import { useZorealLogin, ZorealMark } from '@zoreal/oauth2-react';
|
|
95
94
|
|
|
96
95
|
function ZorealSignIn({ onSignedIn }: { onSignedIn: () => void }) {
|
|
97
|
-
// Busy from the tap until the flow ends. On a computer that is while the
|
|
98
|
-
// pairing modal is open; on a phone it is the moment between the tap and
|
|
99
|
-
// the hand-over to the ZOREAL ID app. Cleared by every outcome below.
|
|
100
|
-
const [busy, setBusy] = useState(false);
|
|
101
|
-
|
|
102
96
|
// `email` (and profile.name, etc.) are returned from /userinfo on your backend.
|
|
103
97
|
const login = useZorealLogin({
|
|
104
98
|
flow: 'auth-code',
|
|
105
99
|
scope: 'openid email profile.name',
|
|
106
100
|
onSuccess: async ({ code, code_verifier, nonce }) => {
|
|
107
|
-
setBusy(false);
|
|
108
101
|
// Send ALL THREE to your backend over TLS. It calls POST /token with the
|
|
109
102
|
// code and verifier plus its client authentication, verifies the ID
|
|
110
103
|
// token's nonce is this one, then reads the email and name from
|
|
@@ -116,12 +109,8 @@ function ZorealSignIn({ onSignedIn }: { onSignedIn: () => void }) {
|
|
|
116
109
|
});
|
|
117
110
|
if (res.ok) onSignedIn();
|
|
118
111
|
},
|
|
119
|
-
onError: (e) =>
|
|
120
|
-
setBusy(false);
|
|
121
|
-
console.error(e.description ?? e.error);
|
|
122
|
-
},
|
|
112
|
+
onError: (e) => console.error(e.description ?? e.error),
|
|
123
113
|
onNonOAuthError: (e) => {
|
|
124
|
-
setBusy(false);
|
|
125
114
|
// The holder declining or ignoring the request, or closing the dialog,
|
|
126
115
|
// is not an error to surface: the button is simply ready again.
|
|
127
116
|
if (e.type === 'request_denied' || e.type === 'request_expired' || e.type === 'popup_closed') return;
|
|
@@ -129,36 +118,26 @@ function ZorealSignIn({ onSignedIn }: { onSignedIn: () => void }) {
|
|
|
129
118
|
},
|
|
130
119
|
});
|
|
131
120
|
|
|
121
|
+
// Pass the click through as it is. The SDK takes the button from it, holds it
|
|
122
|
+
// busy with the pairing modal's light round it until the login ends, and lets
|
|
123
|
+
// it go on every outcome. No wrapper, no busy state of your own.
|
|
132
124
|
return (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type="button"
|
|
138
|
-
disabled={busy}
|
|
139
|
-
aria-busy={busy}
|
|
140
|
-
onClick={() => {
|
|
141
|
-
if (busy) return;
|
|
142
|
-
setBusy(true);
|
|
143
|
-
login(); // from the click handler itself: on a phone this tap is the navigation
|
|
144
|
-
}}
|
|
145
|
-
>
|
|
146
|
-
<ZorealMark size={22} brand />
|
|
147
|
-
Continue with ZOREAL
|
|
148
|
-
</button>
|
|
149
|
-
</ZorealBusyRing>
|
|
125
|
+
<button type="button" onClick={login}>
|
|
126
|
+
<ZorealMark size={22} brand />
|
|
127
|
+
Continue with ZOREAL
|
|
128
|
+
</button>
|
|
150
129
|
);
|
|
151
130
|
}
|
|
152
131
|
```
|
|
153
132
|
|
|
154
133
|
That is the whole integration. When `login()` runs on a computer, the provider
|
|
155
|
-
puts the pairing modal on screen
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
[The pairing modal](#the-pairing-modal)
|
|
161
|
-
translate, time out or replace it.
|
|
134
|
+
puts the pairing modal on screen and the button you tapped waits, disabled, with
|
|
135
|
+
the light round it. On a phone the tap itself navigates to the provider, which
|
|
136
|
+
opens the ZOREAL ID app; once the person has approved, the app brings them back
|
|
137
|
+
to this page, and the same hook finishes the sign-in and calls your `onSuccess`
|
|
138
|
+
there. So mount this component on the page the sign-in starts from, and expect
|
|
139
|
+
`onSuccess` on a fresh page load. See [The pairing modal](#the-pairing-modal)
|
|
140
|
+
for what it does and how to theme, translate, time out or replace it.
|
|
162
141
|
|
|
163
142
|
## Quick start: the button (no backend, pseudonymous)
|
|
164
143
|
|
|
@@ -286,7 +265,7 @@ What the modal does:
|
|
|
286
265
|
|
|
287
266
|
| | |
|
|
288
267
|
| --- | --- |
|
|
289
|
-
| **Mobile** | No QR and no modal. The tap itself is a navigation: the SDK sends the tab to the provider's `/pair/start` with the pairing's parameters, synchronously from the click, and the provider answers with a redirect to the pairing's universal link, which the ZOREAL ID app claims while the page stays put and polls. A browser hands a link to an app only inside a navigation the person began, which is why nothing is fetched first. With no app installed the same redirect lands on the page that installs it.
|
|
268
|
+
| **Mobile** | No QR and no modal. The tap itself is a navigation: the SDK sends the tab to the provider's `/pair/start` with the pairing's parameters, synchronously from the click, and the provider answers with a redirect to the pairing's universal link, which the ZOREAL ID app claims while the page stays put and polls. A browser hands a link to an app only inside a navigation the person began, which is why nothing is fetched first. With no app installed the same redirect lands on the page that installs it. Pass the click event through to `login` (`onClick={login}`): the SDK takes the button from it, disables it and runs the light round it until the flow ends, then lets it go, on every outcome. `ZorealLogin` does the same for itself. Once the holder has approved, the app reopens your page with the pairing named in the fragment, and the first `useZorealLogin` or `ZorealLogin` on that page finishes the sign-in there, through the same `onSuccess` and `onError`; the tab that was left behind stands down when the returned page finishes first; a site with its own button gets the same by passing the click to `login`; `ZorealBusyRing` remains for a site that wants to drive the light itself (a wrapper: pass `block` for a full-width button, keep `overflow: hidden` off its ancestors, and expect `.parent > button` selectors to stop matching). Force one or the other with `display: 'qr'` / `'link'`. |
|
|
290
269
|
| **Live status** | The copy and the title follow the pairing: waiting for a scan, then waiting for approval once the holder has claimed the code (the spent QR blurs out behind a phone glyph). |
|
|
291
270
|
| **Title** | Says what the scan is for, inferred from the request: "Scan to sign in" for `openid`, `email` and `profile.name`; "Scan to verify your identity" once a document attribute such as `zoreal.age` or `profile.birthdate` is requested; "Scan to prove you are a real human" for `openid` alone with `acr_values: 'zoreal.live'`. Override with `intent`, one of `'sign-in'`, `'identify'`, `'presence'`, when the scope does not say. |
|
|
292
271
|
| **Countdown** | Counts down to expiry, turning amber under 20s. Reads the clock each tick rather than decrementing, so a backgrounded tab comes back honest. |
|
|
@@ -613,18 +592,17 @@ error path.
|
|
|
613
592
|
## A complete example
|
|
614
593
|
|
|
615
594
|
A full sign-in component, end to end, the shape a production auth-code
|
|
616
|
-
integration takes: your own button, busy
|
|
617
|
-
the SDK's pairing modal on a computer and the app hand-over on
|
|
618
|
-
`{ code, code_verifier, nonce }` to your backend on success, the human
|
|
619
|
-
treated as the non-events they are, and the return from the app on a
|
|
620
|
-
handled by the same hook.
|
|
595
|
+
integration takes: your own button, held busy by the SDK from the tap until
|
|
596
|
+
the flow ends, the SDK's pairing modal on a computer and the app hand-over on
|
|
597
|
+
a phone, `{ code, code_verifier, nonce }` to your backend on success, the human
|
|
598
|
+
outcomes treated as the non-events they are, and the return from the app on a
|
|
599
|
+
phone handled by the same hook.
|
|
621
600
|
|
|
622
601
|
```tsx
|
|
623
602
|
import { useState } from 'react';
|
|
624
|
-
import { ZorealOAuthProvider, useZorealLogin,
|
|
603
|
+
import { ZorealOAuthProvider, useZorealLogin, ZorealMark } from '@zoreal/oauth2-react';
|
|
625
604
|
|
|
626
605
|
function ZorealSignIn() {
|
|
627
|
-
const [busy, setBusy] = useState(false);
|
|
628
606
|
const [note, setNote] = useState<string | null>(null);
|
|
629
607
|
|
|
630
608
|
const login = useZorealLogin({
|
|
@@ -633,7 +611,6 @@ function ZorealSignIn() {
|
|
|
633
611
|
// acr_values: 'zoreal.live', // request a fresh liveness for a step-up / high-value login
|
|
634
612
|
|
|
635
613
|
onSuccess: async ({ code, code_verifier, nonce }) => {
|
|
636
|
-
setBusy(false);
|
|
637
614
|
// Post ALL THREE to YOUR backend over TLS. Your backend does the /token
|
|
638
615
|
// exchange with its client authentication, verifies the ID token
|
|
639
616
|
// (ES256 against the JWKS, iss/aud/exp, and this nonce), checks the acr
|
|
@@ -654,15 +631,11 @@ function ZorealSignIn() {
|
|
|
654
631
|
},
|
|
655
632
|
|
|
656
633
|
// An OAuth error from the provider (e.g. a scope not on your allow list).
|
|
657
|
-
onError: (e) =>
|
|
658
|
-
setBusy(false);
|
|
659
|
-
setNote(e.description ?? e.error); // the provider's words, verbatim
|
|
660
|
-
},
|
|
634
|
+
onError: (e) => setNote(e.description ?? e.error), // the provider's words, verbatim
|
|
661
635
|
|
|
662
636
|
// The human outcomes: declined, expired, the dialog closed. Not faults:
|
|
663
637
|
// the button is ready again. Do not alarm on these.
|
|
664
638
|
onNonOAuthError: (e) => {
|
|
665
|
-
setBusy(false);
|
|
666
639
|
if (e.type === 'request_denied') setNote('Login was declined. Try again when ready.');
|
|
667
640
|
else if (e.type === 'request_expired') setNote('That took too long. Try again.');
|
|
668
641
|
else if (e.type === 'popup_closed') setNote(null);
|
|
@@ -672,21 +645,12 @@ function ZorealSignIn() {
|
|
|
672
645
|
|
|
673
646
|
return (
|
|
674
647
|
<div>
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (busy) return;
|
|
682
|
-
setBusy(true);
|
|
683
|
-
login();
|
|
684
|
-
}}
|
|
685
|
-
>
|
|
686
|
-
<ZorealMark size={22} brand />
|
|
687
|
-
Continue with ZOREAL
|
|
688
|
-
</button>
|
|
689
|
-
</ZorealBusyRing>
|
|
648
|
+
{/* The click goes through as it is: the SDK holds this button busy, with
|
|
649
|
+
the light round it, until the login ends. */}
|
|
650
|
+
<button type="button" onClick={login}>
|
|
651
|
+
<ZorealMark size={22} brand />
|
|
652
|
+
Continue with ZOREAL
|
|
653
|
+
</button>
|
|
690
654
|
|
|
691
655
|
{note && <p role="status">{note}</p>}
|
|
692
656
|
</div>
|
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var import_react2 = require("react");
|
|
|
42
42
|
|
|
43
43
|
// src/wire.ts
|
|
44
44
|
var WIRE_VERSION = 1;
|
|
45
|
-
var SDK_VERSION = "0.2.
|
|
45
|
+
var SDK_VERSION = "0.2.22";
|
|
46
46
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
47
47
|
var POLL_INTERVAL_MS = 2e3;
|
|
48
48
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -1397,6 +1397,16 @@ var CSS = `
|
|
|
1397
1397
|
transition: opacity 200ms ease-out;
|
|
1398
1398
|
}
|
|
1399
1399
|
.${PREFIX}-ring[data-busy="true"] > .${PREFIX}-ring-svg { opacity: 1; }
|
|
1400
|
+
/* The same light as an overlay in the document body, placed over a site's
|
|
1401
|
+
own control by the package itself (busy.ts): nothing of the site's
|
|
1402
|
+
markup or CSS is touched, and neither an ancestor's overflow nor a
|
|
1403
|
+
selector on the control's parent is affected. */
|
|
1404
|
+
.${PREFIX}-ring-overlay {
|
|
1405
|
+
position: fixed;
|
|
1406
|
+
display: block;
|
|
1407
|
+
z-index: 2147483000;
|
|
1408
|
+
pointer-events: none;
|
|
1409
|
+
}
|
|
1400
1410
|
.${PREFIX}-ring-svg rect {
|
|
1401
1411
|
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1402
1412
|
x: 2px;
|
|
@@ -1632,11 +1642,101 @@ function useZorealOAuth() {
|
|
|
1632
1642
|
}
|
|
1633
1643
|
|
|
1634
1644
|
// src/ZorealLogin.tsx
|
|
1635
|
-
var
|
|
1645
|
+
var import_react4 = require("react");
|
|
1636
1646
|
|
|
1637
1647
|
// src/useZorealLogin.ts
|
|
1638
1648
|
var import_react3 = require("react");
|
|
1639
1649
|
|
|
1650
|
+
// src/busy.ts
|
|
1651
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
1652
|
+
var TAIL = 0.3;
|
|
1653
|
+
var STACK = Array.from({ length: 12 }, (_, i) => 12 - i);
|
|
1654
|
+
var HALO = [3, 2, 1];
|
|
1655
|
+
function radiusOf(control) {
|
|
1656
|
+
const value = parseFloat(getComputedStyle(control).borderTopLeftRadius);
|
|
1657
|
+
return Number.isFinite(value) ? value : 8;
|
|
1658
|
+
}
|
|
1659
|
+
function holdBusy(control) {
|
|
1660
|
+
if (typeof document === "undefined") return () => {
|
|
1661
|
+
};
|
|
1662
|
+
ensureStyles();
|
|
1663
|
+
const hadDisabled = control.hasAttribute("disabled");
|
|
1664
|
+
const hadBusy = control.getAttribute("aria-busy");
|
|
1665
|
+
if (control instanceof HTMLButtonElement || control instanceof HTMLInputElement) {
|
|
1666
|
+
control.disabled = true;
|
|
1667
|
+
} else {
|
|
1668
|
+
control.setAttribute("aria-disabled", "true");
|
|
1669
|
+
}
|
|
1670
|
+
control.setAttribute("aria-busy", "true");
|
|
1671
|
+
const overlay = document.createElement("div");
|
|
1672
|
+
overlay.className = `${cx("root")} ${cx("ring")} ${cx("ring-overlay")}`;
|
|
1673
|
+
overlay.dataset.theme = "auto";
|
|
1674
|
+
overlay.dataset.busy = "true";
|
|
1675
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
1676
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
1677
|
+
svg.setAttribute("class", cx("ring-svg"));
|
|
1678
|
+
const rx = radiusOf(control) + 2;
|
|
1679
|
+
const layer = (name, len, alpha) => {
|
|
1680
|
+
const rect = document.createElementNS(SVG_NS, "rect");
|
|
1681
|
+
rect.setAttribute("class", cx(name));
|
|
1682
|
+
rect.setAttribute("rx", String(rx));
|
|
1683
|
+
rect.setAttribute("ry", String(rx));
|
|
1684
|
+
rect.style.strokeDasharray = `calc(var(--zrl-l) * ${len}) calc(var(--zrl-l) * ${1 - len})`;
|
|
1685
|
+
rect.style.setProperty("--zrl-s", `calc(var(--zrl-l) * ${-(TAIL - len)})`);
|
|
1686
|
+
rect.style.opacity = alpha;
|
|
1687
|
+
svg.appendChild(rect);
|
|
1688
|
+
return rect;
|
|
1689
|
+
};
|
|
1690
|
+
let measured = null;
|
|
1691
|
+
for (const n of HALO) {
|
|
1692
|
+
const rect = layer("ring-halo", TAIL * n / HALO.length, `calc(var(--zrl-glow-opacity) * 0.6 / ${n})`);
|
|
1693
|
+
measured ?? (measured = rect);
|
|
1694
|
+
}
|
|
1695
|
+
for (const n of STACK) layer(n <= 2 ? "ring-head" : "ring-tail", TAIL * n / STACK.length, String(1 / n));
|
|
1696
|
+
overlay.appendChild(svg);
|
|
1697
|
+
document.body.appendChild(overlay);
|
|
1698
|
+
let frame = 0;
|
|
1699
|
+
const place = () => {
|
|
1700
|
+
frame = 0;
|
|
1701
|
+
const box = control.getBoundingClientRect();
|
|
1702
|
+
overlay.style.left = `${box.left}px`;
|
|
1703
|
+
overlay.style.top = `${box.top}px`;
|
|
1704
|
+
overlay.style.width = `${box.width}px`;
|
|
1705
|
+
overlay.style.height = `${box.height}px`;
|
|
1706
|
+
const length = typeof measured?.getTotalLength === "function" ? measured.getTotalLength() : 0;
|
|
1707
|
+
if (length > 0) overlay.style.setProperty("--zrl-ring-len", `${length}px`);
|
|
1708
|
+
};
|
|
1709
|
+
const schedule = () => {
|
|
1710
|
+
if (!frame) frame = requestAnimationFrame(place);
|
|
1711
|
+
};
|
|
1712
|
+
place();
|
|
1713
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
|
|
1714
|
+
observer?.observe(control);
|
|
1715
|
+
window.addEventListener("scroll", schedule, true);
|
|
1716
|
+
window.addEventListener("resize", schedule);
|
|
1717
|
+
let released = false;
|
|
1718
|
+
return () => {
|
|
1719
|
+
if (released) return;
|
|
1720
|
+
released = true;
|
|
1721
|
+
if (frame) cancelAnimationFrame(frame);
|
|
1722
|
+
observer?.disconnect();
|
|
1723
|
+
window.removeEventListener("scroll", schedule, true);
|
|
1724
|
+
window.removeEventListener("resize", schedule);
|
|
1725
|
+
overlay.remove();
|
|
1726
|
+
if (control instanceof HTMLButtonElement || control instanceof HTMLInputElement) {
|
|
1727
|
+
control.disabled = hadDisabled;
|
|
1728
|
+
} else {
|
|
1729
|
+
control.removeAttribute("aria-disabled");
|
|
1730
|
+
}
|
|
1731
|
+
if (hadBusy === null) control.removeAttribute("aria-busy");
|
|
1732
|
+
else control.setAttribute("aria-busy", hadBusy);
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function controlFrom(event) {
|
|
1736
|
+
const target = event?.currentTarget;
|
|
1737
|
+
return typeof HTMLElement !== "undefined" && target instanceof HTMLElement ? target : null;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1640
1740
|
// src/return.ts
|
|
1641
1741
|
var PREFIX2 = "zoreal:oauth2:return:";
|
|
1642
1742
|
var DONE = "zoreal:oauth2:done:";
|
|
@@ -2101,6 +2201,7 @@ function useZorealFlow(options) {
|
|
|
2101
2201
|
() => () => {
|
|
2102
2202
|
abortRef.current?.abort();
|
|
2103
2203
|
publishRef.current?.(null);
|
|
2204
|
+
releaseRef.current();
|
|
2104
2205
|
},
|
|
2105
2206
|
[]
|
|
2106
2207
|
);
|
|
@@ -2159,10 +2260,21 @@ function useZorealFlow(options) {
|
|
|
2159
2260
|
}
|
|
2160
2261
|
})();
|
|
2161
2262
|
}, [clientId, issuer]);
|
|
2162
|
-
const
|
|
2263
|
+
const releaseRef = (0, import_react3.useRef)(() => {
|
|
2264
|
+
});
|
|
2265
|
+
const login = (0, import_react3.useCallback)((event) => {
|
|
2163
2266
|
const opts = optionsRef.current;
|
|
2267
|
+
const control = controlFrom(event);
|
|
2164
2268
|
const run = async () => {
|
|
2165
2269
|
abortRef.current?.abort();
|
|
2270
|
+
releaseRef.current();
|
|
2271
|
+
releaseRef.current = control ? holdBusy(control) : () => {
|
|
2272
|
+
};
|
|
2273
|
+
const release = () => {
|
|
2274
|
+
releaseRef.current();
|
|
2275
|
+
releaseRef.current = () => {
|
|
2276
|
+
};
|
|
2277
|
+
};
|
|
2166
2278
|
const controller = new AbortController();
|
|
2167
2279
|
abortRef.current = controller;
|
|
2168
2280
|
const flow = opts.flow;
|
|
@@ -2311,6 +2423,7 @@ function useZorealFlow(options) {
|
|
|
2311
2423
|
setPairing(null);
|
|
2312
2424
|
publishRef.current?.(null);
|
|
2313
2425
|
if (returnId) markReturnDone(returnId);
|
|
2426
|
+
release();
|
|
2314
2427
|
if (flow === "auth-code") {
|
|
2315
2428
|
opts.onCode?.({
|
|
2316
2429
|
code,
|
|
@@ -2335,6 +2448,7 @@ function useZorealFlow(options) {
|
|
|
2335
2448
|
};
|
|
2336
2449
|
opts.onCredential?.(response);
|
|
2337
2450
|
} catch (e) {
|
|
2451
|
+
release();
|
|
2338
2452
|
setPairing(null);
|
|
2339
2453
|
publishRef.current?.(null);
|
|
2340
2454
|
if (e instanceof DOMException && e.name === "AbortError") {
|
|
@@ -2380,79 +2494,8 @@ function useZorealLogin(options) {
|
|
|
2380
2494
|
}).login;
|
|
2381
2495
|
}
|
|
2382
2496
|
|
|
2383
|
-
// src/ring.tsx
|
|
2384
|
-
var import_react4 = require("react");
|
|
2385
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2386
|
-
var TAIL = 0.3;
|
|
2387
|
-
var STACK = Array.from({ length: 12 }, (_, i) => 12 - i);
|
|
2388
|
-
var HALO = [3, 2, 1];
|
|
2389
|
-
function ZorealBusyRing({
|
|
2390
|
-
busy,
|
|
2391
|
-
radius = 8,
|
|
2392
|
-
theme = "auto",
|
|
2393
|
-
block = false,
|
|
2394
|
-
className,
|
|
2395
|
-
style,
|
|
2396
|
-
children
|
|
2397
|
-
}) {
|
|
2398
|
-
(0, import_react4.useEffect)(() => {
|
|
2399
|
-
ensureStyles();
|
|
2400
|
-
}, []);
|
|
2401
|
-
const wrapRef = (0, import_react4.useRef)(null);
|
|
2402
|
-
const measureRef = (0, import_react4.useRef)(null);
|
|
2403
|
-
(0, import_react4.useLayoutEffect)(() => {
|
|
2404
|
-
const wrap = wrapRef.current;
|
|
2405
|
-
const rect = measureRef.current;
|
|
2406
|
-
if (!wrap || !rect) return;
|
|
2407
|
-
const measure = () => {
|
|
2408
|
-
const length = rect.getTotalLength();
|
|
2409
|
-
if (length > 0) wrap.style.setProperty("--zrl-ring-len", `${length}px`);
|
|
2410
|
-
};
|
|
2411
|
-
measure();
|
|
2412
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
2413
|
-
const observer = new ResizeObserver(measure);
|
|
2414
|
-
observer.observe(wrap);
|
|
2415
|
-
return () => observer.disconnect();
|
|
2416
|
-
}, [radius]);
|
|
2417
|
-
const rx = radius + 2;
|
|
2418
|
-
const layer = (key, name, len, alpha, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2419
|
-
"rect",
|
|
2420
|
-
{
|
|
2421
|
-
ref,
|
|
2422
|
-
className: cx(name),
|
|
2423
|
-
rx,
|
|
2424
|
-
ry: rx,
|
|
2425
|
-
style: {
|
|
2426
|
-
strokeDasharray: `calc(var(--zrl-l) * ${len}) calc(var(--zrl-l) * ${1 - len})`,
|
|
2427
|
-
"--zrl-s": `calc(var(--zrl-l) * ${-(TAIL - len)})`,
|
|
2428
|
-
opacity: alpha
|
|
2429
|
-
}
|
|
2430
|
-
},
|
|
2431
|
-
key
|
|
2432
|
-
);
|
|
2433
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2434
|
-
"span",
|
|
2435
|
-
{
|
|
2436
|
-
ref: wrapRef,
|
|
2437
|
-
className: className ? `${cx("root")} ${cx("ring")} ${className}` : `${cx("root")} ${cx("ring")}`,
|
|
2438
|
-
"data-theme": theme,
|
|
2439
|
-
"data-busy": busy,
|
|
2440
|
-
style: block ? { display: "flex", ...style } : style,
|
|
2441
|
-
children: [
|
|
2442
|
-
children,
|
|
2443
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: cx("ring-svg"), "aria-hidden": "true", children: [
|
|
2444
|
-
HALO.map(
|
|
2445
|
-
(n, i) => layer(`h${n}`, "ring-halo", TAIL * n / HALO.length, `calc(var(--zrl-glow-opacity) * 0.6 / ${n})`, i === 0 ? measureRef : void 0)
|
|
2446
|
-
),
|
|
2447
|
-
STACK.map((n) => layer(`t${n}`, n <= 2 ? "ring-head" : "ring-tail", TAIL * n / STACK.length, String(1 / n)))
|
|
2448
|
-
] })
|
|
2449
|
-
]
|
|
2450
|
-
}
|
|
2451
|
-
);
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
2497
|
// src/ZorealLogin.tsx
|
|
2455
|
-
var
|
|
2498
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2456
2499
|
var TEXTS = {
|
|
2457
2500
|
continue_with: null,
|
|
2458
2501
|
signin_with: "Sign in with ZOREAL",
|
|
@@ -2483,31 +2526,18 @@ function ZorealLogin(props) {
|
|
|
2483
2526
|
} = props;
|
|
2484
2527
|
const { locale } = useZorealOAuth();
|
|
2485
2528
|
const label = TEXTS[text] ?? strings(locale).buttonContinue;
|
|
2486
|
-
const [busy, setBusy] = (0, import_react5.useState)(false);
|
|
2487
2529
|
const { login } = useZorealFlow({
|
|
2488
2530
|
...request,
|
|
2489
2531
|
flow,
|
|
2490
|
-
onCredential: flow === "browser-direct" ?
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
onCode: flow === "auth-code" ? (r) => {
|
|
2495
|
-
setBusy(false);
|
|
2496
|
-
onSuccess(r);
|
|
2497
|
-
} : void 0,
|
|
2498
|
-
onError: (e) => {
|
|
2499
|
-
setBusy(false);
|
|
2500
|
-
onError?.({ type: "unknown", description: e.description ?? e.error });
|
|
2501
|
-
},
|
|
2502
|
-
onNonOAuthError: (e) => {
|
|
2503
|
-
setBusy(false);
|
|
2504
|
-
onError?.(e);
|
|
2505
|
-
}
|
|
2532
|
+
onCredential: flow === "browser-direct" ? onSuccess : void 0,
|
|
2533
|
+
onCode: flow === "auth-code" ? onSuccess : void 0,
|
|
2534
|
+
onError: (e) => onError?.({ type: "unknown", description: e.description ?? e.error }),
|
|
2535
|
+
onNonOAuthError: (e) => onError?.(e)
|
|
2506
2536
|
});
|
|
2507
2537
|
const s = SIZES[size];
|
|
2508
2538
|
const radius = shape === "pill" ? s.height / 2 : shape === "square" ? 4 : s.radius;
|
|
2509
2539
|
const brandMark = theme !== "filled";
|
|
2510
|
-
const style = (0,
|
|
2540
|
+
const style = (0, import_react4.useMemo)(
|
|
2511
2541
|
() => ({
|
|
2512
2542
|
display: "inline-flex",
|
|
2513
2543
|
alignItems: "center",
|
|
@@ -2525,25 +2555,92 @@ function ZorealLogin(props) {
|
|
|
2525
2555
|
}),
|
|
2526
2556
|
[logo_alignment, s, radius, theme, width]
|
|
2527
2557
|
);
|
|
2528
|
-
return /* @__PURE__ */ (0,
|
|
2558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ...containerProps, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2529
2559
|
"button",
|
|
2530
2560
|
{
|
|
2531
2561
|
type: "button",
|
|
2532
|
-
style
|
|
2533
|
-
|
|
2534
|
-
"aria-busy": busy,
|
|
2535
|
-
onClick: () => {
|
|
2536
|
-
if (busy) return;
|
|
2562
|
+
style,
|
|
2563
|
+
onClick: (event) => {
|
|
2537
2564
|
click_listener?.();
|
|
2538
|
-
|
|
2539
|
-
login();
|
|
2565
|
+
login(event);
|
|
2540
2566
|
},
|
|
2541
2567
|
children: [
|
|
2542
|
-
/* @__PURE__ */ (0,
|
|
2568
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ZorealMark, { size: s.mark, brand: brandMark }),
|
|
2543
2569
|
type === "standard" && label
|
|
2544
2570
|
]
|
|
2545
2571
|
}
|
|
2546
|
-
) })
|
|
2572
|
+
) });
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// src/ring.tsx
|
|
2576
|
+
var import_react5 = require("react");
|
|
2577
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2578
|
+
var TAIL2 = 0.3;
|
|
2579
|
+
var STACK2 = Array.from({ length: 12 }, (_, i) => 12 - i);
|
|
2580
|
+
var HALO2 = [3, 2, 1];
|
|
2581
|
+
function ZorealBusyRing({
|
|
2582
|
+
busy,
|
|
2583
|
+
radius = 8,
|
|
2584
|
+
theme = "auto",
|
|
2585
|
+
block = false,
|
|
2586
|
+
className,
|
|
2587
|
+
style,
|
|
2588
|
+
children
|
|
2589
|
+
}) {
|
|
2590
|
+
(0, import_react5.useEffect)(() => {
|
|
2591
|
+
ensureStyles();
|
|
2592
|
+
}, []);
|
|
2593
|
+
const wrapRef = (0, import_react5.useRef)(null);
|
|
2594
|
+
const measureRef = (0, import_react5.useRef)(null);
|
|
2595
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2596
|
+
const wrap = wrapRef.current;
|
|
2597
|
+
const rect = measureRef.current;
|
|
2598
|
+
if (!wrap || !rect) return;
|
|
2599
|
+
const measure = () => {
|
|
2600
|
+
const length = rect.getTotalLength();
|
|
2601
|
+
if (length > 0) wrap.style.setProperty("--zrl-ring-len", `${length}px`);
|
|
2602
|
+
};
|
|
2603
|
+
measure();
|
|
2604
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2605
|
+
const observer = new ResizeObserver(measure);
|
|
2606
|
+
observer.observe(wrap);
|
|
2607
|
+
return () => observer.disconnect();
|
|
2608
|
+
}, [radius]);
|
|
2609
|
+
const rx = radius + 2;
|
|
2610
|
+
const layer = (key, name, len, alpha, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2611
|
+
"rect",
|
|
2612
|
+
{
|
|
2613
|
+
ref,
|
|
2614
|
+
className: cx(name),
|
|
2615
|
+
rx,
|
|
2616
|
+
ry: rx,
|
|
2617
|
+
style: {
|
|
2618
|
+
strokeDasharray: `calc(var(--zrl-l) * ${len}) calc(var(--zrl-l) * ${1 - len})`,
|
|
2619
|
+
"--zrl-s": `calc(var(--zrl-l) * ${-(TAIL2 - len)})`,
|
|
2620
|
+
opacity: alpha
|
|
2621
|
+
}
|
|
2622
|
+
},
|
|
2623
|
+
key
|
|
2624
|
+
);
|
|
2625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2626
|
+
"span",
|
|
2627
|
+
{
|
|
2628
|
+
ref: wrapRef,
|
|
2629
|
+
className: className ? `${cx("root")} ${cx("ring")} ${className}` : `${cx("root")} ${cx("ring")}`,
|
|
2630
|
+
"data-theme": theme,
|
|
2631
|
+
"data-busy": busy,
|
|
2632
|
+
style: block ? { display: "flex", ...style } : style,
|
|
2633
|
+
children: [
|
|
2634
|
+
children,
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { className: cx("ring-svg"), "aria-hidden": "true", children: [
|
|
2636
|
+
HALO2.map(
|
|
2637
|
+
(n, i) => layer(`h${n}`, "ring-halo", TAIL2 * n / HALO2.length, `calc(var(--zrl-glow-opacity) * 0.6 / ${n})`, i === 0 ? measureRef : void 0)
|
|
2638
|
+
),
|
|
2639
|
+
STACK2.map((n) => layer(`t${n}`, n <= 2 ? "ring-head" : "ring-tail", TAIL2 * n / STACK2.length, String(1 / n)))
|
|
2640
|
+
] })
|
|
2641
|
+
]
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2547
2644
|
}
|
|
2548
2645
|
|
|
2549
2646
|
// src/useZorealAutoLogin.ts
|