@zoreal/oauth2-react 0.2.19 → 0.2.21
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 +51 -44
- package/dist/index.cjs +125 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +124 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,10 +89,10 @@ import { ZorealOAuthProvider } from '@zoreal/oauth2-react';
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
```tsx
|
|
92
|
-
// ZorealSignIn.tsx
|
|
93
|
-
import { useZorealLogin } from '@zoreal/oauth2-react';
|
|
92
|
+
// ZorealSignIn.tsx — your own button, styled your way.
|
|
93
|
+
import { useZorealLogin, ZorealMark } from '@zoreal/oauth2-react';
|
|
94
94
|
|
|
95
|
-
function ZorealSignIn() {
|
|
95
|
+
function ZorealSignIn({ onSignedIn }: { onSignedIn: () => void }) {
|
|
96
96
|
// `email` (and profile.name, etc.) are returned from /userinfo on your backend.
|
|
97
97
|
const login = useZorealLogin({
|
|
98
98
|
flow: 'auth-code',
|
|
@@ -102,28 +102,42 @@ function ZorealSignIn() {
|
|
|
102
102
|
// code and verifier plus its client authentication, verifies the ID
|
|
103
103
|
// token's nonce is this one, then reads the email and name from
|
|
104
104
|
// /userinfo. That is where personal data is delivered.
|
|
105
|
-
await fetch('/api/auth/zoreal', {
|
|
105
|
+
const res = await fetch('/api/auth/zoreal', {
|
|
106
106
|
method: 'POST',
|
|
107
107
|
headers: { 'Content-Type': 'application/json' },
|
|
108
108
|
body: JSON.stringify({ code, code_verifier, nonce }),
|
|
109
109
|
});
|
|
110
|
+
if (res.ok) onSignedIn();
|
|
110
111
|
},
|
|
111
112
|
onError: (e) => console.error(e.description ?? e.error),
|
|
112
113
|
onNonOAuthError: (e) => {
|
|
113
|
-
// The holder
|
|
114
|
-
|
|
114
|
+
// The holder declining or ignoring the request, or closing the dialog,
|
|
115
|
+
// is not an error to surface: the button is simply ready again.
|
|
116
|
+
if (e.type === 'request_denied' || e.type === 'request_expired' || e.type === 'popup_closed') return;
|
|
115
117
|
console.error(e.description ?? e.type);
|
|
116
118
|
},
|
|
117
119
|
});
|
|
118
120
|
|
|
119
|
-
|
|
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.
|
|
124
|
+
return (
|
|
125
|
+
<button type="button" onClick={login}>
|
|
126
|
+
<ZorealMark size={22} brand />
|
|
127
|
+
Continue with ZOREAL
|
|
128
|
+
</button>
|
|
129
|
+
);
|
|
120
130
|
}
|
|
121
131
|
```
|
|
122
132
|
|
|
123
|
-
That is the whole integration. When `login()` runs, the provider
|
|
124
|
-
pairing modal on screen
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
That is the whole integration. When `login()` runs on a computer, the provider
|
|
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.
|
|
127
141
|
|
|
128
142
|
## Quick start: the button (no backend, pseudonymous)
|
|
129
143
|
|
|
@@ -251,7 +265,7 @@ What the modal does:
|
|
|
251
265
|
|
|
252
266
|
| | |
|
|
253
267
|
| --- | --- |
|
|
254
|
-
| **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'`. |
|
|
255
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). |
|
|
256
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. |
|
|
257
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. |
|
|
@@ -577,28 +591,26 @@ error path.
|
|
|
577
591
|
|
|
578
592
|
## A complete example
|
|
579
593
|
|
|
580
|
-
A full sign-in component, end to end
|
|
581
|
-
takes
|
|
582
|
-
|
|
583
|
-
|
|
594
|
+
A full sign-in component, end to end, the shape a production auth-code
|
|
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.
|
|
584
600
|
|
|
585
601
|
```tsx
|
|
586
602
|
import { useState } from 'react';
|
|
587
|
-
import { ZorealOAuthProvider, useZorealLogin } from '@zoreal/oauth2-react';
|
|
588
|
-
import type { PairingState } from '@zoreal/oauth2-react';
|
|
603
|
+
import { ZorealOAuthProvider, useZorealLogin, ZorealMark } from '@zoreal/oauth2-react';
|
|
589
604
|
|
|
590
605
|
function ZorealSignIn() {
|
|
591
|
-
const [pairing, setPairing] = useState<PairingState | null>(null);
|
|
592
606
|
const [note, setNote] = useState<string | null>(null);
|
|
593
607
|
|
|
594
608
|
const login = useZorealLogin({
|
|
595
609
|
flow: 'auth-code',
|
|
596
610
|
scope: 'openid email profile.name',
|
|
597
611
|
// acr_values: 'zoreal.live', // request a fresh liveness for a step-up / high-value login
|
|
598
|
-
onPairingStateChange: setPairing,
|
|
599
612
|
|
|
600
613
|
onSuccess: async ({ code, code_verifier, nonce }) => {
|
|
601
|
-
setPairing(null);
|
|
602
614
|
// Post ALL THREE to YOUR backend over TLS. Your backend does the /token
|
|
603
615
|
// exchange with its client authentication, verifies the ID token
|
|
604
616
|
// (ES256 against the JWKS, iss/aud/exp, and this nonce), checks the acr
|
|
@@ -619,38 +631,26 @@ function ZorealSignIn() {
|
|
|
619
631
|
},
|
|
620
632
|
|
|
621
633
|
// An OAuth error from the provider (e.g. a scope not on your allow list).
|
|
622
|
-
onError: (e) =>
|
|
623
|
-
setPairing(null);
|
|
624
|
-
setNote(e.description ?? e.error); // the provider's words, verbatim
|
|
625
|
-
},
|
|
634
|
+
onError: (e) => setNote(e.description ?? e.error), // the provider's words, verbatim
|
|
626
635
|
|
|
627
|
-
// The human outcomes: declined, expired,
|
|
628
|
-
//
|
|
636
|
+
// The human outcomes: declined, expired, the dialog closed. Not faults:
|
|
637
|
+
// the button is ready again. Do not alarm on these.
|
|
629
638
|
onNonOAuthError: (e) => {
|
|
630
|
-
setPairing(null);
|
|
631
639
|
if (e.type === 'request_denied') setNote('Login was declined. Try again when ready.');
|
|
632
640
|
else if (e.type === 'request_expired') setNote('That took too long. Try again.');
|
|
641
|
+
else if (e.type === 'popup_closed') setNote(null);
|
|
633
642
|
else setNote('Something went wrong. Try again.');
|
|
634
643
|
},
|
|
635
644
|
});
|
|
636
645
|
|
|
637
646
|
return (
|
|
638
647
|
<div>
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
<img src={pairing.qrUrl} alt="Log in with ZOREAL" width={200} height={200} />
|
|
646
|
-
<p>
|
|
647
|
-
{pairing.status === 'claimed'
|
|
648
|
-
? 'Approve the login in your ZOREAL ID app.'
|
|
649
|
-
: 'Scan with your phone camera or the ZOREAL ID app.'}
|
|
650
|
-
</p>
|
|
651
|
-
<button onClick={pairing.cancel}>Cancel</button>
|
|
652
|
-
</div>
|
|
653
|
-
)}
|
|
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>
|
|
654
654
|
|
|
655
655
|
{note && <p role="status">{note}</p>}
|
|
656
656
|
</div>
|
|
@@ -659,13 +659,20 @@ function ZorealSignIn() {
|
|
|
659
659
|
|
|
660
660
|
export default function App() {
|
|
661
661
|
return (
|
|
662
|
-
<ZorealOAuthProvider clientId="ast_your_asset_id">
|
|
662
|
+
<ZorealOAuthProvider clientId="ast_your_asset_id" locale="en" theme="auto">
|
|
663
663
|
<ZorealSignIn />
|
|
664
664
|
</ZorealOAuthProvider>
|
|
665
665
|
);
|
|
666
666
|
}
|
|
667
667
|
```
|
|
668
668
|
|
|
669
|
+
On a computer the provider renders the pairing modal for this button; nothing
|
|
670
|
+
here draws a QR. To draw your own instead, see
|
|
671
|
+
[Rendering it yourself](#rendering-it-yourself). On a phone there is no dialog:
|
|
672
|
+
the tap navigates to the provider, the ZOREAL ID app opens, and after the
|
|
673
|
+
approval the app reopens this page, where `useZorealLogin` finishes the sign-in
|
|
674
|
+
and `onSuccess` runs on that fresh page load.
|
|
675
|
+
|
|
669
676
|
**The backend must verify.** This component only starts the flow and forwards a
|
|
670
677
|
code; on its own it proves nothing. The security is your backend exchanging the
|
|
671
678
|
code with its client authentication and verifying the signed ID token — use a
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
PairingModal: () => PairingModal,
|
|
26
26
|
ZorealBusyRing: () => ZorealBusyRing,
|
|
27
27
|
ZorealLogin: () => ZorealLogin,
|
|
28
|
+
ZorealMark: () => ZorealMark,
|
|
28
29
|
ZorealOAuthProvider: () => ZorealOAuthProvider,
|
|
29
30
|
hasGrantedAllScopesZoreal: () => hasGrantedAllScopesZoreal,
|
|
30
31
|
hasGrantedAnyScopeZoreal: () => hasGrantedAnyScopeZoreal,
|
|
@@ -41,7 +42,7 @@ var import_react2 = require("react");
|
|
|
41
42
|
|
|
42
43
|
// src/wire.ts
|
|
43
44
|
var WIRE_VERSION = 1;
|
|
44
|
-
var SDK_VERSION = "0.2.
|
|
45
|
+
var SDK_VERSION = "0.2.21";
|
|
45
46
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
46
47
|
var POLL_INTERVAL_MS = 2e3;
|
|
47
48
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -1396,6 +1397,16 @@ var CSS = `
|
|
|
1396
1397
|
transition: opacity 200ms ease-out;
|
|
1397
1398
|
}
|
|
1398
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
|
+
}
|
|
1399
1410
|
.${PREFIX}-ring-svg rect {
|
|
1400
1411
|
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1401
1412
|
x: 2px;
|
|
@@ -1636,6 +1647,96 @@ var import_react5 = require("react");
|
|
|
1636
1647
|
// src/useZorealLogin.ts
|
|
1637
1648
|
var import_react3 = require("react");
|
|
1638
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
|
+
|
|
1639
1740
|
// src/return.ts
|
|
1640
1741
|
var PREFIX2 = "zoreal:oauth2:return:";
|
|
1641
1742
|
var DONE = "zoreal:oauth2:done:";
|
|
@@ -2100,6 +2201,7 @@ function useZorealFlow(options) {
|
|
|
2100
2201
|
() => () => {
|
|
2101
2202
|
abortRef.current?.abort();
|
|
2102
2203
|
publishRef.current?.(null);
|
|
2204
|
+
releaseRef.current();
|
|
2103
2205
|
},
|
|
2104
2206
|
[]
|
|
2105
2207
|
);
|
|
@@ -2158,10 +2260,21 @@ function useZorealFlow(options) {
|
|
|
2158
2260
|
}
|
|
2159
2261
|
})();
|
|
2160
2262
|
}, [clientId, issuer]);
|
|
2161
|
-
const
|
|
2263
|
+
const releaseRef = (0, import_react3.useRef)(() => {
|
|
2264
|
+
});
|
|
2265
|
+
const login = (0, import_react3.useCallback)((event) => {
|
|
2162
2266
|
const opts = optionsRef.current;
|
|
2267
|
+
const control = controlFrom(event);
|
|
2163
2268
|
const run = async () => {
|
|
2164
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
|
+
};
|
|
2165
2278
|
const controller = new AbortController();
|
|
2166
2279
|
abortRef.current = controller;
|
|
2167
2280
|
const flow = opts.flow;
|
|
@@ -2310,6 +2423,7 @@ function useZorealFlow(options) {
|
|
|
2310
2423
|
setPairing(null);
|
|
2311
2424
|
publishRef.current?.(null);
|
|
2312
2425
|
if (returnId) markReturnDone(returnId);
|
|
2426
|
+
release();
|
|
2313
2427
|
if (flow === "auth-code") {
|
|
2314
2428
|
opts.onCode?.({
|
|
2315
2429
|
code,
|
|
@@ -2334,6 +2448,7 @@ function useZorealFlow(options) {
|
|
|
2334
2448
|
};
|
|
2335
2449
|
opts.onCredential?.(response);
|
|
2336
2450
|
} catch (e) {
|
|
2451
|
+
release();
|
|
2337
2452
|
setPairing(null);
|
|
2338
2453
|
publishRef.current?.(null);
|
|
2339
2454
|
if (e instanceof DOMException && e.name === "AbortError") {
|
|
@@ -2382,9 +2497,9 @@ function useZorealLogin(options) {
|
|
|
2382
2497
|
// src/ring.tsx
|
|
2383
2498
|
var import_react4 = require("react");
|
|
2384
2499
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2385
|
-
var
|
|
2386
|
-
var
|
|
2387
|
-
var
|
|
2500
|
+
var TAIL2 = 0.3;
|
|
2501
|
+
var STACK2 = Array.from({ length: 12 }, (_, i) => 12 - i);
|
|
2502
|
+
var HALO2 = [3, 2, 1];
|
|
2388
2503
|
function ZorealBusyRing({
|
|
2389
2504
|
busy,
|
|
2390
2505
|
radius = 8,
|
|
@@ -2423,7 +2538,7 @@ function ZorealBusyRing({
|
|
|
2423
2538
|
ry: rx,
|
|
2424
2539
|
style: {
|
|
2425
2540
|
strokeDasharray: `calc(var(--zrl-l) * ${len}) calc(var(--zrl-l) * ${1 - len})`,
|
|
2426
|
-
"--zrl-s": `calc(var(--zrl-l) * ${-(
|
|
2541
|
+
"--zrl-s": `calc(var(--zrl-l) * ${-(TAIL2 - len)})`,
|
|
2427
2542
|
opacity: alpha
|
|
2428
2543
|
}
|
|
2429
2544
|
},
|
|
@@ -2440,10 +2555,10 @@ function ZorealBusyRing({
|
|
|
2440
2555
|
children: [
|
|
2441
2556
|
children,
|
|
2442
2557
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: cx("ring-svg"), "aria-hidden": "true", children: [
|
|
2443
|
-
|
|
2444
|
-
(n, i) => layer(`h${n}`, "ring-halo",
|
|
2558
|
+
HALO2.map(
|
|
2559
|
+
(n, i) => layer(`h${n}`, "ring-halo", TAIL2 * n / HALO2.length, `calc(var(--zrl-glow-opacity) * 0.6 / ${n})`, i === 0 ? measureRef : void 0)
|
|
2445
2560
|
),
|
|
2446
|
-
|
|
2561
|
+
STACK2.map((n) => layer(`t${n}`, n <= 2 ? "ring-head" : "ring-tail", TAIL2 * n / STACK2.length, String(1 / n)))
|
|
2447
2562
|
] })
|
|
2448
2563
|
]
|
|
2449
2564
|
}
|
|
@@ -2590,6 +2705,7 @@ function hasGrantedAnyScopeZoreal(response, firstScope, ...restScopes) {
|
|
|
2590
2705
|
PairingModal,
|
|
2591
2706
|
ZorealBusyRing,
|
|
2592
2707
|
ZorealLogin,
|
|
2708
|
+
ZorealMark,
|
|
2593
2709
|
ZorealOAuthProvider,
|
|
2594
2710
|
hasGrantedAllScopesZoreal,
|
|
2595
2711
|
hasGrantedAnyScopeZoreal,
|