import { getSupabaseConfig, supabaseFetch } from './_supabase.js'; import { json } from './_session.js'; const CORS_HEADERS = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'POST, OPTIONS', 'access-control-allow-headers': 'content-type, authorization' }; function cleanText(value, maxLength = 220) { return String(value || '').trim().slice(0, maxLength); } function cleanOrigin(value) { try { const url = new URL(String(value || '')); return url.origin.slice(0, 220); } catch (error) { return cleanText(value, 220); } } function cleanNumber(value) { const numeric = Number(value); return Number.isFinite(numeric) && numeric >= 0 ? numeric : null; } function cleanStatus(value) { const status = cleanText(value, 32).toLowerCase(); return ['confirmed', 'pending', 'rejected', 'refunded'].includes(status) ? status : 'confirmed'; } async function readJson(request) { const text = await request.text(); if (!text || text.length > 32_000) return {}; return JSON.parse(text); } function telemetryRow(payload, request) { const txHash = cleanText(payload.txHash || payload.transactionId || payload.transaction_hash, 180); const eventId = cleanText(payload.eventId || payload.event_id || txHash || crypto.randomUUID(), 180); const amountUsd = cleanNumber(payload.amountUsd ?? payload.amount_usd); const amountNative = cleanNumber(payload.amountNative ?? payload.amount_native); const origin = cleanOrigin(payload.origin || request.headers.get('origin') || request.headers.get('referer') || ''); return { event_id: eventId, client_id: cleanText(payload.clientId || payload.client_id || 'public-widget', 120), integration_origin: origin, provider: cleanText(payload.provider || 'unknown', 80), project_slug: cleanText(payload.projectSlug || payload.project_slug || payload.nonprofitSlug || '', 160), project_name: cleanText(payload.projectName || payload.project_name || payload.nonprofitName || '', 220), tx_hash: txHash, currency: cleanText(payload.currency || 'USD', 16).toUpperCase(), amount_usd: amountUsd, amount_native: amountNative, network: cleanText(payload.network || 'unknown', 64), status: cleanStatus(payload.status), access_expires_at: cleanText(payload.accessExpiresAt || payload.access_expires_at || '', 64) || null, confirmed_at: cleanText(payload.confirmedAt || payload.confirmed_at || new Date().toISOString(), 64), updated_at: new Date().toISOString(), raw_event: payload }; } export async function onRequestOptions() { return new Response(null, { status: 204, headers: CORS_HEADERS }); } export async function onRequestPost({ request, env }) { if (env.SOULWALL_TELEMETRY_ENABLED === '0') { return json({ ok: false, accepted: false, error: 'Telemetry is disabled for this environment.' }, 503, CORS_HEADERS); } let payload; try { payload = await readJson(request); } catch (error) { return json({ ok: false, error: 'Invalid JSON payload.' }, 400, CORS_HEADERS); } const config = getSupabaseConfig(env); const row = telemetryRow(payload, request); if (!row.tx_hash && !row.event_id) { return json({ ok: false, error: 'A transaction hash or event id is required.' }, 400, CORS_HEADERS); } if (!config) { return json({ ok: true, accepted: true, stored: false, reason: 'Supabase ledger is not configured for this environment.' }, 202, CORS_HEADERS); } try { const rows = await supabaseFetch(config, '/rest/v1/soulwall_ledger?on_conflict=event_id', { method: 'POST', headers: { prefer: 'resolution=merge-duplicates,return=representation' }, body: JSON.stringify(row) }); return json({ ok: true, accepted: true, stored: true, event: Array.isArray(rows) ? rows[0] || null : null }, 200, CORS_HEADERS); } catch (error) { return json({ ok: false, accepted: false, stored: false, error: error?.message || 'Ledger write failed.' }, 500, CORS_HEADERS); } }