Skip to main content
Glama
token-manager.ts•3.15 kB
/** * Token management with automatic refresh */ import type { MerchantAuthorization } from '../types.js'; import { getAuthorization, updateAuthorizationTokens } from '../storage/database-sqljs.js'; import { encryptToken, decryptToken } from '../storage/encryption.js'; import { refreshAccessToken } from './oauth-client.js'; const TOKEN_REFRESH_THRESHOLD = 300000; // 5 minutes in milliseconds /** * Get valid access token for a merchant (with auto-refresh) */ export async function getValidAccessToken(merchantDomain: string): Promise<string> { const auth = await getAuthorization(merchantDomain); if (!auth) { throw new Error(`No authorization found for ${merchantDomain}`); } // Check if token needs refresh const now = Date.now(); const timeUntilExpiry = auth.expires_at - now; if (timeUntilExpiry < TOKEN_REFRESH_THRESHOLD) { // Token expired or expiring soon, refresh it await refreshTokenIfNeeded(auth); // Get updated authorization const updatedAuth = await getAuthorization(merchantDomain); if (!updatedAuth) { throw new Error('Authorization lost after refresh'); } return decryptToken(updatedAuth.access_token_encrypted); } // Token is still valid return decryptToken(auth.access_token_encrypted); } /** * Refresh token if needed */ async function refreshTokenIfNeeded(auth: MerchantAuthorization): Promise<void> { // Decrypt refresh token const refreshToken = decryptToken(auth.refresh_token_encrypted); try { // Call refresh endpoint const tokenResponse = await refreshAccessToken(auth.scp_endpoint, refreshToken); // Encrypt new tokens const accessTokenEncrypted = encryptToken(tokenResponse.access_token); const refreshTokenEncrypted = encryptToken(tokenResponse.refresh_token); // Calculate expiration timestamp const expiresAt = Date.now() + (tokenResponse.expires_in * 1000); // Update database await updateAuthorizationTokens( auth.merchant_domain, accessTokenEncrypted, refreshTokenEncrypted, expiresAt ); } catch (error: any) { throw new Error(`Failed to refresh token for ${auth.merchant_domain}: ${error.message}`); } } /** * Check if authorization exists and is valid */ export async function hasValidAuthorization(merchantDomain: string): Promise<boolean> { const auth = await getAuthorization(merchantDomain); if (!auth) { return false; } // Check if token is expired (with some buffer) const now = Date.now(); return auth.expires_at > now; } /** * Get authorization info (without tokens) */ export async function getAuthorizationInfo(merchantDomain: string): Promise<{ authorized: boolean; customer_email?: string; customer_id?: string; scopes?: string[]; authorized_at?: number; expires_at?: number; }> { const auth = await getAuthorization(merchantDomain); if (!auth) { return { authorized: false }; } return { authorized: true, customer_email: auth.customer_email, customer_id: auth.customer_id, scopes: auth.scopes, authorized_at: auth.created_at, expires_at: auth.expires_at }; }

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/shopper-context-protocol/scp-mcp-wrapper'

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