@zoreal/oauth2-react 0.2.17 → 0.2.19
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 +2 -1
- package/dist/index.cjs +182 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +182 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createContext, useContext, useMemo, useState as useState2 } from "react
|
|
|
5
5
|
|
|
6
6
|
// src/wire.ts
|
|
7
7
|
var WIRE_VERSION = 1;
|
|
8
|
-
var SDK_VERSION = "0.2.
|
|
8
|
+
var SDK_VERSION = "0.2.19";
|
|
9
9
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
10
10
|
var POLL_INTERVAL_MS = 2e3;
|
|
11
11
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -1600,6 +1600,76 @@ import { useMemo as useMemo2, useState as useState4 } from "react";
|
|
|
1600
1600
|
// src/useZorealLogin.ts
|
|
1601
1601
|
import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
1602
1602
|
|
|
1603
|
+
// src/return.ts
|
|
1604
|
+
var PREFIX2 = "zoreal:oauth2:return:";
|
|
1605
|
+
var DONE = "zoreal:oauth2:done:";
|
|
1606
|
+
var MAX_AGE_MS = 10 * 60 * 1e3;
|
|
1607
|
+
function storage() {
|
|
1608
|
+
try {
|
|
1609
|
+
return typeof localStorage === "undefined" ? null : localStorage;
|
|
1610
|
+
} catch {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
function saveReturnFlow(flow) {
|
|
1615
|
+
try {
|
|
1616
|
+
storage()?.setItem(PREFIX2 + flow.requestId, JSON.stringify(flow));
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
function peekReturnFlow(requestId) {
|
|
1621
|
+
const store = storage();
|
|
1622
|
+
if (!store) return null;
|
|
1623
|
+
const raw = store.getItem(PREFIX2 + requestId);
|
|
1624
|
+
if (!raw) return null;
|
|
1625
|
+
try {
|
|
1626
|
+
const flow = JSON.parse(raw);
|
|
1627
|
+
if (flow.v !== 1 || flow.requestId !== requestId) return null;
|
|
1628
|
+
if (Date.now() - flow.createdAt > MAX_AGE_MS) return null;
|
|
1629
|
+
return flow;
|
|
1630
|
+
} catch {
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
function forgetReturnFlow(requestId) {
|
|
1635
|
+
try {
|
|
1636
|
+
storage()?.removeItem(PREFIX2 + requestId);
|
|
1637
|
+
} catch {
|
|
1638
|
+
}
|
|
1639
|
+
if (pending === requestId) pending = null;
|
|
1640
|
+
}
|
|
1641
|
+
function markReturnDone(requestId) {
|
|
1642
|
+
try {
|
|
1643
|
+
const store = storage();
|
|
1644
|
+
store?.setItem(DONE + requestId, String(Date.now()));
|
|
1645
|
+
store?.removeItem(PREFIX2 + requestId);
|
|
1646
|
+
} catch {
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
function isReturnDone(requestId) {
|
|
1650
|
+
return storage()?.getItem(DONE + requestId) !== null && storage()?.getItem(DONE + requestId) !== void 0;
|
|
1651
|
+
}
|
|
1652
|
+
function returnToUrl() {
|
|
1653
|
+
if (typeof window === "undefined") return void 0;
|
|
1654
|
+
const { href } = window.location;
|
|
1655
|
+
const hash = href.indexOf("#");
|
|
1656
|
+
return hash === -1 ? href : href.slice(0, hash);
|
|
1657
|
+
}
|
|
1658
|
+
var RETURN_MARK = /(?:^|[#&])zoreal_return=([A-Za-z0-9]{32})(?:&|$)/;
|
|
1659
|
+
var pending = null;
|
|
1660
|
+
function pendingReturnId() {
|
|
1661
|
+
if (pending) return pending;
|
|
1662
|
+
if (typeof window === "undefined") return null;
|
|
1663
|
+
const match = RETURN_MARK.exec(window.location.hash);
|
|
1664
|
+
if (!match) return null;
|
|
1665
|
+
pending = match[1];
|
|
1666
|
+
try {
|
|
1667
|
+
window.history.replaceState(window.history.state, "", returnToUrl());
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
return pending;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1603
1673
|
// src/jwt.ts
|
|
1604
1674
|
function unsafeClaims(idToken) {
|
|
1605
1675
|
try {
|
|
@@ -1691,6 +1761,8 @@ var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
1691
1761
|
}, ms);
|
|
1692
1762
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1693
1763
|
});
|
|
1764
|
+
var SETTLE_MS = 1500;
|
|
1765
|
+
var NETWORK_FAILURES_TOLERATED = 4;
|
|
1694
1766
|
async function pollUntilApproved(issuer, requestId, onState, signal, options = {}) {
|
|
1695
1767
|
let last = { status: "pending" };
|
|
1696
1768
|
const emit = (state) => {
|
|
@@ -1719,11 +1791,27 @@ async function pollUntilApproved(issuer, requestId, onState, signal, options = {
|
|
|
1719
1791
|
})().catch(() => {
|
|
1720
1792
|
});
|
|
1721
1793
|
};
|
|
1794
|
+
const settlingUntil = options.tolerateUnknownUntil ?? 0;
|
|
1795
|
+
let networkFailures = 0;
|
|
1722
1796
|
try {
|
|
1797
|
+
if (settlingUntil > Date.now()) await sleep(SETTLE_MS, signal);
|
|
1723
1798
|
for (; ; ) {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1799
|
+
let response;
|
|
1800
|
+
try {
|
|
1801
|
+
response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
|
|
1802
|
+
signal
|
|
1803
|
+
});
|
|
1804
|
+
networkFailures = 0;
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
if (e instanceof DOMException && e.name === "AbortError") throw e;
|
|
1807
|
+
networkFailures += 1;
|
|
1808
|
+
if (settlingUntil > Date.now() || networkFailures <= NETWORK_FAILURES_TOLERATED) {
|
|
1809
|
+
emit({ ...last, status: last.status });
|
|
1810
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
throw e;
|
|
1814
|
+
}
|
|
1727
1815
|
const body = await parseJson(response);
|
|
1728
1816
|
if (response.status === 404 && (options.tolerateUnknownUntil ?? 0) > Date.now()) {
|
|
1729
1817
|
emit({ status: "pending" });
|
|
@@ -1961,6 +2049,7 @@ function generateRequestId() {
|
|
|
1961
2049
|
}
|
|
1962
2050
|
|
|
1963
2051
|
// src/useZorealLogin.ts
|
|
2052
|
+
var resumedReturns = /* @__PURE__ */ new Set();
|
|
1964
2053
|
function useZorealFlow(options) {
|
|
1965
2054
|
const { clientId, issuer, locale } = useZorealOAuth();
|
|
1966
2055
|
const [pairing, setPairing] = useState3(null);
|
|
@@ -1970,6 +2059,7 @@ function useZorealFlow(options) {
|
|
|
1970
2059
|
const abortRef = useRef2(null);
|
|
1971
2060
|
const optionsRef = useRef2(options);
|
|
1972
2061
|
optionsRef.current = options;
|
|
2062
|
+
const closedByPerson = useRef2(false);
|
|
1973
2063
|
useEffect2(
|
|
1974
2064
|
() => () => {
|
|
1975
2065
|
abortRef.current?.abort();
|
|
@@ -1977,6 +2067,61 @@ function useZorealFlow(options) {
|
|
|
1977
2067
|
},
|
|
1978
2068
|
[]
|
|
1979
2069
|
);
|
|
2070
|
+
useEffect2(() => {
|
|
2071
|
+
const id = pendingReturnId();
|
|
2072
|
+
if (!id || resumedReturns.has(id)) return;
|
|
2073
|
+
const saved = peekReturnFlow(id);
|
|
2074
|
+
if (!saved || saved.clientId !== clientId) return;
|
|
2075
|
+
forgetReturnFlow(id);
|
|
2076
|
+
resumedReturns.add(id);
|
|
2077
|
+
const controller = new AbortController();
|
|
2078
|
+
abortRef.current = controller;
|
|
2079
|
+
void (async () => {
|
|
2080
|
+
const opts = optionsRef.current;
|
|
2081
|
+
try {
|
|
2082
|
+
const code = await pollUntilApproved(issuer, id, void 0, controller.signal, {
|
|
2083
|
+
tolerateUnknownUntil: Date.now() + 5e3
|
|
2084
|
+
});
|
|
2085
|
+
if (saved.flow === "auth-code") {
|
|
2086
|
+
opts.onCode?.({
|
|
2087
|
+
code,
|
|
2088
|
+
scope: saved.scope,
|
|
2089
|
+
app_state: saved.appState,
|
|
2090
|
+
code_verifier: saved.verifier,
|
|
2091
|
+
nonce: saved.nonce
|
|
2092
|
+
});
|
|
2093
|
+
} else {
|
|
2094
|
+
const tokens = await exchangeCode(issuer, {
|
|
2095
|
+
code,
|
|
2096
|
+
code_verifier: saved.verifier,
|
|
2097
|
+
client_id: clientId
|
|
2098
|
+
});
|
|
2099
|
+
const claims = unsafeClaims(tokens.id_token);
|
|
2100
|
+
opts.onCredential?.({
|
|
2101
|
+
credential: tokens.id_token,
|
|
2102
|
+
clientId,
|
|
2103
|
+
select_by: "app_link",
|
|
2104
|
+
acr: claims.acr ?? "zoreal.device"
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
markReturnDone(id);
|
|
2108
|
+
} catch (e) {
|
|
2109
|
+
if (e instanceof DOMException && e.name === "AbortError") return;
|
|
2110
|
+
if (e instanceof FlowAbandonedError) {
|
|
2111
|
+
opts.onNonOAuthError?.(e.reason);
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
if (e instanceof OAuthFlowError) {
|
|
2115
|
+
opts.onError?.({ error: e.error, description: e.description });
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
opts.onNonOAuthError?.({
|
|
2119
|
+
type: "unknown",
|
|
2120
|
+
description: e instanceof Error ? e.message : String(e)
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
})();
|
|
2124
|
+
}, [clientId, issuer]);
|
|
1980
2125
|
const login = useCallback(() => {
|
|
1981
2126
|
const opts = optionsRef.current;
|
|
1982
2127
|
const run = async () => {
|
|
@@ -1993,8 +2138,23 @@ function useZorealFlow(options) {
|
|
|
1993
2138
|
try {
|
|
1994
2139
|
let code;
|
|
1995
2140
|
let selectBy = "device";
|
|
2141
|
+
let returnId = null;
|
|
1996
2142
|
if (useAppLink) {
|
|
1997
2143
|
const requestId = generateRequestId();
|
|
2144
|
+
saveReturnFlow({
|
|
2145
|
+
v: 1,
|
|
2146
|
+
issuer,
|
|
2147
|
+
clientId,
|
|
2148
|
+
flow,
|
|
2149
|
+
verifier,
|
|
2150
|
+
nonce,
|
|
2151
|
+
state,
|
|
2152
|
+
scope: opts.scope ?? "openid",
|
|
2153
|
+
appState: opts.app_state,
|
|
2154
|
+
requestId,
|
|
2155
|
+
createdAt: Date.now()
|
|
2156
|
+
});
|
|
2157
|
+
returnId = requestId;
|
|
1998
2158
|
const startUrl = sameDeviceStartUrl(issuer, {
|
|
1999
2159
|
client_id: clientId,
|
|
2000
2160
|
scope: opts.scope ?? "openid",
|
|
@@ -2007,10 +2167,12 @@ function useZorealFlow(options) {
|
|
|
2007
2167
|
prompt: opts.prompt,
|
|
2008
2168
|
locale,
|
|
2009
2169
|
request_id: requestId,
|
|
2010
|
-
origin: window.location.origin
|
|
2170
|
+
origin: window.location.origin,
|
|
2171
|
+
return_to: returnToUrl()
|
|
2011
2172
|
});
|
|
2012
2173
|
selectBy = "app_link";
|
|
2013
2174
|
const cancel = () => {
|
|
2175
|
+
closedByPerson.current = true;
|
|
2014
2176
|
controller.abort();
|
|
2015
2177
|
setPairing(null);
|
|
2016
2178
|
};
|
|
@@ -2037,6 +2199,9 @@ function useZorealFlow(options) {
|
|
|
2037
2199
|
controller.signal,
|
|
2038
2200
|
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
2039
2201
|
);
|
|
2202
|
+
if (isReturnDone(requestId)) {
|
|
2203
|
+
throw new DOMException("aborted", "AbortError");
|
|
2204
|
+
}
|
|
2040
2205
|
} else {
|
|
2041
2206
|
const started = await startPairing(issuer, {
|
|
2042
2207
|
client_id: clientId,
|
|
@@ -2058,6 +2223,7 @@ function useZorealFlow(options) {
|
|
|
2058
2223
|
selectBy = "qr";
|
|
2059
2224
|
const qrRefreshSeconds = qrRefreshSecondsOf(started);
|
|
2060
2225
|
const cancel = () => {
|
|
2226
|
+
closedByPerson.current = true;
|
|
2061
2227
|
controller.abort();
|
|
2062
2228
|
setPairing(null);
|
|
2063
2229
|
publishRef.current?.(null);
|
|
@@ -2107,6 +2273,7 @@ function useZorealFlow(options) {
|
|
|
2107
2273
|
}
|
|
2108
2274
|
setPairing(null);
|
|
2109
2275
|
publishRef.current?.(null);
|
|
2276
|
+
if (returnId) markReturnDone(returnId);
|
|
2110
2277
|
if (flow === "auth-code") {
|
|
2111
2278
|
opts.onCode?.({
|
|
2112
2279
|
code,
|
|
@@ -2133,7 +2300,16 @@ function useZorealFlow(options) {
|
|
|
2133
2300
|
} catch (e) {
|
|
2134
2301
|
setPairing(null);
|
|
2135
2302
|
publishRef.current?.(null);
|
|
2136
|
-
if (e instanceof DOMException && e.name === "AbortError")
|
|
2303
|
+
if (e instanceof DOMException && e.name === "AbortError") {
|
|
2304
|
+
if (closedByPerson.current) {
|
|
2305
|
+
closedByPerson.current = false;
|
|
2306
|
+
opts.onNonOAuthError?.({
|
|
2307
|
+
type: "popup_closed",
|
|
2308
|
+
description: "the sign-in dialog was closed before the holder approved"
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2137
2313
|
if (e instanceof FlowAbandonedError) {
|
|
2138
2314
|
opts.onNonOAuthError?.(e.reason);
|
|
2139
2315
|
return;
|