@zoreal/oauth2-react 0.2.13 → 0.2.15
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 +23 -1
- package/dist/index.cjs +141 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +138 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,28 @@ scans the QR with their phone and approves in the ZOREAL ID app. On a phone it
|
|
|
148
148
|
skips the QR and opens the app directly. Either way your page just receives
|
|
149
149
|
`onSuccess`.
|
|
150
150
|
|
|
151
|
+
### What it looks like
|
|
152
|
+
|
|
153
|
+
The default: the mark in the brand blue, the label in the page's font, a 12px
|
|
154
|
+
corner. `shape="pill"`, `theme="filled_black"` and `text="verify_with"` are the
|
|
155
|
+
other shapes and words it takes.
|
|
156
|
+
|
|
157
|
+
<p>
|
|
158
|
+
<img src="docs/buttons/continue-with-zoreal.svg" alt="Continue with ZOREAL" height="62">
|
|
159
|
+
<img src="docs/buttons/verify-with-zoreal-id-pill.svg" alt="Verify with ZOREAL ID, pill" height="62">
|
|
160
|
+
<img src="docs/buttons/sign-in-with-zoreal-black.svg" alt="Sign in with ZOREAL, black" height="62">
|
|
161
|
+
</p>
|
|
162
|
+
|
|
163
|
+
While the tap is being answered, on a phone between the tap and the hand-over
|
|
164
|
+
to the app, the button is disabled and the pairing modal's light runs round it:
|
|
165
|
+
|
|
166
|
+
<p>
|
|
167
|
+
<img src="docs/buttons/continue-with-zoreal-busy.svg" alt="Continue with ZOREAL, busy" height="82">
|
|
168
|
+
</p>
|
|
169
|
+
|
|
170
|
+
A site that renders its own button can wrap it in `ZorealBusyRing` for the
|
|
171
|
+
same light; see [the pairing modal](#the-pairing-modal), **Mobile**.
|
|
172
|
+
|
|
151
173
|
### On your backend
|
|
152
174
|
|
|
153
175
|
There is a sibling library for every major backend — use one instead of
|
|
@@ -229,7 +251,7 @@ What the modal does:
|
|
|
229
251
|
|
|
230
252
|
| | |
|
|
231
253
|
| --- | --- |
|
|
232
|
-
| **Mobile** | No QR. The SDK
|
|
254
|
+
| **Mobile** | No QR and no modal. The SDK creates the pairing and sends the tab to the pairing link, which the ZOREAL ID app claims; with no app installed the same link is the page that installs it. That is one round trip after the tap: `ZorealLogin` disables itself and runs a light round its edge for that gap; a site with its own button keeps that button and draws its own busy state from the tap until `onSuccess` or `onError` fires, or wraps it in `ZorealBusyRing` to get the same light (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'`. |
|
|
233
255
|
| **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). |
|
|
234
256
|
| **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. |
|
|
235
257
|
| **Countdown** | Counts down to expiry, turning amber under 20s. Reads the clock each tick rather than decrementing, so a backgrounded tab comes back honest. |
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var index_exports = {};
|
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
DEFAULT_PAIRING_TIMEOUT_MS: () => DEFAULT_PAIRING_TIMEOUT_MS,
|
|
25
25
|
PairingModal: () => PairingModal,
|
|
26
|
+
ZorealBusyRing: () => ZorealBusyRing,
|
|
26
27
|
ZorealLogin: () => ZorealLogin,
|
|
27
28
|
ZorealOAuthProvider: () => ZorealOAuthProvider,
|
|
28
29
|
hasGrantedAllScopesZoreal: () => hasGrantedAllScopesZoreal,
|
|
@@ -40,7 +41,7 @@ var import_react2 = require("react");
|
|
|
40
41
|
|
|
41
42
|
// src/wire.ts
|
|
42
43
|
var WIRE_VERSION = 1;
|
|
43
|
-
var SDK_VERSION = "0.2.
|
|
44
|
+
var SDK_VERSION = "0.2.15";
|
|
44
45
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
45
46
|
var POLL_INTERVAL_MS = 2e3;
|
|
46
47
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -1362,6 +1363,65 @@ var CSS = `
|
|
|
1362
1363
|
}
|
|
1363
1364
|
|
|
1364
1365
|
@keyframes ${PREFIX}-orbit { to { transform: rotate(360deg) } }
|
|
1366
|
+
/* THE BUSY RING. The light of the QR well, around any control that is
|
|
1367
|
+
waiting on the provider: the button on a phone between the tap and the
|
|
1368
|
+
hand-over to the app. The well's sweep is a cone from the centre, which is
|
|
1369
|
+
even on a square and useless on a wide button: it crawls along the long
|
|
1370
|
+
sides and lights two edges at once near the ends. So here the light is a
|
|
1371
|
+
dash on an SVG outline, which moves at one speed the whole way round
|
|
1372
|
+
whatever the shape, drawn with the well's tokens: its colour and head
|
|
1373
|
+
tint, its line width, its halo, its four second lap. pathLength makes the
|
|
1374
|
+
outline 100 units long, so every dash and offset is a percentage of the
|
|
1375
|
+
way round; the three layers share one head at 30 and differ in length
|
|
1376
|
+
and brightness, which is the comet's tail. Shown only while busy. */
|
|
1377
|
+
.${PREFIX}-ring {
|
|
1378
|
+
position: relative;
|
|
1379
|
+
display: inline-flex;
|
|
1380
|
+
vertical-align: middle;
|
|
1381
|
+
--zrl-beam-time: 4s;
|
|
1382
|
+
}
|
|
1383
|
+
.${PREFIX}-ring-svg {
|
|
1384
|
+
position: absolute;
|
|
1385
|
+
inset: -4px;
|
|
1386
|
+
width: calc(100% + 8px);
|
|
1387
|
+
height: calc(100% + 8px);
|
|
1388
|
+
overflow: visible;
|
|
1389
|
+
pointer-events: none;
|
|
1390
|
+
opacity: 0;
|
|
1391
|
+
transition: opacity 200ms ease-out;
|
|
1392
|
+
}
|
|
1393
|
+
.${PREFIX}-ring[data-busy="true"] > .${PREFIX}-ring-svg { opacity: 1; }
|
|
1394
|
+
.${PREFIX}-ring-svg rect {
|
|
1395
|
+
x: 2px;
|
|
1396
|
+
y: 2px;
|
|
1397
|
+
width: calc(100% - 4px);
|
|
1398
|
+
height: calc(100% - 4px);
|
|
1399
|
+
fill: none;
|
|
1400
|
+
stroke: var(--zrl-beam);
|
|
1401
|
+
stroke-width: var(--zrl-beam-line);
|
|
1402
|
+
stroke-linecap: round;
|
|
1403
|
+
animation: ${PREFIX}-dash var(--zrl-beam-time) linear infinite;
|
|
1404
|
+
}
|
|
1405
|
+
.${PREFIX}-ring-tail { stroke-dasharray: 30 70; stroke-dashoffset: 0; opacity: 0.35; }
|
|
1406
|
+
.${PREFIX}-ring-body { stroke-dasharray: 16 84; stroke-dashoffset: -14; opacity: 0.8; }
|
|
1407
|
+
.${PREFIX}-ring-head { stroke-dasharray: 5 95; stroke-dashoffset: -25; stroke: var(--zrl-beam-head); }
|
|
1408
|
+
.${PREFIX}-ring-halo {
|
|
1409
|
+
stroke-dasharray: 30 70;
|
|
1410
|
+
stroke-dashoffset: 0;
|
|
1411
|
+
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1412
|
+
opacity: calc(var(--zrl-glow-opacity) * 0.5);
|
|
1413
|
+
filter: blur(var(--zrl-glow-blur));
|
|
1414
|
+
}
|
|
1415
|
+
@keyframes ${PREFIX}-dash {
|
|
1416
|
+
to { stroke-dashoffset: calc(var(--zrl-dash-start, 0) - 100); }
|
|
1417
|
+
}
|
|
1418
|
+
.${PREFIX}-ring-body { --zrl-dash-start: -14; }
|
|
1419
|
+
.${PREFIX}-ring-head { --zrl-dash-start: -25; }
|
|
1420
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1421
|
+
.${PREFIX}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1422
|
+
.${PREFIX}-ring-halo, .${PREFIX}-ring-head { display: none; }
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1365
1425
|
|
|
1366
1426
|
@media (prefers-reduced-motion: reduce) {
|
|
1367
1427
|
.${PREFIX}-scrim,
|
|
@@ -1570,7 +1630,7 @@ function useZorealOAuth() {
|
|
|
1570
1630
|
}
|
|
1571
1631
|
|
|
1572
1632
|
// src/ZorealLogin.tsx
|
|
1573
|
-
var
|
|
1633
|
+
var import_react5 = require("react");
|
|
1574
1634
|
|
|
1575
1635
|
// src/useZorealLogin.ts
|
|
1576
1636
|
var import_react3 = require("react");
|
|
@@ -1937,8 +1997,45 @@ function useZorealLogin(options) {
|
|
|
1937
1997
|
}).login;
|
|
1938
1998
|
}
|
|
1939
1999
|
|
|
1940
|
-
// src/
|
|
2000
|
+
// src/ring.tsx
|
|
2001
|
+
var import_react4 = require("react");
|
|
1941
2002
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2003
|
+
function ZorealBusyRing({
|
|
2004
|
+
busy,
|
|
2005
|
+
radius = 8,
|
|
2006
|
+
theme = "auto",
|
|
2007
|
+
block = false,
|
|
2008
|
+
className,
|
|
2009
|
+
style,
|
|
2010
|
+
children
|
|
2011
|
+
}) {
|
|
2012
|
+
(0, import_react4.useEffect)(() => {
|
|
2013
|
+
ensureStyles();
|
|
2014
|
+
}, []);
|
|
2015
|
+
const rx = radius + 2;
|
|
2016
|
+
const layer = (name) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { className: cx(name), rx, ry: rx, pathLength: 100 });
|
|
2017
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2018
|
+
"span",
|
|
2019
|
+
{
|
|
2020
|
+
className: className ? `${cx("root")} ${cx("ring")} ${className}` : `${cx("root")} ${cx("ring")}`,
|
|
2021
|
+
"data-theme": theme,
|
|
2022
|
+
"data-busy": busy,
|
|
2023
|
+
style: block ? { display: "flex", ...style } : style,
|
|
2024
|
+
children: [
|
|
2025
|
+
children,
|
|
2026
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: cx("ring-svg"), "aria-hidden": "true", children: [
|
|
2027
|
+
layer("ring-halo"),
|
|
2028
|
+
layer("ring-tail"),
|
|
2029
|
+
layer("ring-body"),
|
|
2030
|
+
layer("ring-head")
|
|
2031
|
+
] })
|
|
2032
|
+
]
|
|
2033
|
+
}
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// src/ZorealLogin.tsx
|
|
2038
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1942
2039
|
var TEXTS = {
|
|
1943
2040
|
continue_with: null,
|
|
1944
2041
|
signin_with: "Sign in with ZOREAL",
|
|
@@ -1947,9 +2044,9 @@ var TEXTS = {
|
|
|
1947
2044
|
verify_with: "Verify with ZOREAL ID"
|
|
1948
2045
|
};
|
|
1949
2046
|
var SIZES = {
|
|
1950
|
-
large: { height:
|
|
1951
|
-
medium: { height:
|
|
1952
|
-
small: { height:
|
|
2047
|
+
large: { height: 50, font: 14, pad: 20, mark: 22, gap: 12, radius: 12 },
|
|
2048
|
+
medium: { height: 42, font: 14, pad: 16, mark: 20, gap: 10, radius: 10 },
|
|
2049
|
+
small: { height: 34, font: 12, pad: 12, mark: 16, gap: 8, radius: 8 }
|
|
1953
2050
|
};
|
|
1954
2051
|
function ZorealLogin(props) {
|
|
1955
2052
|
const {
|
|
@@ -1957,11 +2054,11 @@ function ZorealLogin(props) {
|
|
|
1957
2054
|
onError,
|
|
1958
2055
|
containerProps,
|
|
1959
2056
|
type = "standard",
|
|
1960
|
-
theme = "
|
|
2057
|
+
theme = "outline",
|
|
1961
2058
|
size = "large",
|
|
1962
2059
|
text = "continue_with",
|
|
1963
2060
|
shape = "rectangular",
|
|
1964
|
-
logo_alignment = "
|
|
2061
|
+
logo_alignment = "center",
|
|
1965
2062
|
width,
|
|
1966
2063
|
click_listener,
|
|
1967
2064
|
flow = "browser-direct",
|
|
@@ -1969,21 +2066,36 @@ function ZorealLogin(props) {
|
|
|
1969
2066
|
} = props;
|
|
1970
2067
|
const { locale } = useZorealOAuth();
|
|
1971
2068
|
const label = TEXTS[text] ?? strings(locale).buttonContinue;
|
|
2069
|
+
const [busy, setBusy] = (0, import_react5.useState)(false);
|
|
1972
2070
|
const { login } = useZorealFlow({
|
|
1973
2071
|
...request,
|
|
1974
2072
|
flow,
|
|
1975
|
-
onCredential: flow === "browser-direct" ?
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2073
|
+
onCredential: flow === "browser-direct" ? (r) => {
|
|
2074
|
+
setBusy(false);
|
|
2075
|
+
onSuccess(r);
|
|
2076
|
+
} : void 0,
|
|
2077
|
+
onCode: flow === "auth-code" ? (r) => {
|
|
2078
|
+
setBusy(false);
|
|
2079
|
+
onSuccess(r);
|
|
2080
|
+
} : void 0,
|
|
2081
|
+
onError: (e) => {
|
|
2082
|
+
setBusy(false);
|
|
2083
|
+
onError?.({ type: "unknown", description: e.description ?? e.error });
|
|
2084
|
+
},
|
|
2085
|
+
onNonOAuthError: (e) => {
|
|
2086
|
+
setBusy(false);
|
|
2087
|
+
onError?.(e);
|
|
2088
|
+
}
|
|
1979
2089
|
});
|
|
1980
2090
|
const s = SIZES[size];
|
|
1981
|
-
const
|
|
2091
|
+
const radius = shape === "pill" ? s.height / 2 : shape === "square" ? 4 : s.radius;
|
|
2092
|
+
const brandMark = theme !== "filled";
|
|
2093
|
+
const style = (0, import_react5.useMemo)(
|
|
1982
2094
|
() => ({
|
|
1983
2095
|
display: "inline-flex",
|
|
1984
2096
|
alignItems: "center",
|
|
1985
2097
|
justifyContent: logo_alignment === "center" ? "center" : "flex-start",
|
|
1986
|
-
gap:
|
|
2098
|
+
gap: s.gap,
|
|
1987
2099
|
height: s.height,
|
|
1988
2100
|
padding: `0 ${s.pad}px`,
|
|
1989
2101
|
width,
|
|
@@ -1991,30 +2103,34 @@ function ZorealLogin(props) {
|
|
|
1991
2103
|
fontFamily: "inherit",
|
|
1992
2104
|
fontWeight: 500,
|
|
1993
2105
|
cursor: "pointer",
|
|
1994
|
-
borderRadius:
|
|
1995
|
-
...theme === "outline" ? { background: "
|
|
2106
|
+
borderRadius: radius,
|
|
2107
|
+
...theme === "outline" ? { background: "#ffffff", color: "#16181c", border: "1px solid #e2e4de" } : theme === "filled_black" ? { background: "#111", color: "#fff", border: "1px solid #111" } : { background: "#00b4d9", color: "#fff", border: "1px solid #00b4d9" }
|
|
1996
2108
|
}),
|
|
1997
|
-
[logo_alignment, s,
|
|
2109
|
+
[logo_alignment, s, radius, theme, width]
|
|
1998
2110
|
);
|
|
1999
|
-
return /* @__PURE__ */ (0,
|
|
2111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ...containerProps, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ZorealBusyRing, { busy, radius, theme: theme === "outline" ? "auto" : "light", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2000
2112
|
"button",
|
|
2001
2113
|
{
|
|
2002
2114
|
type: "button",
|
|
2003
|
-
style,
|
|
2115
|
+
style: busy ? { ...style, cursor: "progress" } : style,
|
|
2116
|
+
disabled: busy,
|
|
2117
|
+
"aria-busy": busy,
|
|
2004
2118
|
onClick: () => {
|
|
2119
|
+
if (busy) return;
|
|
2005
2120
|
click_listener?.();
|
|
2121
|
+
setBusy(true);
|
|
2006
2122
|
login();
|
|
2007
2123
|
},
|
|
2008
2124
|
children: [
|
|
2009
|
-
/* @__PURE__ */ (0,
|
|
2125
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ZorealMark, { size: s.mark, brand: brandMark }),
|
|
2010
2126
|
type === "standard" && label
|
|
2011
2127
|
]
|
|
2012
2128
|
}
|
|
2013
|
-
) });
|
|
2129
|
+
) }) });
|
|
2014
2130
|
}
|
|
2015
2131
|
|
|
2016
2132
|
// src/useZorealAutoLogin.ts
|
|
2017
|
-
var
|
|
2133
|
+
var import_react6 = require("react");
|
|
2018
2134
|
function useZorealAutoLogin(options) {
|
|
2019
2135
|
const { login } = useZorealFlow({
|
|
2020
2136
|
flow: "browser-direct",
|
|
@@ -2031,8 +2147,8 @@ function useZorealAutoLogin(options) {
|
|
|
2031
2147
|
},
|
|
2032
2148
|
onNonOAuthError: (e) => options.onError?.(e)
|
|
2033
2149
|
});
|
|
2034
|
-
const fired = (0,
|
|
2035
|
-
(0,
|
|
2150
|
+
const fired = (0, import_react6.useRef)(false);
|
|
2151
|
+
(0, import_react6.useEffect)(() => {
|
|
2036
2152
|
if (options.disabled || fired.current) return;
|
|
2037
2153
|
fired.current = true;
|
|
2038
2154
|
login();
|
|
@@ -2056,6 +2172,7 @@ function hasGrantedAnyScopeZoreal(response, firstScope, ...restScopes) {
|
|
|
2056
2172
|
0 && (module.exports = {
|
|
2057
2173
|
DEFAULT_PAIRING_TIMEOUT_MS,
|
|
2058
2174
|
PairingModal,
|
|
2175
|
+
ZorealBusyRing,
|
|
2059
2176
|
ZorealLogin,
|
|
2060
2177
|
ZorealOAuthProvider,
|
|
2061
2178
|
hasGrantedAllScopesZoreal,
|