Files
Lauris SkraucisGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>cubic-dev-ai[bot] <1082092+cubic-dev-ai[bot]@users.noreply.github.com>Rajiv Sahal
fc602d3b03 feat: OAuth 2.0 support for atoms (#27158)
* fix: useOAuthClient support OAuth 2.0

* fix: cannot read properties of undefined (reading NEXT_PUBLIC_IS_E2E)

* fix: allow OAuth 2.0 token to connect gcal or ms calendar

* fix: allow OAuth 2.0 token to save gcal or ms calendar credentials

* refactor: dont set oauth id header for OAuth 2.0

* fix: calendar events not showing and emails not sent

* feat: CalOAuth2Provider

* chore: make OAuth 2.0 work in examples app

* chore: refresh OAuth 2.0 tokens

* docs: running examples app with oauth 2.0

* fix: remove sensitive console.log statements that leak secrets

Remove logging of:
- OAuth authorization codes (oauth2-user.ts)
- Token-bearing exchange responses (oauth2-user.ts)
- /me response data containing PII (oauth2-user.ts)
- OAuth2 refresh response with tokens (refresh.ts)
- Response payload with access tokens (_app.tsx)

Addresses Cubic AI review feedback for issues with confidence >= 9/10

Co-Authored-By: unknown <>

* docs: update readme

* fix: implemente cubic feedback

* fix: seed script import

* fix: seed script pkce

* fix: correct typos and SQLite capitalization in OAuth2 README (#27176)

Co-authored-by: cubic-dev-ai[bot] <1082092+cubic-dev-ai[bot]@users.noreply.github.com>

* refactor: dont return name in public oauth endpoint

* docs: CalOAuthProvider

* chore: add NEXT_PUBLIC_IS_E2E constant to test

* docs: fix duplicated 'or' in Cal OAuth Provider documentation (#27177)

Co-authored-by: cubic-dev-ai[bot] <1082092+cubic-dev-ai[bot]@users.noreply.github.com>

* revert: is e2e constant

* fix: typecheck

* refactor: example app users select

* update readme

* chore: update oauth atoms readme

* refactor: enable booking managed event types with user.username instead of profile.username

* fix: EventTypeSettings when viewing round robin

* test: add e2e tests for atoms-oauth2 controller

Co-Authored-By: [email protected] <[email protected]>

* fix: correct error message path in atoms-oauth2 e2e test

Co-Authored-By: [email protected] <[email protected]>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <1082092+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Rajiv Sahal <[email protected]>
2026-02-04 12:54:15 +01:00

113 lines
3.8 KiB
TypeScript

"use client";
import { VERSION_2024_06_14 } from "@calcom/platform-constants";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import { useEffect } from "react";
import http from "../lib/http";
import type { BaseCalProviderProps } from "./BaseCalProvider";
import { BaseCalProvider } from "./BaseCalProvider";
import type { translationKeys } from "./languages";
function isRetryableError(error: unknown): boolean {
if (!isAxiosError(error) || !error.response) {
// network errors or timeouts — retryable
return true;
}
const status = error.response.status;
// retry on 408 (timeout), 429 (rate limit), and 5xx (server errors)
return status === 408 || status === 429 || status >= 500;
}
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount: number, error: unknown): boolean => {
if (!isRetryableError(error)) return false;
return failureCount < 3;
},
retryDelay: (attempt: number) => Math.min(1000 * 2 ** attempt, 30000),
},
mutations: {
retry: (failureCount: number, error: unknown): boolean => {
if (!isRetryableError(error)) return false;
return failureCount < 1;
},
retryDelay: (attempt: number) => Math.min(1000 * 2 ** attempt, 30000),
},
},
});
type CalProviderProps = Omit<BaseCalProviderProps, "isOAuth2">;
/**
* Renders a CalProvider component.
*
* @component
* @param {string} props.clientId - The platform oauth client ID.
* @param {string} props.accessToken - The access token of your managed user. - Optional
* @param {object} props.options - The options object.
* @param {string} [options.apiUrl] - The API URL. https://api.cal.com/v2
* @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.
*/
export function CalProvider({
clientId,
accessToken,
options,
children,
autoUpdateTimezone = true,
labels,
language = "en",
onTimezoneChange,
onTokenRefreshStart,
onTokenRefreshSuccess,
onTokenRefreshError,
version = VERSION_2024_06_14,
organizationId,
isEmbed = false,
}: CalProviderProps): JSX.Element {
useEffect(() => {
http.setVersionHeader(version);
}, [version]);
useEffect(() => {
if (accessToken) {
queryClient.resetQueries();
}
}, [accessToken]);
useEffect(() => {
http.setPlatformEmbedHeader(isEmbed);
}, [isEmbed]);
return (
<QueryClientProvider client={queryClient}>
<BaseCalProvider
isEmbed={isEmbed}
isOAuth2={false}
autoUpdateTimezone={autoUpdateTimezone}
onTimezoneChange={onTimezoneChange}
onTokenRefreshStart={onTokenRefreshStart}
onTokenRefreshSuccess={onTokenRefreshSuccess}
onTokenRefreshError={onTokenRefreshError}
clientId={clientId}
accessToken={accessToken}
options={options}
version={version}
labels={labels as Record<translationKeys, string>}
language={language}
organizationId={organizationId}
>
{children}
</BaseCalProvider>
</QueryClientProvider>
);
}