bf39489899
Add three new optional callbacks to CalProvider that enable users to react during the access token refresh lifecycle: - onTokenRefreshStart: Called when token refresh begins - onTokenRefreshSuccess: Called when token refresh succeeds - onTokenRefreshError: Called when token refresh fails with error message These callbacks are invoked in both token refresh locations: 1. Response interceptor (when API returns 498 status) 2. Initial access token validation The callbacks are properly passed through the component hierarchy: CalProvider -> BaseCalProvider -> useOAuthFlow hook Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import type { AxiosError, AxiosRequestConfig } from "axios";
|
|
|
|
import { debounce } from "lodash";
|
|
import { useEffect, useState } from "react";
|
|
import usePrevious from "react-use/lib/usePrevious";
|
|
|
|
import type { ApiResponse } from "@calcom/platform-types";
|
|
|
|
import http from "../lib/http";
|
|
|
|
export interface useOAuthProps {
|
|
accessToken?: string;
|
|
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,
|
|
onTokenRefreshStart,
|
|
onTokenRefreshSuccess,
|
|
onTokenRefreshError,
|
|
}: useOAuthProps) => {
|
|
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
|
|
const [clientAccessToken, setClientAccessToken] = useState<string>("");
|
|
const prevAccessToken = usePrevious(accessToken);
|
|
useEffect(() => {
|
|
const interceptorId =
|
|
clientAccessToken && http.getAuthorizationHeader()
|
|
? http.responseInterceptor.use(undefined, async (err: AxiosError) => {
|
|
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 {
|
|
const errorMessage = "Invalid Refresh Token.";
|
|
onError?.(errorMessage);
|
|
onTokenRefreshError?.(errorMessage);
|
|
}
|
|
|
|
setIsRefreshing(false);
|
|
}
|
|
return Promise.reject(err);
|
|
})
|
|
: "";
|
|
|
|
return () => {
|
|
if (interceptorId) {
|
|
http.responseInterceptor.eject(interceptorId);
|
|
}
|
|
};
|
|
}, [clientAccessToken, isRefreshing, refreshUrl, onError, onSuccess, onTokenRefreshStart, onTokenRefreshSuccess, onTokenRefreshError]);
|
|
|
|
useEffect(() => {
|
|
if (accessToken && http.getUrl() && prevAccessToken !== accessToken) {
|
|
http.setAuthorizationHeader(accessToken);
|
|
http
|
|
.get<ApiResponse>(`/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);
|
|
}
|
|
setIsRefreshing(false);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
setClientAccessToken(accessToken);
|
|
});
|
|
}
|
|
}, [accessToken, clientId, refreshUrl, prevAccessToken, onError, onSuccess, onTokenRefreshStart, onTokenRefreshSuccess, onTokenRefreshError]);
|
|
|
|
return { isRefreshing, currentAccessToken: clientAccessToken };
|
|
};
|