import { NextRequest, NextResponse } from 'next/server'; export interface EarnLayerProxyConfig { apiKey: string; baseUrl?: string; authUrl?: string; onError?: (error: Error) => void; } export function createEarnLayerProxy(config: EarnLayerProxyConfig) { const { apiKey, baseUrl, authUrl, onError } = config; if (!apiKey) { throw new Error('EARNLAYER_API_KEY is required for createEarnLayerProxy'); } const defaultBaseUrl = 'https://api.earnlayerai.com'; const defaultAuthUrl = 'https://app.earnlayerai.com'; const earnlayerBaseUrl = baseUrl || defaultBaseUrl; const earnlayerAuthUrl = authUrl || defaultAuthUrl; let cachedJWT: string | null = null; let jwtExpiry: number | null = null; async function getJWT(): Promise { if (cachedJWT && jwtExpiry && jwtExpiry > Date.now() / 1000 + 300) { return cachedJWT; } try { const response = await fetch(`${earnlayerAuthUrl}/api/v1/exchange-token`, { method: 'POST', headers: { 'x-api-key': apiKey } }); if (!response.ok) { let errorMessage = `Failed to exchange API key for JWT: ${response.statusText}`; if (response.status === 401) { errorMessage = 'Invalid EarnLayer API key. Please check if your EarnLayer API key is valid.'; } else if (response.status === 429) { errorMessage = 'API key rate limit exceeded. Please wait a minute for it to reset.'; } console.error('[EarnLayer SDK] Failed to fetch JWT token:', { endpoint: `${earnlayerAuthUrl}/api/v1/exchange-token`, status: response.status, error: errorMessage, timestamp: new Date().toISOString() }); throw new Error(errorMessage); } const data = await response.json(); const jwt = data.jwt; if (!jwt || typeof jwt !== 'string') { const errorMessage = 'Invalid JWT received from auth service'; console.error('[EarnLayer SDK] Failed to fetch JWT token:', { endpoint: `${earnlayerAuthUrl}/api/v1/exchange-token`, error: errorMessage, timestamp: new Date().toISOString() }); throw new Error(errorMessage); } cachedJWT = jwt; const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64').toString()); jwtExpiry = payload.exp; return jwt; } catch (error) { const err = error as Error; if (!err.message.includes('Failed to exchange') && !err.message.includes('Invalid JWT')) { console.error('[EarnLayer SDK] Failed to fetch JWT token:', { endpoint: `${earnlayerAuthUrl}/api/v1/exchange-token`, error: err.message, timestamp: new Date().toISOString() }); } onError?.(err); throw err; } } return async function handler(req: NextRequest): Promise { try { const pathname = req.nextUrl.pathname; const slug = pathname.replace('/api/earnlayer/', ''); let earnlayerUrl: string; let method = req.method; let body: string | undefined; if (slug.startsWith('initialize')) { earnlayerUrl = `${earnlayerBaseUrl}/api/conversations/initialize`; method = 'POST'; body = await req.text(); } else if (slug.startsWith('displayad/')) { const conversationId = slug.replace('displayad/', ''); const searchParams = req.nextUrl.searchParams.toString(); earnlayerUrl = `${earnlayerBaseUrl}/api/displayad/${conversationId}${searchParams ? `?${searchParams}` : ''}`; method = 'GET'; } else if (slug.startsWith('track/impression')) { earnlayerUrl = `${earnlayerBaseUrl}/api/ads/impressions/`; method = 'POST'; body = await req.text(); } else if (slug.startsWith('track/click/')) { const impressionId = slug.replace('track/click/', ''); earnlayerUrl = `${earnlayerBaseUrl}/api/ads/click/${impressionId}`; method = 'GET'; } else if (slug.startsWith('impressions/confirm')) { earnlayerUrl = `${earnlayerBaseUrl}/api/ads/impressions/confirm`; method = 'POST'; body = await req.text(); } else { return NextResponse.json({ error: 'Unknown endpoint' }, { status: 404 }); } const jwt = await getJWT(); const headers: HeadersInit = { 'Authorization': `Bearer ${jwt}`, }; if (body) { headers['Content-Type'] = 'application/json'; } const response = await fetch(earnlayerUrl, { method, headers, body, }); if (!response.ok) { const errorText = await response.text(); let errorDetails: any; try { errorDetails = JSON.parse(errorText); } catch { errorDetails = { message: errorText }; } let userFriendlyMessage: string; let errorCode: string; if (response.status === 429) { userFriendlyMessage = 'API rate limit exceeded. Please wait a minute for it to reset.'; errorCode = 'RATE_LIMIT_EXCEEDED'; } else if (response.status === 401 || response.status === 403) { userFriendlyMessage = 'Authentication failed. Please check your EarnLayer API key.'; errorCode = 'AUTH_FAILED'; } else if (response.status === 404) { userFriendlyMessage = `Resource not found: ${slug}`; errorCode = 'NOT_FOUND'; } else if (response.status >= 500) { userFriendlyMessage = 'EarnLayer service temporarily unavailable. Please try again.'; errorCode = 'SERVICE_ERROR'; } else { userFriendlyMessage = errorDetails.message || 'Request failed'; errorCode = 'REQUEST_FAILED'; } const enhancedError = { error: userFriendlyMessage, code: errorCode, status: response.status, details: errorDetails, endpoint: slug, timestamp: new Date().toISOString() }; console.error('[EarnLayer SDK Error]', enhancedError); const error = new Error(`EarnLayer API error: ${response.statusText}`); onError?.(error); return NextResponse.json(enhancedError, { status: response.status }); } const data = await response.json(); return NextResponse.json(data); } catch (error) { const err = error as Error; onError?.(err); console.error('EarnLayer proxy error:', err); // Determine appropriate status code based on error message let statusCode = 500; if (err.message?.includes('rate limit exceeded')) { statusCode = 429; } else if (err.message?.includes('Invalid EarnLayer API key')) { statusCode = 401; } return NextResponse.json( { error: err.message || 'Internal proxy error' }, { status: statusCode } ); } }; }