Skip to main content
Glama
proxy.ts4.79 kB
import type { IntlayerConfig } from '@intlayer/types'; import type { FetcherOptions } from './fetcher'; import { getIntlayerAPI } from './getIntlayerAPI'; import type { IntlayerAPI } from './getIntlayerAPI/index'; import { getOAuthAPI } from './getIntlayerAPI/oAuth'; type OAuthTokenLike = { accessToken?: string; accessTokenExpiresAt?: string | Date; expires_in?: number; expiresIn?: number; expiresAt?: string | Date; }; const ONE_MINUTE_MS = 60_000; /** * Returns the expiration timestamp in ms from an OAuth token-like object. */ const getExpiryTimestamp = ( token: OAuthTokenLike | undefined ): number | undefined => { if (!token) return undefined; const dateLike = (token.accessTokenExpiresAt ?? token.expiresAt) as | string | Date | undefined; if (dateLike) { const ts = typeof dateLike === 'string' ? Date.parse(dateLike) : dateLike.getTime?.(); if (typeof ts === 'number' && Number.isFinite(ts)) return ts; } const seconds = token.expires_in ?? token.expiresIn; if (typeof seconds === 'number' && Number.isFinite(seconds)) { return Date.now() + seconds * 1000; } return undefined; }; let currentAccessToken: string | undefined; let currentExpiryTs: number | undefined; let pendingRefresh: Promise<void> | undefined; /** * Build an auto-auth proxy around getIntlayerAPI that: * - Fetches an OAuth2 token when needed * - Injects Authorization header for each request * - Refreshes token proactively when near expiry * * The returned API matches the shape of getIntlayerAPI. */ export const getIntlayerAPIProxy = ( _baseAuthOptions: FetcherOptions = {}, intlayerConfig?: IntlayerConfig ): IntlayerAPI => { // Use a shared mutable auth options object captured by the API closures const authOptionsRef: FetcherOptions = { ..._baseAuthOptions }; const hasCMSAuth = intlayerConfig?.editor?.clientId && intlayerConfig?.editor?.clientSecret; const baseApi = getIntlayerAPI(authOptionsRef, intlayerConfig); const needsRefresh = (): boolean => { if (!currentAccessToken) return true; if (!currentExpiryTs) return false; // If unknown, assume usable until failure return Date.now() + ONE_MINUTE_MS >= currentExpiryTs; // refresh 1 min before expiry }; const refreshToken = async (): Promise<void> => { const doRefresh = async () => { const authApi = getOAuthAPI(intlayerConfig); const res = await authApi.getOAuth2AccessToken(); const tokenData = res?.data as OAuthTokenLike | undefined; currentAccessToken = tokenData?.accessToken; currentExpiryTs = getExpiryTimestamp(tokenData); }; if (!pendingRefresh) { pendingRefresh = doRefresh().finally(() => { pendingRefresh = undefined; }); } await pendingRefresh; }; const ensureValidToken = async () => { if (needsRefresh()) { await refreshToken(); } }; const applyAuthHeaderToRef = () => { if (!currentAccessToken) return; authOptionsRef.headers = { ...(authOptionsRef.headers ?? {}), Authorization: `Bearer ${currentAccessToken}`, } as HeadersInit; }; const wrapSection = <T extends Record<string, unknown>>( section: T, skipAuth = !hasCMSAuth ): T => { return new Proxy(section, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (typeof value === 'function') { // Wrap section method to inject token and headers return async (...args: unknown[]) => { if (!skipAuth) { await ensureValidToken(); applyAuthHeaderToRef(); } try { return await value.apply(target, args); } catch (err) { // Best-effort retry: if token might be stale, refresh once and retry if (!skipAuth) { await refreshToken(); applyAuthHeaderToRef(); return await value.apply(target, args); } throw err; } }; } return value; }, }); }; return { organization: wrapSection(baseApi.organization), project: wrapSection(baseApi.project), user: wrapSection(baseApi.user), oAuth: wrapSection(baseApi.oAuth, true), // do NOT inject auth for token endpoint dictionary: wrapSection(baseApi.dictionary), stripe: wrapSection(baseApi.stripe), ai: wrapSection(baseApi.ai), tag: wrapSection(baseApi.tag), search: wrapSection(baseApi.search), editor: wrapSection(baseApi.editor), newsletter: wrapSection(baseApi.newsletter), audit: wrapSection(baseApi.audit), } as IntlayerAPI; }; export type IntlayerAPIProxy = ReturnType<typeof getIntlayerAPIProxy>;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server