UNPKG

@replyke/react-js

Version:

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

104 lines 5.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = require("react"); const core_1 = require("@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 } = (0, core_1.useProject)(); const dispatch = (0, core_1.useReplykeDispatch)(); const accessToken = (0, core_1.useReplykeSelector)(core_1.selectAccessToken); const [isLoading, setIsLoading] = (0, react_1.useState)(false); const [error, setError] = (0, react_1.useState)(null); // Shared helper for both /authorize and /link endpoints const startOAuthFlow = (0, react_1.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 = (0, react_1.useCallback)(({ provider, redirectAfterAuth }) => startOAuthFlow("authorize", provider, redirectAfterAuth), [startOAuthFlow]); const linkOAuthProvider = (0, react_1.useCallback)(({ provider, redirectAfterAuth }) => startOAuthFlow("link", provider, redirectAfterAuth), [startOAuthFlow]); const handleOAuthCallback = (0, react_1.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((0, core_1.setTokens)({ accessToken: fragmentAccessToken, refreshToken })); dispatch((0, core_1.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((0, core_1.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 }; } exports.default = useOAuthSignIn; //# sourceMappingURL=useOAuthSignIn.js.map