'use client'; import { AxiosError } from 'axios'; import React, { DependencyList, useEffect, useState } from 'react'; import { kratos } from './sdk/kratos'; import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime'; export const HandleError = ( getFlow: | ((flowId: string) => Promise) | undefined = undefined, setFlow: React.Dispatch> | undefined = undefined, defaultNav: string | undefined = undefined, fatalToError = false, router: AppRouterInstance, ) => { return async ( error: AxiosError, ): Promise => { if (!error.response || error.response?.status === 0) { window.location.href = `/flow/error?error=${encodeURIComponent( JSON.stringify(error.response), )}`; return Promise.resolve(); } const responseData = error.response?.data || {}; switch (error.response?.status) { case 400: { if (responseData.error?.id == 'session_already_available') { router.push('/'); return Promise.resolve(); } // the request could contain invalid parameters which would set error messages in the flow if (setFlow !== undefined) { console.warn('sdkError 400: update flow data'); setFlow(responseData); return Promise.resolve(); } break; } // we have no session or the session is invalid case 401: { console.warn('handleError hook 401: Navigate to /login'); router.push('/flow/login'); return Promise.resolve(); } case 403: { // the user might have a session, but would require 2FA (Two-Factor Authentication) if (responseData.error?.id === 'session_aal2_required') { router.push('/flow/login?aal2=true'); router.refresh(); return Promise.resolve(); } if ( responseData.error?.id === 'session_refresh_required' && responseData.redirect_browser_to ) { console.warn( 'sdkError 403: Redirect browser to', responseData.redirect_browser_to, ); window.location = responseData.redirect_browser_to; return Promise.resolve(); } break; } case 404: { console.warn('sdkError 404: Navigate to Error'); const errorMsg = { data: error.response?.data || error, status: error.response?.status, statusText: error.response?.statusText, url: window.location.href, }; router.push( `/flow/error?error=${encodeURIComponent(JSON.stringify(errorMsg))}`, ); return Promise.resolve(); } // error.id handling // "self_service_flow_expired" case 410: { if (getFlow !== undefined && responseData.use_flow_id !== undefined) { console.warn('sdkError 410: Update flow'); return getFlow(responseData.use_flow_id).catch((error) => { // Something went seriously wrong - log and redirect to defaultNav if possible console.error(error); if (defaultNav !== undefined) { router.push(defaultNav); } else { // Rethrow error when can't navigate and let caller handle throw error; } }); } else if (defaultNav !== undefined) { console.warn('sdkError 410: Navigate to', defaultNav); router.push(defaultNav); return Promise.resolve(); } break; } // we need to parse the response and follow the `redirect_browser_to` URL // this could be when the user needs to perform a 2FA challenge // or passwordless login case 422: { if (responseData.redirect_browser_to !== undefined) { const currentUrl = new URL(window.location.href); const redirect = new URL(responseData.redirect_browser_to); // host name has changed, then change location if (currentUrl.host !== redirect.host) { console.warn('sdkError 422: Host changed redirect'); window.location = responseData.redirect_browser_to; return Promise.resolve(); } // Path has changed if (currentUrl.pathname !== redirect.pathname) { console.warn('sdkError 422: Update path'); router.push(redirect.pathname + redirect.search); return Promise.resolve(); } // for webauthn we need to reload the flow const flowId = redirect.searchParams.get('flow'); if (flowId != null && getFlow !== undefined) { // get new flow data based on the flow id in the redirect url console.warn('sdkError 422: Update flow'); return getFlow(flowId).catch((error) => { // Something went seriously wrong - log and redirect to defaultNav if possible console.error(error); if (defaultNav !== undefined) { router.push(defaultNav); } else { // Rethrow error when can't navigate and let caller handle throw error; } }); } else { console.warn('sdkError 422: Redirect browser to'); window.location = responseData.redirect_browser_to; return Promise.resolve(); } } } } console.error(error); if (fatalToError) { console.warn('sdkError: fatal error redirect to /error'); router.push('/flow/error?id=' + encodeURI(error.response?.data.error?.id)); return Promise.resolve(); } throw error; }; }; // Returns a function which will log the user out export function LogoutLink(deps?: DependencyList) { const [logoutToken, setLogoutToken] = useState(''); useEffect(() => { kratos .createBrowserLogoutFlow() .then(({ data }) => { setLogoutToken(data.logout_token); }) .catch((err: AxiosError) => { switch (err.response?.status) { case 401: // do nothing, the user is not logged in return; } // Something else happened! return Promise.reject(err); }); }, deps); return () => { if (logoutToken) { kratos .updateLogoutFlow({ token: logoutToken }) .then(() => window.location.href = '/flow/login'); } }; }