@civic/auth 0.5.3 → 0.5.4-beta.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,3 @@
1
- # 0.5.3 UserButton component styling
2
- - fix the UserButton styling to make it look better with darkmode and more consistent with the other buttons.
3
-
4
1
  # 0.5.2 Fix NextJS multiple router refresh bug
5
2
  - fix a bug that was causing the NextJS router to refresh multiple times post-login
6
3
 
@@ -1 +1 @@
1
- {"version":3,"file":"ClientTokenExchangeSessionProvider.d.ts","sourceRoot":"","sources":["../../../src/reactjs/providers/ClientTokenExchangeSessionProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAON,MAAM,OAAO,CAAC;AAIf,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAU1D,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CAC1D,CAAC;AAcF,QAAA,MAAM,iCAAiC,yDACkC,CAAC;AAE1E,KAAK,qCAAqC,GAAG;IAC3C,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,QAAA,MAAM,kCAAkC,kBAErC,qCAAqC,qDAsLvC,CAAC;AAEF,YAAY,EAAE,qCAAqC,IAAI,kBAAkB,EAAE,CAAC;AAC5E,OAAO,EACL,kCAAkC,EAClC,iCAAiC,GAClC,CAAC"}
1
+ {"version":3,"file":"ClientTokenExchangeSessionProvider.d.ts","sourceRoot":"","sources":["../../../src/reactjs/providers/ClientTokenExchangeSessionProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAON,MAAM,OAAO,CAAC;AAIf,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAU1D,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CAC1D,CAAC;AAcF,QAAA,MAAM,iCAAiC,yDACkC,CAAC;AAE1E,KAAK,qCAAqC,GAAG;IAC3C,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,QAAA,MAAM,kCAAkC,kBAErC,qCAAqC,qDAiMvC,CAAC;AAEF,YAAY,EAAE,qCAAqC,IAAI,kBAAkB,EAAE,CAAC;AAC5E,OAAO,EACL,kCAAkC,EAClC,iCAAiC,GAClC,CAAC"}
@@ -68,10 +68,16 @@ const ClientTokenExchangeSessionProvider = ({ children, }) => {
68
68
  processedCodes.current.add(exchangeKey);
69
69
  setIsLoading(true);
70
70
  await authService.tokenExchange(code, state);
71
+ // Explicitly emit completion event after successful token exchange
72
+ LocalStorageAdapter.emitter.emit("civic-auth-signin-complete");
71
73
  }
72
74
  catch (error) {
73
75
  setError(error);
74
76
  setSession({ authenticated: false });
77
+ // Emit error event if exchange fails
78
+ LocalStorageAdapter.emitter.emit("civic-auth-signin-error", {
79
+ error,
80
+ });
75
81
  }
76
82
  setIsLoading(false);
77
83
  }
@@ -115,7 +121,9 @@ const ClientTokenExchangeSessionProvider = ({ children, }) => {
115
121
  // The AbortController is mainly used to prevent token exchange after unmount
116
122
  const abortController = new AbortController();
117
123
  const onPageLoad = async () => {
118
- if (validationInProgress || authStatus === AuthStatus.SIGNING_OUT)
124
+ if (validationInProgress ||
125
+ authStatus === AuthStatus.SIGNING_OUT ||
126
+ authStatus === AuthStatus.AUTHENTICATING)
119
127
  return;
120
128
  // if we have existing tokens, then validate them and return the session data
121
129
  // otherwise check if we have a code in the url and exchange it for tokens
@@ -1 +1 @@
1
- {"version":3,"file":"ClientTokenExchangeSessionProvider.js","sourceRoot":"","sources":["../../../src/reactjs/providers/ClientTokenExchangeSessionProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport type { ReactNode } from \"react\";\nimport React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n useRef,\n} from \"react\";\nimport { BrowserAuthenticationService } from \"@/services/AuthenticationService.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\nimport { isWindowInIframe } from \"@/lib/windowUtil.js\";\nimport { AuthStatus, type SessionData } from \"@/types.js\";\nimport {\n useCurrentUrl,\n useCivicAuthConfig,\n useSignIn,\n useIframe,\n} from \"@/shared/hooks/index.js\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport { getIframeRef } from \"@/shared/lib/iframeUtils.js\";\n\nexport type ClientTokenExchangeSessionProviderOutput = {\n data: SessionData | null;\n error: Error | null;\n isLoading: boolean;\n doTokenExchange: null | ((url: string) => Promise<void>);\n};\nconst defaultSession: ClientTokenExchangeSessionProviderOutput = {\n data: {\n authenticated: false,\n idToken: undefined,\n accessToken: undefined,\n displayMode: \"iframe\",\n },\n error: null,\n isLoading: false,\n doTokenExchange: null,\n};\n\n// Context for exposing session specifically to the TokenProvider\nconst ClientTokenExchangeSessionContext =\n createContext<ClientTokenExchangeSessionProviderOutput>(defaultSession);\n\ntype ClientTokenExchangeSessionContextType = {\n children: ReactNode;\n};\n\nconst ClientTokenExchangeSessionProvider = ({\n children,\n}: ClientTokenExchangeSessionContextType) => {\n const authConfig = useCivicAuthConfig();\n const [authService, setAuthService] = useState<AuthenticationResolver>();\n const [error, setError] = useState<Error | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [session, setSession] = useState<SessionData | null>(null);\n const { iframeRef } = useIframe();\n const currentUrl = useCurrentUrl();\n // Use the signIn hook with iframe displayMode\n const { startSignIn, authStatus } = useSignIn({\n displayMode: authConfig?.displayMode || \"iframe\",\n });\n\n // Add a ref to track processed codes\n const processedCodes = useRef(new Set<string>());\n\n useEffect(() => {\n if (!currentUrl || !authConfig) return;\n\n const {\n redirectUrl,\n clientId,\n oauthServer,\n scopes,\n logoutRedirectUrl,\n logoutUrl,\n } = authConfig;\n BrowserAuthenticationService.build({\n clientId,\n redirectUrl,\n logoutRedirectUrl,\n logoutUrl,\n oauthServer,\n scopes,\n displayMode: \"iframe\",\n }).then(setAuthService);\n }, [currentUrl, authConfig]);\n\n const isInIframe = isWindowInIframe(globalThis.window);\n\n const doTokenExchange = useCallback(\n async (inUrl: string) => {\n if (!authService) return;\n const url = new URL(inUrl);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n\n // Create a unique key for this code/state combination\n const exchangeKey = `${code}:${state}`;\n\n // If we've already processed this code, skip it\n if (processedCodes.current.has(exchangeKey)) {\n console.log(\"Token exchange already processed for this code\");\n return;\n }\n\n if (code && state) {\n try {\n // Mark this code as processed before starting the exchange\n processedCodes.current.add(exchangeKey);\n setIsLoading(true);\n await authService.tokenExchange(code, state);\n } catch (error) {\n setError(error as Error);\n setSession({ authenticated: false });\n }\n setIsLoading(false);\n }\n },\n [authService],\n );\n\n const onSignIn = useCallback(async () => {\n if (!authService) return;\n const session = await authService.getSessionData();\n setSession(session);\n }, [authService]);\n\n const onSignOut = useCallback(() => {\n setSession(null);\n }, []);\n\n useEffect(() => {\n LocalStorageAdapter.emitter.on(\"signIn\", onSignIn);\n LocalStorageAdapter.emitter.on(\"signOut\", onSignOut);\n return () => {\n LocalStorageAdapter.emitter.off(\"signIn\", onSignIn);\n LocalStorageAdapter.emitter.off(\"signOut\", onSignOut);\n };\n }, [onSignIn, onSignOut]);\n\n useEffect(() => {\n if (!authConfig) {\n setIsLoading(true);\n } else {\n setIsLoading(false);\n }\n }, [authConfig]);\n\n const [validationInProgress, setValidationInProgress] = useState(false);\n // Handle page load or refocus\n useEffect(() => {\n if (\n !authConfig?.redirectUrl ||\n !authService ||\n !currentUrl ||\n isInIframe ||\n isLoading\n ) {\n return;\n }\n\n // We specifically don't want to abort token refresh operations\n // The AbortController is mainly used to prevent token exchange after unmount\n const abortController = new AbortController();\n\n const onPageLoad = async () => {\n if (validationInProgress || authStatus === AuthStatus.SIGNING_OUT) return;\n\n // if we have existing tokens, then validate them and return the session data\n // otherwise check if we have a code in the url and exchange it for tokens\n // if we have neither, initiate sign-in flow\n setValidationInProgress(true);\n\n try {\n const existingSessionData = await authService.validateExistingSession();\n\n if (existingSessionData.authenticated) {\n setSession(existingSessionData);\n return;\n }\n // If we have a code in the URL, attempt token exchange\n if (new URL(currentUrl).searchParams.get(\"code\")) {\n await doTokenExchange(currentUrl);\n return;\n }\n // No valid session and no code in URL - initiate sign-in\n // But only if we're not in an iframe (to prevent infinite loops in embedded scenarios)\n if (!isInIframe && authConfig) {\n const ref = getIframeRef(iframeRef?.current, true);\n if (ref && authConfig?.displayMode === \"iframe\") {\n startSignIn();\n }\n }\n } catch (error) {\n console.error(\"Error during session validation:\", error);\n } finally {\n setValidationInProgress(false);\n }\n };\n\n onPageLoad();\n\n return () => {\n abortController.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n authConfig,\n authService,\n currentUrl,\n doTokenExchange,\n isInIframe,\n isLoading,\n startSignIn,\n ]);\n\n const value = useMemo(\n () => ({\n data: session,\n error,\n isLoading,\n doTokenExchange: authService ? doTokenExchange : null,\n }),\n [session, error, isLoading, authService, doTokenExchange],\n );\n\n return (\n <ClientTokenExchangeSessionContext.Provider value={value}>\n {children}\n </ClientTokenExchangeSessionContext.Provider>\n );\n};\n\nexport type { ClientTokenExchangeSessionContextType as SessionContextType };\nexport {\n ClientTokenExchangeSessionProvider,\n ClientTokenExchangeSessionContext,\n};\n"]}
1
+ {"version":3,"file":"ClientTokenExchangeSessionProvider.js","sourceRoot":"","sources":["../../../src/reactjs/providers/ClientTokenExchangeSessionProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport type { ReactNode } from \"react\";\nimport React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n useRef,\n} from \"react\";\nimport { BrowserAuthenticationService } from \"@/services/AuthenticationService.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\nimport { isWindowInIframe } from \"@/lib/windowUtil.js\";\nimport { AuthStatus, type SessionData } from \"@/types.js\";\nimport {\n useCurrentUrl,\n useCivicAuthConfig,\n useSignIn,\n useIframe,\n} from \"@/shared/hooks/index.js\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport { getIframeRef } from \"@/shared/lib/iframeUtils.js\";\n\nexport type ClientTokenExchangeSessionProviderOutput = {\n data: SessionData | null;\n error: Error | null;\n isLoading: boolean;\n doTokenExchange: null | ((url: string) => Promise<void>);\n};\nconst defaultSession: ClientTokenExchangeSessionProviderOutput = {\n data: {\n authenticated: false,\n idToken: undefined,\n accessToken: undefined,\n displayMode: \"iframe\",\n },\n error: null,\n isLoading: false,\n doTokenExchange: null,\n};\n\n// Context for exposing session specifically to the TokenProvider\nconst ClientTokenExchangeSessionContext =\n createContext<ClientTokenExchangeSessionProviderOutput>(defaultSession);\n\ntype ClientTokenExchangeSessionContextType = {\n children: ReactNode;\n};\n\nconst ClientTokenExchangeSessionProvider = ({\n children,\n}: ClientTokenExchangeSessionContextType) => {\n const authConfig = useCivicAuthConfig();\n const [authService, setAuthService] = useState<AuthenticationResolver>();\n const [error, setError] = useState<Error | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [session, setSession] = useState<SessionData | null>(null);\n const { iframeRef } = useIframe();\n const currentUrl = useCurrentUrl();\n // Use the signIn hook with iframe displayMode\n const { startSignIn, authStatus } = useSignIn({\n displayMode: authConfig?.displayMode || \"iframe\",\n });\n\n // Add a ref to track processed codes\n const processedCodes = useRef(new Set<string>());\n\n useEffect(() => {\n if (!currentUrl || !authConfig) return;\n\n const {\n redirectUrl,\n clientId,\n oauthServer,\n scopes,\n logoutRedirectUrl,\n logoutUrl,\n } = authConfig;\n BrowserAuthenticationService.build({\n clientId,\n redirectUrl,\n logoutRedirectUrl,\n logoutUrl,\n oauthServer,\n scopes,\n displayMode: \"iframe\",\n }).then(setAuthService);\n }, [currentUrl, authConfig]);\n\n const isInIframe = isWindowInIframe(globalThis.window);\n\n const doTokenExchange = useCallback(\n async (inUrl: string) => {\n if (!authService) return;\n const url = new URL(inUrl);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n\n // Create a unique key for this code/state combination\n const exchangeKey = `${code}:${state}`;\n\n // If we've already processed this code, skip it\n if (processedCodes.current.has(exchangeKey)) {\n console.log(\"Token exchange already processed for this code\");\n return;\n }\n\n if (code && state) {\n try {\n // Mark this code as processed before starting the exchange\n processedCodes.current.add(exchangeKey);\n setIsLoading(true);\n await authService.tokenExchange(code, state);\n // Explicitly emit completion event after successful token exchange\n LocalStorageAdapter.emitter.emit(\"civic-auth-signin-complete\");\n } catch (error) {\n setError(error as Error);\n setSession({ authenticated: false });\n // Emit error event if exchange fails\n LocalStorageAdapter.emitter.emit(\"civic-auth-signin-error\", {\n error,\n });\n }\n setIsLoading(false);\n }\n },\n [authService],\n );\n\n const onSignIn = useCallback(async () => {\n if (!authService) return;\n const session = await authService.getSessionData();\n setSession(session);\n }, [authService]);\n\n const onSignOut = useCallback(() => {\n setSession(null);\n }, []);\n\n useEffect(() => {\n LocalStorageAdapter.emitter.on(\"signIn\", onSignIn);\n LocalStorageAdapter.emitter.on(\"signOut\", onSignOut);\n return () => {\n LocalStorageAdapter.emitter.off(\"signIn\", onSignIn);\n LocalStorageAdapter.emitter.off(\"signOut\", onSignOut);\n };\n }, [onSignIn, onSignOut]);\n\n useEffect(() => {\n if (!authConfig) {\n setIsLoading(true);\n } else {\n setIsLoading(false);\n }\n }, [authConfig]);\n\n const [validationInProgress, setValidationInProgress] = useState(false);\n // Handle page load or refocus\n useEffect(() => {\n if (\n !authConfig?.redirectUrl ||\n !authService ||\n !currentUrl ||\n isInIframe ||\n isLoading\n ) {\n return;\n }\n\n // We specifically don't want to abort token refresh operations\n // The AbortController is mainly used to prevent token exchange after unmount\n const abortController = new AbortController();\n\n const onPageLoad = async () => {\n if (\n validationInProgress ||\n authStatus === AuthStatus.SIGNING_OUT ||\n authStatus === AuthStatus.AUTHENTICATING\n )\n return;\n\n // if we have existing tokens, then validate them and return the session data\n // otherwise check if we have a code in the url and exchange it for tokens\n // if we have neither, initiate sign-in flow\n setValidationInProgress(true);\n\n try {\n const existingSessionData = await authService.validateExistingSession();\n\n if (existingSessionData.authenticated) {\n setSession(existingSessionData);\n return;\n }\n // If we have a code in the URL, attempt token exchange\n if (new URL(currentUrl).searchParams.get(\"code\")) {\n await doTokenExchange(currentUrl);\n return;\n }\n // No valid session and no code in URL - initiate sign-in\n // But only if we're not in an iframe (to prevent infinite loops in embedded scenarios)\n if (!isInIframe && authConfig) {\n const ref = getIframeRef(iframeRef?.current, true);\n if (ref && authConfig?.displayMode === \"iframe\") {\n startSignIn();\n }\n }\n } catch (error) {\n console.error(\"Error during session validation:\", error);\n } finally {\n setValidationInProgress(false);\n }\n };\n\n onPageLoad();\n\n return () => {\n abortController.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n authConfig,\n authService,\n currentUrl,\n doTokenExchange,\n isInIframe,\n isLoading,\n startSignIn,\n ]);\n\n const value = useMemo(\n () => ({\n data: session,\n error,\n isLoading,\n doTokenExchange: authService ? doTokenExchange : null,\n }),\n [session, error, isLoading, authService, doTokenExchange],\n );\n\n return (\n <ClientTokenExchangeSessionContext.Provider value={value}>\n {children}\n </ClientTokenExchangeSessionContext.Provider>\n );\n};\n\nexport type { ClientTokenExchangeSessionContextType as SessionContextType };\nexport {\n ClientTokenExchangeSessionProvider,\n ClientTokenExchangeSessionContext,\n};\n"]}
@@ -6,6 +6,9 @@ type SignInProps = {
6
6
  postSignOut?: () => Promise<void>;
7
7
  displayMode: DisplayMode;
8
8
  };
9
+ /**
10
+ * Hook to manage authentication flow.
11
+ */
9
12
  declare const useSignIn: ({ pkceConsumer, preSignOut, postSignOut, displayMode }?: SignInProps) => {
10
13
  signIn: () => Promise<void>;
11
14
  signOut: () => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"useSignIn.d.ts","sourceRoot":"","sources":["../../../src/shared/hooks/useSignIn.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAEjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQpE,KAAK,WAAW,GAAG;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAGF,QAAA,MAAM,SAAS,4DAC2C,WAAW;kBA6I9B,OAAO,CAAC,IAAI,CAAC;;;;;CAsHnD,CAAC;AACF,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"useSignIn.d.ts","sourceRoot":"","sources":["../../../src/shared/hooks/useSignIn.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAEjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQpE,KAAK,WAAW,GAAG;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAcF;;GAEG;AACH,QAAA,MAAM,SAAS,4DAC2C,WAAW;kBAoQ9B,OAAO,CAAC,IAAI,CAAC;;uBA/GR,OAAO,CAAC,IAAI,CAAC;;;CAiSxD,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -12,24 +12,70 @@ import { useLocalStorage } from "usehooks-ts";
12
12
  import { LOGOUT_STATE } from "../../constants.js";
13
13
  import { useAuthStatus } from "../../shared/providers/AuthStatusContext.js";
14
14
  const SIGN_IN_TIMEOUT_MS = 9 * 60 * 1000; // 9 minutes in milliseconds
15
+ /**
16
+ * Hook to manage authentication flow.
17
+ */
15
18
  const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
16
19
  displayMode: "iframe",
17
20
  }) => {
21
+ // Config and external state
18
22
  const civicAuthConfig = useCivicAuthConfig();
19
- const { iframeRef, logoutIframeRef, setIframeIsVisible, iframeIsVisible, setLogoutIframeIsVisible, iframeAborted, setIframeAborted, } = useIframe();
23
+ const { iframeRef, logoutIframeRef, setIframeIsVisible, setLogoutIframeIsVisible, iframeAborted, setIframeAborted, } = useIframe();
20
24
  const { data: session } = useSession();
21
- // Use the shared auth status from context instead of local state
22
25
  const { authStatus, setAuthStatus } = useAuthStatus();
23
26
  const [, setDesignOption] = useLocalStorage(`loginAppDesign`, { colorMode: "auto" });
27
+ // Internal state tracking
24
28
  const timeoutRef = useRef(null);
25
29
  const authStatusRef = useRef(AuthStatus.UNAUTHENTICATED);
30
+ const isSigningInRef = useRef(false);
31
+ // Promise handling
32
+ const signInPromiseRef = useRef();
33
+ const signInResolveRef = useRef(null);
34
+ const signInRejectRef = useRef(null);
35
+ // Event handlers
36
+ const handlersRef = useRef({
37
+ handleSignInComplete: null,
38
+ handleSignInError: null,
39
+ });
40
+ // Helper to resolve/reject sign-in promise with cleanup
41
+ const resolveSignInPromise = useCallback(() => {
42
+ if (signInResolveRef.current) {
43
+ setAuthStatus(AuthStatus.AUTHENTICATED);
44
+ authStatusRef.current = AuthStatus.AUTHENTICATED;
45
+ isSigningInRef.current = false;
46
+ signInResolveRef.current();
47
+ // Clean up after resolving
48
+ signInResolveRef.current = null;
49
+ signInRejectRef.current = null;
50
+ signInPromiseRef.current = undefined;
51
+ if (timeoutRef.current !== null) {
52
+ window.clearTimeout(timeoutRef.current);
53
+ timeoutRef.current = null;
54
+ }
55
+ }
56
+ }, [setAuthStatus]);
57
+ const rejectSignInPromise = useCallback((error, newStatus = AuthStatus.ERROR) => {
58
+ if (signInRejectRef.current) {
59
+ signInRejectRef.current(error);
60
+ // Clean up after rejecting
61
+ signInResolveRef.current = null;
62
+ signInRejectRef.current = null;
63
+ signInPromiseRef.current = undefined;
64
+ isSigningInRef.current = false;
65
+ setAuthStatus(newStatus);
66
+ if (timeoutRef.current !== null) {
67
+ window.clearTimeout(timeoutRef.current);
68
+ timeoutRef.current = null;
69
+ }
70
+ }
71
+ }, [setAuthStatus]);
72
+ // Create authentication initiator
26
73
  const authInitiator = useMemo(() => {
27
- if (!civicAuthConfig) {
74
+ if (!civicAuthConfig)
28
75
  return null;
29
- }
30
76
  const { clientId, redirectUrl, logoutUrl, logoutRedirectUrl, nonce, oauthServer, endpoints, scopes, } = civicAuthConfig;
31
77
  return new BrowserAuthenticationInitiator({
32
- pkceConsumer: pkceConsumer || new BrowserPublicClientPKCEProducer(), // generate and retrieve the challenge client-side
78
+ pkceConsumer: pkceConsumer || new BrowserPublicClientPKCEProducer(),
33
79
  clientId,
34
80
  redirectUrl,
35
81
  logoutUrl,
@@ -41,20 +87,19 @@ const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
41
87
  nonce,
42
88
  }, setDesignOption);
43
89
  }, [civicAuthConfig, displayMode, pkceConsumer, setDesignOption]);
90
+ // Cleanup resources when component unmounts
44
91
  useEffect(() => {
45
92
  return () => {
46
- // Clean up on unmount
47
93
  if (authInitiator) {
48
94
  authInitiator.cleanup();
49
95
  }
50
- // Clear any existing timeout
51
96
  if (timeoutRef.current !== null) {
52
97
  window.clearTimeout(timeoutRef.current);
53
98
  timeoutRef.current = null;
54
99
  }
55
100
  };
56
101
  }, [authInitiator]);
57
- // This effect is used to clear the tokens and user when the user signs out after a redirect
102
+ // Handle logout state cleanup
58
103
  useEffect(() => {
59
104
  const params = new URLSearchParams(window.location.search);
60
105
  const state = params.get("state");
@@ -72,44 +117,85 @@ const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
72
117
  }
73
118
  });
74
119
  }, []);
120
+ // Main sign-in logic
75
121
  const startSignIn = useCallback(async () => {
76
- if (!authInitiator)
77
- return;
78
- if (![AuthStatus.UNAUTHENTICATED, AuthStatus.ERROR].includes(authStatus)) {
79
- return;
122
+ if (!authInitiator) {
123
+ return Promise.reject(new Error("Auth initiator not available"));
124
+ }
125
+ // Check if sign-in is already in progress using the ref
126
+ if (isSigningInRef.current) {
127
+ return (signInPromiseRef.current ||
128
+ Promise.reject(new Error("Sign-in already in progress")));
129
+ }
130
+ // State machine for sign-in flow
131
+ switch (authStatus) {
132
+ case AuthStatus.AUTHENTICATED:
133
+ return Promise.resolve();
134
+ case AuthStatus.UNAUTHENTICATED:
135
+ case AuthStatus.ERROR:
136
+ break;
137
+ case AuthStatus.AUTHENTICATING:
138
+ // If we're authenticating but isSigningInRef is false, something went wrong
139
+ // Reset the state and allow a new sign-in attempt
140
+ setAuthStatus(AuthStatus.UNAUTHENTICATED);
141
+ break;
142
+ default:
143
+ return Promise.reject(new Error(`Invalid state for sign-in: ${authStatus}`));
80
144
  }
145
+ // Set signing in flag first
146
+ isSigningInRef.current = true;
81
147
  // Clear any existing timeout
82
148
  if (timeoutRef.current !== null) {
83
149
  window.clearTimeout(timeoutRef.current);
84
150
  timeoutRef.current = null;
85
151
  }
86
- setAuthStatus(AuthStatus.AUTHENTICATING);
87
- authInitiator.setDisplayMode(displayMode);
88
- // Set a timeout to reset authentication state and retry if still authenticating
89
- timeoutRef.current = window.setTimeout(() => {
90
- // Check the current auth status via ref when the timeout fires
91
- if (authStatusRef.current === AuthStatus.AUTHENTICATING) {
92
- console.log("Sign-in timeout reached. Resetting authentication state.");
93
- setAuthStatus(AuthStatus.UNAUTHENTICATED);
94
- // Call startSignIn again to restart with fresh state
95
- startSignIn();
96
- }
97
- }, SIGN_IN_TIMEOUT_MS);
98
- const useIframeRef = iframeRef?.current || null;
99
- await authInitiator.signIn(useIframeRef).catch((error) => {
100
- setAuthStatus(AuthStatus.ERROR);
101
- console.error("signIn error", {
102
- error,
103
- isPopupError: error instanceof PopupError,
104
- });
105
- // if we've tried to open a popup and it has failed, then fallback to redirect mode
152
+ // Create the promise and store its handlers
153
+ const promise = new Promise((resolve, reject) => {
154
+ signInResolveRef.current = resolve;
155
+ signInRejectRef.current = reject;
156
+ });
157
+ signInPromiseRef.current = promise;
158
+ try {
159
+ // Set authenticating status before any async operations
160
+ setAuthStatus(AuthStatus.AUTHENTICATING);
161
+ authStatusRef.current = AuthStatus.AUTHENTICATING;
162
+ authInitiator.setDisplayMode(displayMode);
163
+ // Set a timeout to reject the promise if authentication takes too long
164
+ timeoutRef.current = window.setTimeout(() => {
165
+ if (authStatusRef.current === AuthStatus.AUTHENTICATING) {
166
+ rejectSignInPromise(new Error("Sign-in timeout"));
167
+ }
168
+ }, SIGN_IN_TIMEOUT_MS);
169
+ // Start the authentication process
170
+ const useIframeRef = iframeRef?.current || null;
171
+ await authInitiator.signIn(useIframeRef);
172
+ }
173
+ catch (error) {
174
+ // Reset the signing in flag on error
175
+ isSigningInRef.current = false;
106
176
  if (error instanceof PopupError) {
107
- setIframeIsVisible(false); // hide the iframe
108
- authInitiator.cleanup(); // clear any event listeners from before
109
- authInitiator.setDisplayMode("redirect"); // switch to redirect mode
110
- authInitiator.signIn(useIframeRef); // retry the sign in
177
+ // Fallback to redirect if popup fails
178
+ setIframeIsVisible(false);
179
+ authInitiator.cleanup();
180
+ authInitiator.setDisplayMode("redirect");
181
+ try {
182
+ // Call signIn again with redirect mode
183
+ await authInitiator.signIn(iframeRef?.current || null);
184
+ }
185
+ catch (retryError) {
186
+ console.error("[useSignIn] Redirect sign-in initiation error", retryError);
187
+ rejectSignInPromise(retryError instanceof Error
188
+ ? retryError
189
+ : new Error(String(retryError)));
190
+ return promise;
191
+ }
111
192
  }
112
- });
193
+ else {
194
+ rejectSignInPromise(error instanceof Error ? error : new Error(String(error)));
195
+ return promise;
196
+ }
197
+ }
198
+ return promise;
113
199
  }, [
114
200
  authInitiator,
115
201
  displayMode,
@@ -117,13 +203,16 @@ const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
117
203
  setIframeIsVisible,
118
204
  authStatus,
119
205
  setAuthStatus,
206
+ rejectSignInPromise,
120
207
  ]);
208
+ // Public sign-in method
121
209
  const signIn = useCallback(async () => {
122
210
  if (displayMode === "iframe") {
123
211
  setIframeIsVisible(true);
124
212
  }
125
213
  return startSignIn();
126
214
  }, [startSignIn, displayMode, setIframeIsVisible]);
215
+ // Sign-out method
127
216
  const signOut = useCallback(async () => {
128
217
  const idToken = session?.idToken;
129
218
  if (!authInitiator)
@@ -142,17 +231,17 @@ const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
142
231
  error,
143
232
  isPopupError: error instanceof PopupError,
144
233
  });
145
- // Same popup fallback as signIn
146
234
  if (error instanceof PopupError) {
147
235
  setLogoutIframeIsVisible(false);
148
236
  authInitiator.cleanup();
149
237
  authInitiator.setDisplayMode("redirect");
150
- authInitiator.signOut(idToken, useIframeRef);
238
+ authInitiator.signOut(idToken, useIframeRef); // Retry sign out
151
239
  }
152
240
  });
153
241
  }
154
242
  catch (error) {
155
243
  console.error("Signout error:", error);
244
+ setAuthStatus(AuthStatus.ERROR);
156
245
  }
157
246
  }, [
158
247
  session?.idToken,
@@ -164,57 +253,88 @@ const useSignIn = ({ pkceConsumer, preSignOut, postSignOut, displayMode } = {
164
253
  logoutIframeRef,
165
254
  setAuthStatus,
166
255
  ]);
167
- // Keep the authStatusRef in sync with authStatus
256
+ // Set up event listeners for authentication status
168
257
  useEffect(() => {
169
- authStatusRef.current = authStatus;
170
- if (authStatus !== AuthStatus.AUTHENTICATING &&
171
- timeoutRef.current !== null) {
172
- window.clearTimeout(timeoutRef.current);
173
- timeoutRef.current = null;
258
+ // Define handlers
259
+ handlersRef.current = {
260
+ handleSignInComplete: () => {
261
+ if (authStatusRef.current === AuthStatus.AUTHENTICATING) {
262
+ resolveSignInPromise();
263
+ }
264
+ },
265
+ handleSignInError: (event) => {
266
+ if (authStatusRef.current === AuthStatus.AUTHENTICATING) {
267
+ const error = event.detail.error;
268
+ rejectSignInPromise(error instanceof Error
269
+ ? error
270
+ : new Error(error.message || "Sign-in failed"));
271
+ }
272
+ },
273
+ };
274
+ // Add listeners only when in authenticating state
275
+ if (authStatus === AuthStatus.AUTHENTICATING &&
276
+ handlersRef.current.handleSignInComplete &&
277
+ handlersRef.current.handleSignInError) {
278
+ LocalStorageAdapter.emitter.on("civic-auth-signin-complete", handlersRef.current.handleSignInComplete);
279
+ LocalStorageAdapter.emitter.on("civic-auth-signin-error", handlersRef.current.handleSignInError);
174
280
  }
175
- }, [authStatus]);
281
+ // Cleanup listeners
282
+ return () => {
283
+ if (handlersRef.current.handleSignInComplete) {
284
+ LocalStorageAdapter.emitter.off("civic-auth-signin-complete", handlersRef.current.handleSignInComplete);
285
+ }
286
+ if (handlersRef.current.handleSignInError) {
287
+ LocalStorageAdapter.emitter.off("civic-auth-signin-error", handlersRef.current.handleSignInError);
288
+ }
289
+ };
290
+ }, [authStatus, resolveSignInPromise, rejectSignInPromise]);
291
+ // Effect to handle session updates and iframe aborts
176
292
  useEffect(() => {
177
- if (session?.authenticated) {
293
+ // If session becomes authenticated, update state and resolve pending promises
294
+ if (session?.authenticated &&
295
+ authStatusRef.current !== AuthStatus.AUTHENTICATED) {
178
296
  setAuthStatus(AuthStatus.AUTHENTICATED);
179
- // Clear timeout when authentication is successful
180
- if (timeoutRef.current !== null) {
181
- window.clearTimeout(timeoutRef.current);
182
- timeoutRef.current = null;
297
+ // If we have a pending sign-in promise, resolve it
298
+ if (authStatusRef.current === AuthStatus.AUTHENTICATING &&
299
+ signInResolveRef.current) {
300
+ resolveSignInPromise();
183
301
  }
184
302
  }
303
+ // Handle iframe abortion
185
304
  if (displayMode === "iframe" && iframeAborted) {
186
305
  setIframeAborted(false);
187
- // Clear timeout when iframe is aborted
188
- if (timeoutRef.current !== null) {
189
- window.clearTimeout(timeoutRef.current);
190
- timeoutRef.current = null;
306
+ if (authStatusRef.current === AuthStatus.AUTHENTICATING) {
307
+ rejectSignInPromise(new Error("Sign-in aborted by user"), AuthStatus.UNAUTHENTICATED);
191
308
  }
192
309
  }
193
- if (!session?.authenticated) {
194
- setAuthStatus(AuthStatus.UNAUTHENTICATED);
310
+ // Update unauthenticated state when session is absent
311
+ if (!session?.authenticated &&
312
+ ![AuthStatus.AUTHENTICATING, AuthStatus.SIGNING_OUT].includes(authStatusRef.current)) {
313
+ if (authStatusRef.current !== AuthStatus.UNAUTHENTICATED) {
314
+ setAuthStatus(AuthStatus.UNAUTHENTICATED);
315
+ }
195
316
  }
196
317
  }, [
197
318
  displayMode,
198
319
  iframeAborted,
199
320
  session?.authenticated,
200
321
  setIframeAborted,
322
+ resolveSignInPromise,
323
+ rejectSignInPromise,
201
324
  setAuthStatus,
202
325
  ]);
203
- // handle logout finished
326
+ // Handle logout completion
204
327
  useEffect(() => {
205
328
  if (authStatus === AuthStatus.SIGNING_OUT && !session?.authenticated) {
206
329
  setAuthStatus(AuthStatus.UNAUTHENTICATED);
207
330
  postSignOut?.().then(() => {
208
331
  setLogoutIframeIsVisible(false);
209
332
  });
210
- return;
211
333
  }
212
334
  }, [
213
335
  session,
214
336
  postSignOut,
215
337
  setLogoutIframeIsVisible,
216
- displayMode,
217
- iframeIsVisible,
218
338
  authStatus,
219
339
  setAuthStatus,
220
340
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"useSignIn.js","sourceRoot":"","sources":["../../../src/shared/hooks/useSignIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EACL,UAAU,GAGX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,UAAU,EAAqB,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAQxE,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,4BAA4B;AAEtE,MAAM,SAAS,GAAG,CAChB,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,KAAkB;IACpE,WAAW,EAAE,QAAQ;CACtB,EACD,EAAE;IACF,MAAM,eAAe,GAAG,kBAAkB,EAAE,CAAC;IAC7C,MAAM,EACJ,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,wBAAwB,EACxB,aAAa,EACb,gBAAgB,GACjB,GAAG,SAAS,EAAE,CAAC;IAChB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;IACvC,iEAAiE;IACjE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAAC;IACtD,MAAM,CAAC,EAAE,eAAe,CAAC,GAAG,eAAe,CACzC,gBAAgB,EAChB,EAAE,SAAS,EAAE,MAAM,EAAE,CACtB,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,MAAM,CAAa,UAAU,CAAC,eAAe,CAAC,CAAC;IAErE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE;QACjC,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,EACJ,QAAQ,EACR,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,SAAS,EACT,MAAM,GACP,GAAG,eAAe,CAAC;QACpB,OAAO,IAAI,8BAA8B,CACvC;YACE,YAAY,EAAE,YAAY,IAAI,IAAI,+BAA+B,EAAE,EAAE,kDAAkD;YACvH,QAAQ;YACR,WAAW;YACX,SAAS;YACT,iBAAiB;YACjB,MAAM;YACN,WAAW;YACX,WAAW;YACX,iBAAiB,EAAE,SAAS;YAC5B,KAAK;SACN,EACD,eAAe,CAChB,CAAC;IACJ,CAAC,EAAE,CAAC,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC;IAElE,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,sBAAsB;YACtB,IAAI,aAAa,EAAE,CAAC;gBAClB,aAAa,CAAC,OAAO,EAAE,CAAC;YAC1B,CAAC;YAED,6BAA6B;YAC7B,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAChC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACxC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,4FAA4F;IAC5F,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,YAAY,GAAG,IAAI,mBAAmB,EAAE,CAAC;QAC/C,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,EAAE;YACxD,IAAI,KAAK,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;gBACzC,gBAAgB;gBAChB,WAAW,CAAC,YAAY,CAAC,CAAC;gBAC1B,SAAS,CAAC,YAAY,CAAC,CAAC;gBACxB,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAE5C,2BAA2B;gBAC3B,cAAc,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;gBACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBACpD,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC,aAAa;YAAE,OAAO;QAE3B,IAAI,CAAC,CAAC,UAAU,CAAC,eAAe,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACzE,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAChC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACxC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;QAED,aAAa,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QACzC,aAAa,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAE1C,gFAAgF;QAChF,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;YAC1C,+DAA+D;YAC/D,IAAI,aAAa,CAAC,OAAO,KAAK,UAAU,CAAC,cAAc,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;gBACxE,aAAa,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;gBAC1C,qDAAqD;gBACrD,WAAW,EAAE,CAAC;YAChB,CAAC;QACH,CAAC,EAAE,kBAAkB,CAAC,CAAC;QAEvB,MAAM,YAAY,GAAG,SAAS,EAAE,OAAO,IAAI,IAAI,CAAC;QAChD,MAAM,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACvD,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE;gBAC5B,KAAK;gBACL,YAAY,EAAE,KAAK,YAAY,UAAU;aAC1C,CAAC,CAAC;YACH,mFAAmF;YACnF,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;gBAChC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,kBAAkB;gBAC7C,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,aAAa,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,0BAA0B;gBACpE,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,oBAAoB;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,EAAE;QACD,aAAa;QACb,WAAW;QACX,SAAS;QACT,kBAAkB;QAClB,UAAU;QACV,aAAa;KACd,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACnD,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC7B,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACrC,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;QACjC,IAAI,CAAC,aAAa;YAAE,OAAO;QAE3B,aAAa,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QACtC,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC7B,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,wBAAwB,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,UAAU,EAAE,EAAE,CAAC;YAErB,MAAM,YAAY,GAAG,eAAe,EAAE,OAAO,IAAI,IAAI,CAAC;YACtD,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACjE,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE;oBAC7B,KAAK;oBACL,YAAY,EAAE,KAAK,YAAY,UAAU;iBAC1C,CAAC,CAAC;gBAEH,gCAAgC;gBAChC,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;oBAChC,wBAAwB,CAAC,KAAK,CAAC,CAAC;oBAChC,aAAa,CAAC,OAAO,EAAE,CAAC;oBACxB,aAAa,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;oBACzC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,EAAE;QACD,OAAO,EAAE,OAAO;QAChB,aAAa;QACb,WAAW;QACX,wBAAwB;QACxB,kBAAkB;QAClB,UAAU;QACV,eAAe;QACf,aAAa;KACd,CAAC,CAAC;IAEH,iDAAiD;IACjD,SAAS,CAAC,GAAG,EAAE;QACb,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC;QACnC,IACE,UAAU,KAAK,UAAU,CAAC,cAAc;YACxC,UAAU,CAAC,OAAO,KAAK,IAAI,EAC3B,CAAC;YACD,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACxC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,EAAE,aAAa,EAAE,CAAC;YAC3B,aAAa,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAExC,kDAAkD;YAClD,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAChC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACxC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,IAAI,WAAW,KAAK,QAAQ,IAAI,aAAa,EAAE,CAAC;YAC9C,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAExB,uCAAuC;YACvC,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAChC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACxC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC;YAC5B,aAAa,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,EAAE;QACD,WAAW;QACX,aAAa;QACb,OAAO,EAAE,aAAa;QACtB,gBAAgB;QAChB,aAAa;KACd,CAAC,CAAC;IAEH,yBAAyB;IACzB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,KAAK,UAAU,CAAC,WAAW,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC;YACrE,aAAa,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;YAC1C,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;gBACxB,wBAAwB,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,WAAW;QACX,wBAAwB;QACxB,WAAW;QACX,eAAe;QACf,UAAU;QACV,aAAa;KACd,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;QACN,OAAO;QACP,WAAW;QACX,UAAU;QACV,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC;AACF,OAAO,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import { BrowserAuthenticationInitiator } from \"@/services/AuthenticationService.js\";\nimport { BrowserPublicClientPKCEProducer } from \"@/services/PKCE.js\";\nimport { useCivicAuthConfig } from \"@/shared/hooks/useCivicAuthConfig.js\";\nimport {\n AuthStatus,\n type DisplayMode,\n type LoginAppDesignOptions,\n} from \"@/types.js\";\nimport { useIframe } from \"@/shared/hooks/useIframe.js\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { PopupError, type PKCEConsumer } from \"@/services/types.js\";\nimport { useSession } from \"./useSession.js\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport { clearTokens, clearUser } from \"../lib/util.js\";\nimport { useLocalStorage } from \"usehooks-ts\";\nimport { LOGOUT_STATE } from \"@/constants.js\";\nimport { useAuthStatus } from \"@/shared/providers/AuthStatusContext.js\";\n\ntype SignInProps = {\n pkceConsumer?: PKCEConsumer;\n preSignOut?: () => Promise<void>;\n postSignOut?: () => Promise<void>;\n displayMode: DisplayMode;\n};\nconst SIGN_IN_TIMEOUT_MS = 9 * 60 * 1000; // 9 minutes in milliseconds\n\nconst useSignIn = (\n { pkceConsumer, preSignOut, postSignOut, displayMode }: SignInProps = {\n displayMode: \"iframe\",\n },\n) => {\n const civicAuthConfig = useCivicAuthConfig();\n const {\n iframeRef,\n logoutIframeRef,\n setIframeIsVisible,\n iframeIsVisible,\n setLogoutIframeIsVisible,\n iframeAborted,\n setIframeAborted,\n } = useIframe();\n const { data: session } = useSession();\n // Use the shared auth status from context instead of local state\n const { authStatus, setAuthStatus } = useAuthStatus();\n const [, setDesignOption] = useLocalStorage<LoginAppDesignOptions>(\n `loginAppDesign`,\n { colorMode: \"auto\" },\n );\n const timeoutRef = useRef<number | null>(null);\n const authStatusRef = useRef<AuthStatus>(AuthStatus.UNAUTHENTICATED);\n\n const authInitiator = useMemo(() => {\n if (!civicAuthConfig) {\n return null;\n }\n const {\n clientId,\n redirectUrl,\n logoutUrl,\n logoutRedirectUrl,\n nonce,\n oauthServer,\n endpoints,\n scopes,\n } = civicAuthConfig;\n return new BrowserAuthenticationInitiator(\n {\n pkceConsumer: pkceConsumer || new BrowserPublicClientPKCEProducer(), // generate and retrieve the challenge client-side\n clientId,\n redirectUrl,\n logoutUrl,\n logoutRedirectUrl,\n scopes,\n displayMode,\n oauthServer,\n endpointOverrides: endpoints,\n nonce,\n },\n setDesignOption,\n );\n }, [civicAuthConfig, displayMode, pkceConsumer, setDesignOption]);\n\n useEffect(() => {\n return () => {\n // Clean up on unmount\n if (authInitiator) {\n authInitiator.cleanup();\n }\n\n // Clear any existing timeout\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [authInitiator]);\n\n // This effect is used to clear the tokens and user when the user signs out after a redirect\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n const state = params.get(\"state\");\n const localStorage = new LocalStorageAdapter();\n localStorage.get(LOGOUT_STATE).then((storedLogoutState) => {\n if (state && state === storedLogoutState) {\n // Clear storage\n clearTokens(localStorage);\n clearUser(localStorage);\n LocalStorageAdapter.emitter.emit(\"signOut\");\n\n // Clean up storage and URL\n sessionStorage.removeItem(LOGOUT_STATE);\n const cleanUrl = window.location.href.split(\"?\")[0];\n window.history.replaceState({}, document.title, cleanUrl);\n }\n });\n }, []);\n\n const startSignIn = useCallback(async () => {\n if (!authInitiator) return;\n\n if (![AuthStatus.UNAUTHENTICATED, AuthStatus.ERROR].includes(authStatus)) {\n return;\n }\n\n // Clear any existing timeout\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n setAuthStatus(AuthStatus.AUTHENTICATING);\n authInitiator.setDisplayMode(displayMode);\n\n // Set a timeout to reset authentication state and retry if still authenticating\n timeoutRef.current = window.setTimeout(() => {\n // Check the current auth status via ref when the timeout fires\n if (authStatusRef.current === AuthStatus.AUTHENTICATING) {\n console.log(\"Sign-in timeout reached. Resetting authentication state.\");\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n // Call startSignIn again to restart with fresh state\n startSignIn();\n }\n }, SIGN_IN_TIMEOUT_MS);\n\n const useIframeRef = iframeRef?.current || null;\n await authInitiator.signIn(useIframeRef).catch((error) => {\n setAuthStatus(AuthStatus.ERROR);\n console.error(\"signIn error\", {\n error,\n isPopupError: error instanceof PopupError,\n });\n // if we've tried to open a popup and it has failed, then fallback to redirect mode\n if (error instanceof PopupError) {\n setIframeIsVisible(false); // hide the iframe\n authInitiator.cleanup(); // clear any event listeners from before\n authInitiator.setDisplayMode(\"redirect\"); // switch to redirect mode\n authInitiator.signIn(useIframeRef); // retry the sign in\n }\n });\n }, [\n authInitiator,\n displayMode,\n iframeRef,\n setIframeIsVisible,\n authStatus,\n setAuthStatus,\n ]);\n\n const signIn = useCallback(async (): Promise<void> => {\n if (displayMode === \"iframe\") {\n setIframeIsVisible(true);\n }\n return startSignIn();\n }, [startSignIn, displayMode, setIframeIsVisible]);\n\n const signOut = useCallback(async () => {\n const idToken = session?.idToken;\n if (!authInitiator) return;\n\n setAuthStatus(AuthStatus.SIGNING_OUT);\n if (displayMode === \"iframe\") {\n setIframeIsVisible(false);\n setLogoutIframeIsVisible(true);\n }\n\n try {\n await preSignOut?.();\n\n const useIframeRef = logoutIframeRef?.current || null;\n await authInitiator.signOut(idToken, useIframeRef).catch((error) => {\n setAuthStatus(AuthStatus.ERROR);\n console.error(\"signOut error\", {\n error,\n isPopupError: error instanceof PopupError,\n });\n\n // Same popup fallback as signIn\n if (error instanceof PopupError) {\n setLogoutIframeIsVisible(false);\n authInitiator.cleanup();\n authInitiator.setDisplayMode(\"redirect\");\n authInitiator.signOut(idToken, useIframeRef);\n }\n });\n } catch (error) {\n console.error(\"Signout error:\", error);\n }\n }, [\n session?.idToken,\n authInitiator,\n displayMode,\n setLogoutIframeIsVisible,\n setIframeIsVisible,\n preSignOut,\n logoutIframeRef,\n setAuthStatus,\n ]);\n\n // Keep the authStatusRef in sync with authStatus\n useEffect(() => {\n authStatusRef.current = authStatus;\n if (\n authStatus !== AuthStatus.AUTHENTICATING &&\n timeoutRef.current !== null\n ) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }, [authStatus]);\n\n useEffect(() => {\n if (session?.authenticated) {\n setAuthStatus(AuthStatus.AUTHENTICATED);\n\n // Clear timeout when authentication is successful\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }\n if (displayMode === \"iframe\" && iframeAborted) {\n setIframeAborted(false);\n\n // Clear timeout when iframe is aborted\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }\n if (!session?.authenticated) {\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n }\n }, [\n displayMode,\n iframeAborted,\n session?.authenticated,\n setIframeAborted,\n setAuthStatus,\n ]);\n\n // handle logout finished\n useEffect(() => {\n if (authStatus === AuthStatus.SIGNING_OUT && !session?.authenticated) {\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n postSignOut?.().then(() => {\n setLogoutIframeIsVisible(false);\n });\n return;\n }\n }, [\n session,\n postSignOut,\n setLogoutIframeIsVisible,\n displayMode,\n iframeIsVisible,\n authStatus,\n setAuthStatus,\n ]);\n\n return {\n signIn,\n signOut,\n startSignIn,\n authStatus,\n displayMode,\n };\n};\nexport { useSignIn };\n"]}
1
+ {"version":3,"file":"useSignIn.js","sourceRoot":"","sources":["../../../src/shared/hooks/useSignIn.ts"],"names":[],"mappings":"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","sourcesContent":["import { BrowserAuthenticationInitiator } from \"@/services/AuthenticationService.js\";\nimport { BrowserPublicClientPKCEProducer } from \"@/services/PKCE.js\";\nimport { useCivicAuthConfig } from \"@/shared/hooks/useCivicAuthConfig.js\";\nimport {\n AuthStatus,\n type DisplayMode,\n type LoginAppDesignOptions,\n} from \"@/types.js\";\nimport { useIframe } from \"@/shared/hooks/useIframe.js\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { PopupError, type PKCEConsumer } from \"@/services/types.js\";\nimport { useSession } from \"./useSession.js\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport { clearTokens, clearUser } from \"../lib/util.js\";\nimport { useLocalStorage } from \"usehooks-ts\";\nimport { LOGOUT_STATE } from \"@/constants.js\";\nimport { useAuthStatus } from \"@/shared/providers/AuthStatusContext.js\";\n\ntype SignInProps = {\n pkceConsumer?: PKCEConsumer;\n preSignOut?: () => Promise<void>;\n postSignOut?: () => Promise<void>;\n displayMode: DisplayMode;\n};\n\ntype SignInError = Error | { message: string; [key: string]: unknown };\n\ninterface SignInEventDetail {\n error: SignInError;\n}\n\ninterface SignInErrorEvent {\n detail: SignInEventDetail;\n}\n\nconst SIGN_IN_TIMEOUT_MS = 9 * 60 * 1000; // 9 minutes in milliseconds\n\n/**\n * Hook to manage authentication flow.\n */\nconst useSignIn = (\n { pkceConsumer, preSignOut, postSignOut, displayMode }: SignInProps = {\n displayMode: \"iframe\",\n },\n) => {\n // Config and external state\n const civicAuthConfig = useCivicAuthConfig();\n const {\n iframeRef,\n logoutIframeRef,\n setIframeIsVisible,\n setLogoutIframeIsVisible,\n iframeAborted,\n setIframeAborted,\n } = useIframe();\n const { data: session } = useSession();\n const { authStatus, setAuthStatus } = useAuthStatus();\n const [, setDesignOption] = useLocalStorage<LoginAppDesignOptions>(\n `loginAppDesign`,\n { colorMode: \"auto\" },\n );\n\n // Internal state tracking\n const timeoutRef = useRef<number | null>(null);\n const authStatusRef = useRef<AuthStatus>(AuthStatus.UNAUTHENTICATED);\n const isSigningInRef = useRef<boolean>(false);\n\n // Promise handling\n const signInPromiseRef = useRef<Promise<void>>();\n const signInResolveRef = useRef<\n ((value: void | PromiseLike<void>) => void) | null\n >(null);\n const signInRejectRef = useRef<((reason: Error) => void) | null>(null);\n\n // Event handlers\n const handlersRef = useRef<{\n handleSignInComplete: (() => void) | null;\n handleSignInError: ((event: SignInErrorEvent) => void) | null;\n }>({\n handleSignInComplete: null,\n handleSignInError: null,\n });\n\n // Helper to resolve/reject sign-in promise with cleanup\n const resolveSignInPromise = useCallback(() => {\n if (signInResolveRef.current) {\n setAuthStatus(AuthStatus.AUTHENTICATED);\n authStatusRef.current = AuthStatus.AUTHENTICATED;\n isSigningInRef.current = false;\n signInResolveRef.current();\n\n // Clean up after resolving\n signInResolveRef.current = null;\n signInRejectRef.current = null;\n signInPromiseRef.current = undefined;\n\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }\n }, [setAuthStatus]);\n\n const rejectSignInPromise = useCallback(\n (error: Error, newStatus: AuthStatus = AuthStatus.ERROR) => {\n if (signInRejectRef.current) {\n signInRejectRef.current(error);\n\n // Clean up after rejecting\n signInResolveRef.current = null;\n signInRejectRef.current = null;\n signInPromiseRef.current = undefined;\n isSigningInRef.current = false;\n setAuthStatus(newStatus);\n\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }\n },\n [setAuthStatus],\n );\n\n // Create authentication initiator\n const authInitiator = useMemo(() => {\n if (!civicAuthConfig) return null;\n\n const {\n clientId,\n redirectUrl,\n logoutUrl,\n logoutRedirectUrl,\n nonce,\n oauthServer,\n endpoints,\n scopes,\n } = civicAuthConfig;\n\n return new BrowserAuthenticationInitiator(\n {\n pkceConsumer: pkceConsumer || new BrowserPublicClientPKCEProducer(),\n clientId,\n redirectUrl,\n logoutUrl,\n logoutRedirectUrl,\n scopes,\n displayMode,\n oauthServer,\n endpointOverrides: endpoints,\n nonce,\n },\n setDesignOption,\n );\n }, [civicAuthConfig, displayMode, pkceConsumer, setDesignOption]);\n\n // Cleanup resources when component unmounts\n useEffect(() => {\n return () => {\n if (authInitiator) {\n authInitiator.cleanup();\n }\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [authInitiator]);\n\n // Handle logout state cleanup\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n const state = params.get(\"state\");\n const localStorage = new LocalStorageAdapter();\n localStorage.get(LOGOUT_STATE).then((storedLogoutState) => {\n if (state && state === storedLogoutState) {\n // Clear storage\n clearTokens(localStorage);\n clearUser(localStorage);\n LocalStorageAdapter.emitter.emit(\"signOut\");\n\n // Clean up storage and URL\n sessionStorage.removeItem(LOGOUT_STATE);\n const cleanUrl = window.location.href.split(\"?\")[0];\n window.history.replaceState({}, document.title, cleanUrl);\n }\n });\n }, []);\n\n // Main sign-in logic\n const startSignIn = useCallback(async (): Promise<void> => {\n if (!authInitiator) {\n return Promise.reject(new Error(\"Auth initiator not available\"));\n }\n\n // Check if sign-in is already in progress using the ref\n if (isSigningInRef.current) {\n return (\n signInPromiseRef.current ||\n Promise.reject(new Error(\"Sign-in already in progress\"))\n );\n }\n\n // State machine for sign-in flow\n switch (authStatus) {\n case AuthStatus.AUTHENTICATED:\n return Promise.resolve();\n\n case AuthStatus.UNAUTHENTICATED:\n case AuthStatus.ERROR:\n break;\n\n case AuthStatus.AUTHENTICATING:\n // If we're authenticating but isSigningInRef is false, something went wrong\n // Reset the state and allow a new sign-in attempt\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n break;\n\n default:\n return Promise.reject(\n new Error(`Invalid state for sign-in: ${authStatus}`),\n );\n }\n\n // Set signing in flag first\n isSigningInRef.current = true;\n\n // Clear any existing timeout\n if (timeoutRef.current !== null) {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n // Create the promise and store its handlers\n const promise = new Promise<void>((resolve, reject) => {\n signInResolveRef.current = resolve;\n signInRejectRef.current = reject;\n });\n signInPromiseRef.current = promise;\n\n try {\n // Set authenticating status before any async operations\n setAuthStatus(AuthStatus.AUTHENTICATING);\n authStatusRef.current = AuthStatus.AUTHENTICATING;\n authInitiator.setDisplayMode(displayMode);\n\n // Set a timeout to reject the promise if authentication takes too long\n timeoutRef.current = window.setTimeout(() => {\n if (authStatusRef.current === AuthStatus.AUTHENTICATING) {\n rejectSignInPromise(new Error(\"Sign-in timeout\"));\n }\n }, SIGN_IN_TIMEOUT_MS);\n\n // Start the authentication process\n const useIframeRef = iframeRef?.current || null;\n\n await authInitiator.signIn(useIframeRef);\n } catch (error) {\n // Reset the signing in flag on error\n isSigningInRef.current = false;\n\n if (error instanceof PopupError) {\n // Fallback to redirect if popup fails\n setIframeIsVisible(false);\n authInitiator.cleanup();\n authInitiator.setDisplayMode(\"redirect\");\n try {\n // Call signIn again with redirect mode\n await authInitiator.signIn(iframeRef?.current || null);\n } catch (retryError) {\n console.error(\n \"[useSignIn] Redirect sign-in initiation error\",\n retryError,\n );\n rejectSignInPromise(\n retryError instanceof Error\n ? retryError\n : new Error(String(retryError)),\n );\n return promise;\n }\n } else {\n rejectSignInPromise(\n error instanceof Error ? error : new Error(String(error)),\n );\n return promise;\n }\n }\n\n return promise;\n }, [\n authInitiator,\n displayMode,\n iframeRef,\n setIframeIsVisible,\n authStatus,\n setAuthStatus,\n rejectSignInPromise,\n ]);\n\n // Public sign-in method\n const signIn = useCallback(async (): Promise<void> => {\n if (displayMode === \"iframe\") {\n setIframeIsVisible(true);\n }\n return startSignIn();\n }, [startSignIn, displayMode, setIframeIsVisible]);\n\n // Sign-out method\n const signOut = useCallback(async () => {\n const idToken = session?.idToken;\n if (!authInitiator) return;\n\n setAuthStatus(AuthStatus.SIGNING_OUT);\n if (displayMode === \"iframe\") {\n setIframeIsVisible(false);\n setLogoutIframeIsVisible(true);\n }\n\n try {\n await preSignOut?.();\n\n const useIframeRef = logoutIframeRef?.current || null;\n await authInitiator.signOut(idToken, useIframeRef).catch((error) => {\n setAuthStatus(AuthStatus.ERROR);\n console.error(\"signOut error\", {\n error,\n isPopupError: error instanceof PopupError,\n });\n\n if (error instanceof PopupError) {\n setLogoutIframeIsVisible(false);\n authInitiator.cleanup();\n authInitiator.setDisplayMode(\"redirect\");\n authInitiator.signOut(idToken, useIframeRef); // Retry sign out\n }\n });\n } catch (error) {\n console.error(\"Signout error:\", error);\n setAuthStatus(AuthStatus.ERROR);\n }\n }, [\n session?.idToken,\n authInitiator,\n displayMode,\n setLogoutIframeIsVisible,\n setIframeIsVisible,\n preSignOut,\n logoutIframeRef,\n setAuthStatus,\n ]);\n\n // Set up event listeners for authentication status\n useEffect(() => {\n // Define handlers\n handlersRef.current = {\n handleSignInComplete: () => {\n if (authStatusRef.current === AuthStatus.AUTHENTICATING) {\n resolveSignInPromise();\n }\n },\n handleSignInError: (event: SignInErrorEvent) => {\n if (authStatusRef.current === AuthStatus.AUTHENTICATING) {\n const error = event.detail.error;\n rejectSignInPromise(\n error instanceof Error\n ? error\n : new Error(error.message || \"Sign-in failed\"),\n );\n }\n },\n };\n\n // Add listeners only when in authenticating state\n if (\n authStatus === AuthStatus.AUTHENTICATING &&\n handlersRef.current.handleSignInComplete &&\n handlersRef.current.handleSignInError\n ) {\n LocalStorageAdapter.emitter.on(\n \"civic-auth-signin-complete\",\n handlersRef.current.handleSignInComplete,\n );\n LocalStorageAdapter.emitter.on(\n \"civic-auth-signin-error\",\n handlersRef.current.handleSignInError,\n );\n }\n\n // Cleanup listeners\n return () => {\n if (handlersRef.current.handleSignInComplete) {\n LocalStorageAdapter.emitter.off(\n \"civic-auth-signin-complete\",\n handlersRef.current.handleSignInComplete,\n );\n }\n if (handlersRef.current.handleSignInError) {\n LocalStorageAdapter.emitter.off(\n \"civic-auth-signin-error\",\n handlersRef.current.handleSignInError,\n );\n }\n };\n }, [authStatus, resolveSignInPromise, rejectSignInPromise]);\n\n // Effect to handle session updates and iframe aborts\n useEffect(() => {\n // If session becomes authenticated, update state and resolve pending promises\n if (\n session?.authenticated &&\n authStatusRef.current !== AuthStatus.AUTHENTICATED\n ) {\n setAuthStatus(AuthStatus.AUTHENTICATED);\n\n // If we have a pending sign-in promise, resolve it\n if (\n authStatusRef.current === AuthStatus.AUTHENTICATING &&\n signInResolveRef.current\n ) {\n resolveSignInPromise();\n }\n }\n\n // Handle iframe abortion\n if (displayMode === \"iframe\" && iframeAborted) {\n setIframeAborted(false);\n if (authStatusRef.current === AuthStatus.AUTHENTICATING) {\n rejectSignInPromise(\n new Error(\"Sign-in aborted by user\"),\n AuthStatus.UNAUTHENTICATED,\n );\n }\n }\n\n // Update unauthenticated state when session is absent\n if (\n !session?.authenticated &&\n ![AuthStatus.AUTHENTICATING, AuthStatus.SIGNING_OUT].includes(\n authStatusRef.current,\n )\n ) {\n if (authStatusRef.current !== AuthStatus.UNAUTHENTICATED) {\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n }\n }\n }, [\n displayMode,\n iframeAborted,\n session?.authenticated,\n setIframeAborted,\n resolveSignInPromise,\n rejectSignInPromise,\n setAuthStatus,\n ]);\n\n // Handle logout completion\n useEffect(() => {\n if (authStatus === AuthStatus.SIGNING_OUT && !session?.authenticated) {\n setAuthStatus(AuthStatus.UNAUTHENTICATED);\n postSignOut?.().then(() => {\n setLogoutIframeIsVisible(false);\n });\n }\n }, [\n session,\n postSignOut,\n setLogoutIframeIsVisible,\n authStatus,\n setAuthStatus,\n ]);\n\n return {\n signIn,\n signOut,\n startSignIn,\n authStatus,\n displayMode,\n };\n};\n\nexport { useSignIn };\n"]}
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "@civic/auth:0.5.3";
1
+ export declare const VERSION = "@civic/auth:0.5.4-beta.1";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,sBAAsB,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,6BAA6B,CAAC"}
@@ -1,3 +1,3 @@
1
1
  // This is an auto-generated file. Do not edit.
2
- export const VERSION = "@civic/auth:0.5.3";
2
+ export const VERSION = "@civic/auth:0.5.4-beta.1";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,mBAAmB,CAAC","sourcesContent":["// This is an auto-generated file. Do not edit.\n\nexport const VERSION = \"@civic/auth:0.5.3\";\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,0BAA0B,CAAC","sourcesContent":["// This is an auto-generated file. Do not edit.\n\nexport const VERSION = \"@civic/auth:0.5.4-beta.1\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civic/auth",
3
- "version": "0.5.3",
3
+ "version": "0.5.4-beta.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",