diff --git a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts index fc204c6a91..e12b3392d7 100644 --- a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts +++ b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts @@ -22,7 +22,10 @@ import { type AuthTokenPair } from '~/generated/graphql'; import { logDebug } from '~/utils/logDebug'; import { REST_API_BASE_URL } from '@/apollo/constant/rest-api-base-url'; +import { type ApolloManager } from '@/apollo/types/apolloManager.interface'; import { getTokenPair } from '@/apollo/utils/getTokenPair'; +import { loggerLink } from '@/apollo/utils/loggerLink'; +import { StreamingRestLink } from '@/apollo/utils/streamingRestLink'; import { i18n } from '@lingui/core'; import { t } from '@lingui/core/macro'; import { @@ -33,14 +36,17 @@ import { } from 'graphql'; import isEmpty from 'lodash.isempty'; import { getGenericOperationName, isDefined } from 'twenty-shared/utils'; +import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { cookieStorage } from '~/utils/cookie-storage'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; -import { type ApolloManager } from '@/apollo/types/apolloManager.interface'; -import { loggerLink } from '@/apollo/utils/loggerLink'; -import { StreamingRestLink } from '@/apollo/utils/streamingRestLink'; const logger = loggerLink(() => 'Twenty'); +// Shared across all ApolloFactory instances so concurrent +// UNAUTHENTICATED errors from /graphql and /metadata clients +// deduplicate into a single renewal request. +let renewalPromise: Promise | null = null; + export interface Options extends ApolloClientOptions { onError?: (err: readonly GraphQLFormattedError[] | undefined) => void; onNetworkError?: (err: Error | ServerParseError | ServerError) => void; @@ -145,8 +151,11 @@ export class ApolloFactory implements ApolloManager { operation: Operation, forward: (operation: Operation) => Observable, ) => { - return fromPromise( - renewToken(uri, getTokenPair()) + if (!renewalPromise) { + // Always renew through /metadata since the RenewToken is only exposed there + const graphqlUri = `${REACT_APP_SERVER_BASE_URL}/metadata`; + + renewalPromise = renewToken(graphqlUri, getTokenPair()) .then((tokens) => { if (isDefined(tokens)) { // eslint-disable-next-line no-console @@ -161,8 +170,13 @@ export class ApolloFactory implements ApolloManager { 'Failed to renew token, triggering unauthenticated error from handleTokenRenewal', ); onUnauthenticatedError?.(); - }), - ).flatMap(() => forward(operation)); + }) + .finally(() => { + renewalPromise = null; + }); + } + + return fromPromise(renewalPromise).flatMap(() => forward(operation)); }; const sendToSentry = ({