UNPKG

@replyke/react-js

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

102 lines 4.96 kB
import { useCallback, useState } from "react"; import { useProject, useReplykeDispatch, useReplykeSelector, setTokens, setInitialized, selectAccessToken, requestNewAccessTokenThunk, } from "@replyke/core"; const BASE_URL = "https://api.replyke.com/v7"; /** * Web-only hook for OAuth sign-in and identity linking. * Uses window.location for redirect-based OAuth flow. * * Usage (sign-in): * const { initiateOAuth, handleOAuthCallback } = useOAuthSignIn(); * await initiateOAuth("google", "https://myapp.com/auth/callback"); * * Usage (link provider to current user): * const { linkOAuthProvider, handleOAuthCallback } = useOAuthSignIn(); * await linkOAuthProvider("github", "https://myapp.com/settings"); * * On the callback page (component mount): * useEffect(() => { handleOAuthCallback(); }, []); */ function useOAuthSignIn() { const { projectId } = useProject(); const dispatch = useReplykeDispatch(); const accessToken = useReplykeSelector(selectAccessToken); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // Shared helper for both /authorize and /link endpoints const startOAuthFlow = useCallback(async (endpoint, provider, redirectAfterAuth) => { if (!projectId) { setError("No projectId available."); return; } if (endpoint === "link" && !accessToken) { setError("Must be authenticated to link an OAuth provider."); return; } setIsLoading(true); setError(null); try { const redirect = redirectAfterAuth || window.location.href; // /authorize is unauthenticated, /link requires the access token const headers = { "Content-Type": "application/json", }; if (endpoint === "link") { headers["Authorization"] = `Bearer ${accessToken}`; } const response = await fetch(`${BASE_URL}/${projectId}/oauth/${endpoint}`, { method: "POST", headers, body: JSON.stringify({ provider, redirectAfterAuth: redirect }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Failed to initiate OAuth"); } const data = await response.json(); // Redirect browser to provider's authorization page. // isLoading intentionally stays true since we're navigating away. window.location.href = data.authorizationUrl; } catch (err) { setError(err.message); setIsLoading(false); } }, [projectId, accessToken]); const initiateOAuth = useCallback(({ provider, redirectAfterAuth }) => startOAuthFlow("authorize", provider, redirectAfterAuth), [startOAuthFlow]); const linkOAuthProvider = useCallback(({ provider, redirectAfterAuth }) => startOAuthFlow("link", provider, redirectAfterAuth), [startOAuthFlow]); const handleOAuthCallback = useCallback(() => { // Tokens arrive in the URL fragment (#accessToken=...&refreshToken=...) // Errors arrive in query params (?error=...&error_description=...) const hash = window.location.hash.substring(1); // Remove leading # const fragmentParams = new URLSearchParams(hash); const queryParams = new URLSearchParams(window.location.search); const fragmentAccessToken = fragmentParams.get("accessToken"); const refreshToken = fragmentParams.get("refreshToken"); const oauthError = queryParams.get("error"); if (oauthError) { setError(queryParams.get("error_description") || oauthError); // Clean URL window.history.replaceState({}, "", window.location.pathname); return false; } if (fragmentAccessToken && refreshToken) { // Store tokens in Redux. The AccountManager (via useAccountSync) // will detect the new tokens and persist them to localStorage. dispatch(setTokens({ accessToken: fragmentAccessToken, refreshToken })); dispatch(setInitialized(true)); // Fetch user profile so useAccountSync can persist the account. // The thunk reads the just-set refresh token from Redux, calls the // server, and dispatches setUser + setUserInUserSlice on success. if (projectId) { dispatch(requestNewAccessTokenThunk({ projectId })); } // Clean URL (remove fragment with tokens) window.history.replaceState({}, "", window.location.pathname); return true; } return false; }, [dispatch, projectId]); return { initiateOAuth, linkOAuthProvider, handleOAuthCallback, isLoading, error }; } export default useOAuthSignIn; //# sourceMappingURL=useOAuthSignIn.js.map