@zoreal/oauth2-react 0.2.18 → 0.2.20

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.d.cts CHANGED
@@ -254,6 +254,13 @@ declare function ZorealBusyRing({ busy, radius, theme, block, className, style,
254
254
  children: ReactNode;
255
255
  }): react.JSX.Element;
256
256
 
257
+ declare function ZorealMark({ size, brand, className, }: {
258
+ size?: number;
259
+ /** Paint the brand blue instead of inheriting currentColor. */
260
+ brand?: boolean;
261
+ className?: string;
262
+ }): react.JSX.Element;
263
+
257
264
  /**
258
265
  * The pairing modal, rendered by the SDK rather than by every integrator.
259
266
  *
@@ -323,4 +330,4 @@ declare function hasGrantedAnyScopeZoreal(response: Pick<ZorealCodeResponse, 'sc
323
330
  */
324
331
  declare function resolveIntent(intent: LoginIntent | undefined, scope: string | undefined, acrValues: string | readonly string[] | undefined): LoginIntent;
325
332
 
326
- export { type AcrValue, type AuthCodeFlowOptions, type BrowserDirectFlowOptions, DEFAULT_PAIRING_TIMEOUT_MS, type ErrorCode, type LoginIntent, type NonOAuthError, PairingModal, type PairingModalProps, type PairingState, type PairingUI, type SelectBy, type UseZorealAutoLoginOptions, ZorealBusyRing, type ZorealButtonConfiguration, type ZorealCodeResponse, type ZorealCredentialResponse, ZorealLogin, type ZorealLoginProps, type ZorealLoginRequestOptions, type ZorealOAuthContextProps, ZorealOAuthProvider, type ZorealOAuthProviderProps, type ZorealTheme, hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal, resolveIntent, useZorealAutoLogin, useZorealLogin, useZorealOAuth, zorealLogout };
333
+ export { type AcrValue, type AuthCodeFlowOptions, type BrowserDirectFlowOptions, DEFAULT_PAIRING_TIMEOUT_MS, type ErrorCode, type LoginIntent, type NonOAuthError, PairingModal, type PairingModalProps, type PairingState, type PairingUI, type SelectBy, type UseZorealAutoLoginOptions, ZorealBusyRing, type ZorealButtonConfiguration, type ZorealCodeResponse, type ZorealCredentialResponse, ZorealLogin, type ZorealLoginProps, type ZorealLoginRequestOptions, ZorealMark, type ZorealOAuthContextProps, ZorealOAuthProvider, type ZorealOAuthProviderProps, type ZorealTheme, hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal, resolveIntent, useZorealAutoLogin, useZorealLogin, useZorealOAuth, zorealLogout };
package/dist/index.d.ts CHANGED
@@ -254,6 +254,13 @@ declare function ZorealBusyRing({ busy, radius, theme, block, className, style,
254
254
  children: ReactNode;
255
255
  }): react.JSX.Element;
256
256
 
257
+ declare function ZorealMark({ size, brand, className, }: {
258
+ size?: number;
259
+ /** Paint the brand blue instead of inheriting currentColor. */
260
+ brand?: boolean;
261
+ className?: string;
262
+ }): react.JSX.Element;
263
+
257
264
  /**
258
265
  * The pairing modal, rendered by the SDK rather than by every integrator.
259
266
  *
@@ -323,4 +330,4 @@ declare function hasGrantedAnyScopeZoreal(response: Pick<ZorealCodeResponse, 'sc
323
330
  */
324
331
  declare function resolveIntent(intent: LoginIntent | undefined, scope: string | undefined, acrValues: string | readonly string[] | undefined): LoginIntent;
325
332
 
326
- export { type AcrValue, type AuthCodeFlowOptions, type BrowserDirectFlowOptions, DEFAULT_PAIRING_TIMEOUT_MS, type ErrorCode, type LoginIntent, type NonOAuthError, PairingModal, type PairingModalProps, type PairingState, type PairingUI, type SelectBy, type UseZorealAutoLoginOptions, ZorealBusyRing, type ZorealButtonConfiguration, type ZorealCodeResponse, type ZorealCredentialResponse, ZorealLogin, type ZorealLoginProps, type ZorealLoginRequestOptions, type ZorealOAuthContextProps, ZorealOAuthProvider, type ZorealOAuthProviderProps, type ZorealTheme, hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal, resolveIntent, useZorealAutoLogin, useZorealLogin, useZorealOAuth, zorealLogout };
333
+ export { type AcrValue, type AuthCodeFlowOptions, type BrowserDirectFlowOptions, DEFAULT_PAIRING_TIMEOUT_MS, type ErrorCode, type LoginIntent, type NonOAuthError, PairingModal, type PairingModalProps, type PairingState, type PairingUI, type SelectBy, type UseZorealAutoLoginOptions, ZorealBusyRing, type ZorealButtonConfiguration, type ZorealCodeResponse, type ZorealCredentialResponse, ZorealLogin, type ZorealLoginProps, type ZorealLoginRequestOptions, ZorealMark, type ZorealOAuthContextProps, ZorealOAuthProvider, type ZorealOAuthProviderProps, type ZorealTheme, hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal, resolveIntent, useZorealAutoLogin, useZorealLogin, useZorealOAuth, zorealLogout };
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.20";
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;
@@ -2395,6 +2553,7 @@ export {
2395
2553
  PairingModal,
2396
2554
  ZorealBusyRing,
2397
2555
  ZorealLogin,
2556
+ ZorealMark,
2398
2557
  ZorealOAuthProvider,
2399
2558
  hasGrantedAllScopesZoreal,
2400
2559
  hasGrantedAnyScopeZoreal,