diff --git a/packages/platform/atoms/cal-provider/BaseCalProvider.tsx b/packages/platform/atoms/cal-provider/BaseCalProvider.tsx index f89b77302d..ca2267d3e9 100644 --- a/packages/platform/atoms/cal-provider/BaseCalProvider.tsx +++ b/packages/platform/atoms/cal-provider/BaseCalProvider.tsx @@ -42,6 +42,9 @@ export type CalProviderProps = { options: { refreshUrl?: string; apiUrl: string; readingDirection?: "ltr" | "rtl" }; autoUpdateTimezone?: boolean; onTimezoneChange?: () => void; + onTokenRefreshStart?: () => void; + onTokenRefreshSuccess?: () => void; + onTokenRefreshError?: (error: string) => void; version?: API_VERSIONS_ENUM; organizationId?: number; isEmbed?: boolean; @@ -57,6 +60,9 @@ export function BaseCalProvider({ language = EN, organizationId, onTimezoneChange, + onTokenRefreshStart, + onTokenRefreshSuccess, + onTokenRefreshError, isEmbed, }: CalProviderProps) { const [error, setError] = useState(""); @@ -100,6 +106,9 @@ export function BaseCalProvider({ onSuccess: () => { setError(""); }, + onTokenRefreshStart, + onTokenRefreshSuccess, + onTokenRefreshError, clientId, }); diff --git a/packages/platform/atoms/cal-provider/CalProvider.tsx b/packages/platform/atoms/cal-provider/CalProvider.tsx index 5d1dca821d..13c86528f4 100644 --- a/packages/platform/atoms/cal-provider/CalProvider.tsx +++ b/packages/platform/atoms/cal-provider/CalProvider.tsx @@ -23,6 +23,9 @@ const queryClient = new QueryClient(); * @param {string} [options.refreshUrl] - The url point to your refresh endpoint. - Optional, required if accessToken is provided. * @param {boolean} [autoUpdateTimezone=true] - Whether to automatically update the timezone. - Optional * @param {function} props.onTimezoneChange - The callback function for timezone change. - Optional + * @param {function} props.onTokenRefreshStart - The callback function called when token refresh starts. - Optional + * @param {function} props.onTokenRefreshSuccess - The callback function called when token refresh succeeds. - Optional + * @param {function} props.onTokenRefreshError - The callback function called when token refresh fails. - Optional * @param {ReactNode} props.children - The child components. - Optional * @returns {JSX.Element} The rendered CalProvider component. */ @@ -35,6 +38,9 @@ export function CalProvider({ labels, language = "en", onTimezoneChange, + onTokenRefreshStart, + onTokenRefreshSuccess, + onTokenRefreshError, version = VERSION_2024_06_14, organizationId, isEmbed = false, @@ -59,6 +65,9 @@ export function CalProvider({ isEmbed={isEmbed} autoUpdateTimezone={autoUpdateTimezone} onTimezoneChange={onTimezoneChange} + onTokenRefreshStart={onTokenRefreshStart} + onTokenRefreshSuccess={onTokenRefreshSuccess} + onTokenRefreshError={onTokenRefreshError} clientId={clientId} accessToken={accessToken} options={options} diff --git a/packages/platform/atoms/hooks/useOAuthFlow.ts b/packages/platform/atoms/hooks/useOAuthFlow.ts index 5c01471efa..aa154ef25a 100644 --- a/packages/platform/atoms/hooks/useOAuthFlow.ts +++ b/packages/platform/atoms/hooks/useOAuthFlow.ts @@ -1,5 +1,5 @@ import type { AxiosError, AxiosRequestConfig } from "axios"; -// eslint-disable-next-line no-restricted-imports + import { debounce } from "lodash"; import { useEffect, useState } from "react"; import usePrevious from "react-use/lib/usePrevious"; @@ -13,11 +13,23 @@ export interface useOAuthProps { refreshUrl?: string; onError?: (error: string) => void; onSuccess?: () => void; + onTokenRefreshStart?: () => void; + onTokenRefreshSuccess?: () => void; + onTokenRefreshError?: (error: string) => void; clientId: string; } const debouncedRefresh = debounce(http.refreshTokens, 10000, { leading: true, trailing: false }); -export const useOAuthFlow = ({ accessToken, refreshUrl, clientId, onError, onSuccess }: useOAuthProps) => { +export const useOAuthFlow = ({ + accessToken, + refreshUrl, + clientId, + onError, + onSuccess, + onTokenRefreshStart, + onTokenRefreshSuccess, + onTokenRefreshError, +}: useOAuthProps) => { const [isRefreshing, setIsRefreshing] = useState(false); const [clientAccessToken, setClientAccessToken] = useState(""); const prevAccessToken = usePrevious(accessToken); @@ -28,16 +40,20 @@ export const useOAuthFlow = ({ accessToken, refreshUrl, clientId, onError, onSuc const originalRequest = err.config as AxiosRequestConfig; if (refreshUrl && err.response?.status === 498 && !isRefreshing) { setIsRefreshing(true); + onTokenRefreshStart?.(); const refreshedToken = await debouncedRefresh(refreshUrl); if (refreshedToken) { setClientAccessToken(refreshedToken); onSuccess?.(); + onTokenRefreshSuccess?.(); return http.instance({ ...originalRequest, headers: { ...originalRequest.headers, Authorization: `Bearer ${refreshedToken}` }, }); } else { - onError?.("Invalid Refresh Token."); + const errorMessage = "Invalid Refresh Token."; + onError?.(errorMessage); + onTokenRefreshError?.(errorMessage); } setIsRefreshing(false); @@ -51,33 +67,35 @@ export const useOAuthFlow = ({ accessToken, refreshUrl, clientId, onError, onSuc http.responseInterceptor.eject(interceptorId); } }; - }, [clientAccessToken, isRefreshing, refreshUrl, onError, onSuccess]); + }, [clientAccessToken, isRefreshing, refreshUrl, onError, onSuccess, onTokenRefreshStart, onTokenRefreshSuccess, onTokenRefreshError]); useEffect(() => { if (accessToken && http.getUrl() && prevAccessToken !== accessToken) { http.setAuthorizationHeader(accessToken); - try { - http - .get(`/provider/${clientId}/access-token`) - .catch(async (err: AxiosError) => { - if ((err.response?.status === 498 || err.response?.status === 401) && refreshUrl) { - setIsRefreshing(true); - const refreshedToken = await http.refreshTokens(refreshUrl); - if (refreshedToken) { - setClientAccessToken(refreshedToken); - onSuccess?.(); - } else { - onError?.("Invalid Refresh Token."); - } - setIsRefreshing(false); + http + .get(`/provider/${clientId}/access-token`) + .catch(async (err: AxiosError) => { + if ((err.response?.status === 498 || err.response?.status === 401) && refreshUrl) { + setIsRefreshing(true); + onTokenRefreshStart?.(); + const refreshedToken = await http.refreshTokens(refreshUrl); + if (refreshedToken) { + setClientAccessToken(refreshedToken); + onSuccess?.(); + onTokenRefreshSuccess?.(); + } else { + const errorMessage = "Invalid Refresh Token."; + onError?.(errorMessage); + onTokenRefreshError?.(errorMessage); } - }) - .finally(() => { - setClientAccessToken(accessToken); - }); - } catch (err) {} + setIsRefreshing(false); + } + }) + .finally(() => { + setClientAccessToken(accessToken); + }); } - }, [accessToken, clientId, refreshUrl, prevAccessToken, onError, onSuccess]); + }, [accessToken, clientId, refreshUrl, prevAccessToken, onError, onSuccess, onTokenRefreshStart, onTokenRefreshSuccess, onTokenRefreshError]); return { isRefreshing, currentAccessToken: clientAccessToken }; };