@zoreal/oauth2-react 0.2.18 → 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/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.18";
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 {
@@ -1979,6 +2049,7 @@ function generateRequestId() {
1979
2049
  }
1980
2050
 
1981
2051
  // src/useZorealLogin.ts
2052
+ var resumedReturns = /* @__PURE__ */ new Set();
1982
2053
  function useZorealFlow(options) {
1983
2054
  const { clientId, issuer, locale } = useZorealOAuth();
1984
2055
  const [pairing, setPairing] = useState3(null);
@@ -1988,6 +2059,7 @@ function useZorealFlow(options) {
1988
2059
  const abortRef = useRef2(null);
1989
2060
  const optionsRef = useRef2(options);
1990
2061
  optionsRef.current = options;
2062
+ const closedByPerson = useRef2(false);
1991
2063
  useEffect2(
1992
2064
  () => () => {
1993
2065
  abortRef.current?.abort();
@@ -1995,6 +2067,61 @@ function useZorealFlow(options) {
1995
2067
  },
1996
2068
  []
1997
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]);
1998
2125
  const login = useCallback(() => {
1999
2126
  const opts = optionsRef.current;
2000
2127
  const run = async () => {
@@ -2011,8 +2138,23 @@ function useZorealFlow(options) {
2011
2138
  try {
2012
2139
  let code;
2013
2140
  let selectBy = "device";
2141
+ let returnId = null;
2014
2142
  if (useAppLink) {
2015
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;
2016
2158
  const startUrl = sameDeviceStartUrl(issuer, {
2017
2159
  client_id: clientId,
2018
2160
  scope: opts.scope ?? "openid",
@@ -2025,10 +2167,12 @@ function useZorealFlow(options) {
2025
2167
  prompt: opts.prompt,
2026
2168
  locale,
2027
2169
  request_id: requestId,
2028
- origin: window.location.origin
2170
+ origin: window.location.origin,
2171
+ return_to: returnToUrl()
2029
2172
  });
2030
2173
  selectBy = "app_link";
2031
2174
  const cancel = () => {
2175
+ closedByPerson.current = true;
2032
2176
  controller.abort();
2033
2177
  setPairing(null);
2034
2178
  };
@@ -2055,6 +2199,9 @@ function useZorealFlow(options) {
2055
2199
  controller.signal,
2056
2200
  { tolerateUnknownUntil: Date.now() + 15e3 }
2057
2201
  );
2202
+ if (isReturnDone(requestId)) {
2203
+ throw new DOMException("aborted", "AbortError");
2204
+ }
2058
2205
  } else {
2059
2206
  const started = await startPairing(issuer, {
2060
2207
  client_id: clientId,
@@ -2076,6 +2223,7 @@ function useZorealFlow(options) {
2076
2223
  selectBy = "qr";
2077
2224
  const qrRefreshSeconds = qrRefreshSecondsOf(started);
2078
2225
  const cancel = () => {
2226
+ closedByPerson.current = true;
2079
2227
  controller.abort();
2080
2228
  setPairing(null);
2081
2229
  publishRef.current?.(null);
@@ -2125,6 +2273,7 @@ function useZorealFlow(options) {
2125
2273
  }
2126
2274
  setPairing(null);
2127
2275
  publishRef.current?.(null);
2276
+ if (returnId) markReturnDone(returnId);
2128
2277
  if (flow === "auth-code") {
2129
2278
  opts.onCode?.({
2130
2279
  code,
@@ -2151,7 +2300,16 @@ function useZorealFlow(options) {
2151
2300
  } catch (e) {
2152
2301
  setPairing(null);
2153
2302
  publishRef.current?.(null);
2154
- if (e instanceof DOMException && e.name === "AbortError") return;
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
+ }
2155
2313
  if (e instanceof FlowAbandonedError) {
2156
2314
  opts.onNonOAuthError?.(e.reason);
2157
2315
  return;