/* eslint-disable no-console */ /** * Test script for Fireflies webhook against local Twenty instance * * Usage: * yarn test:webhook * # or * npx tsx scripts/test-webhook.ts * * Prerequisites: * 1. Twenty server running on http://localhost:3000 * 2. Fireflies app synced: npx twenty-cli app sync * 3. Custom fields created: yarn setup:fields * 4. API key configured (get from Settings > Developers > API Keys) * 5. Environment variables set (copy .env.example to .env and fill values) */ import * as crypto from 'crypto'; import * as dotenv from 'dotenv'; import { existsSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; // Get __dirname equivalent for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables const envPath = join(__dirname, '..', '.env'); if (existsSync(envPath)) { dotenv.config({ path: envPath }); } else { console.warn('โš ๏ธ .env file not found, using environment variables'); } // Configuration const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000'; const TWENTY_API_KEY = process.env.TWENTY_API_KEY; const FIREFLIES_WEBHOOK_SECRET = process.env.FIREFLIES_WEBHOOK_SECRET || 'test_secret'; const _FIREFLIES_API_KEY = process.env.FIREFLIES_API_KEY || 'test_api_key'; // Test meeting data (simulating Fireflies API response) const TEST_MEETING_ID = 'test-meeting-local-' + Date.now(); const TEST_WEBHOOK_PAYLOAD = { meetingId: TEST_MEETING_ID, eventType: 'Transcription completed', clientReferenceId: 'test-client-ref', }; // Mock Fireflies GraphQL API response const MOCK_FIREFLIES_RESPONSE = { data: { meeting: { id: TEST_MEETING_ID, title: 'Local Test Meeting', date: new Date().toISOString(), duration: 1800, // 30 minutes participants: [ { email: 'test1@example.com', name: 'Test User One' }, { email: 'test2@example.com', name: 'Test User Two' }, ], organizer_email: 'organizer@example.com', summary: { action_items: ['Complete integration testing', 'Review webhook logs'], keywords: ['testing', 'integration', 'webhook'], overview: 'This is a test meeting to verify the Fireflies webhook integration.', gist: 'Quick test summary', topics_discussed: ['Webhook testing', 'Integration verification'], meeting_type: 'Test', }, analytics: { sentiments: { positive_pct: 75, negative_pct: 5, neutral_pct: 20, }, }, transcript_url: 'https://app.fireflies.ai/transcript/' + TEST_MEETING_ID, recording_url: 'https://app.fireflies.ai/recording/' + TEST_MEETING_ID, summary_status: 'ready', }, }, }; // Generate HMAC signature const generateHMACSignature = (body: string, secret: string): string => { const signature = crypto .createHmac('sha256', secret) .update(body, 'utf8') .digest('hex'); return `sha256=${signature}`; }; // Mock Fireflies API fetch (currently unused but kept for reference) // In production, you'd need to mock this at the network level const _mockFirefliesFetch = async (url: string, options?: RequestInit) => { if (url.includes('graphql.fireflies.ai')) { // Return mock Fireflies API response return new Response(JSON.stringify(MOCK_FIREFLIES_RESPONSE), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } // For Twenty API calls, use real fetch return fetch(url, options); }; const main = async () => { console.log('๐Ÿงช Testing Fireflies Webhook Against Local Twenty Instance\n'); console.log(`๐Ÿ“ Server URL: ${SERVER_URL}`); console.log(`๐Ÿ”‘ API Key: ${TWENTY_API_KEY ? 'โœ… Configured' : 'โŒ Missing'}`); console.log(`๐Ÿ” Webhook Secret: ${FIREFLIES_WEBHOOK_SECRET ? 'โœ… Configured' : 'โš ๏ธ Using test secret'}\n`); // Validation if (!TWENTY_API_KEY) { console.error('โŒ Error: TWENTY_API_KEY is required'); console.error(' Get your API key from: Settings > Developers > API Keys'); process.exit(1); } // Prepare webhook payload const body = JSON.stringify(TEST_WEBHOOK_PAYLOAD); const signature = generateHMACSignature(body, FIREFLIES_WEBHOOK_SECRET); console.log('๐Ÿ“ค Sending webhook payload:'); console.log(JSON.stringify(TEST_WEBHOOK_PAYLOAD, null, 2)); console.log(`\n๐Ÿ” HMAC Signature: ${signature}\n`); // Check if server is reachable try { const healthCheck = await fetch(`${SERVER_URL}/api/health`); if (!healthCheck.ok) { throw new Error(`Server health check failed: ${healthCheck.status}`); } console.log('โœ… Server is reachable\n'); } catch { console.error(`โŒ Cannot reach server at ${SERVER_URL}`); console.error(' Make sure Twenty is running: cd twenty && yarn dev'); process.exit(1); } // Note: In a real test, we'd intercept fetch calls // For now, we'll make a direct request to the webhook endpoint // The actual serverless function will call Fireflies API // This test validates the endpoint is accessible // Webhook endpoint: The route path from manifest is /webhook/fireflies // Routes are matched after removing /s/ prefix // So /s/webhook/fireflies should match the route /webhook/fireflies const webhookUrl = `${SERVER_URL}/s/webhook/fireflies`; console.log(`๐Ÿ“ก Calling webhook endpoint: ${webhookUrl}\n`); try { // Note: This will fail because the serverless function needs to call // Fireflies API, which we can't easily mock at the endpoint level. // In development, you might want to set FIREFLIES_API_KEY to a test value // and mock the Fireflies API endpoint separately. const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${TWENTY_API_KEY}`, 'x-hub-signature': signature, }, body: body, }); const responseText = await response.text(); let responseData; try { responseData = JSON.parse(responseText); } catch { responseData = responseText; } console.log(`๐Ÿ“ฅ Response Status: ${response.status} ${response.statusText}`); console.log('๐Ÿ“ฅ Response Body:'); console.log(JSON.stringify(responseData, null, 2)); if (response.ok) { console.log('\nโœ… Webhook test completed successfully!'); console.log('\n๐Ÿ“‹ Next steps:'); console.log(' 1. Check Twenty CRM for new Meeting/Note records'); console.log(' 2. Verify custom fields are populated'); console.log(' 3. Check server logs for any errors'); } else { console.log('\nโš ๏ธ Webhook returned an error status'); console.log(' This might be expected if Fireflies API key is not configured'); console.log(' or if the meeting data fetch fails.'); } } catch (error) { console.error('\nโŒ Error calling webhook:'); console.error(error instanceof Error ? error.message : String(error)); console.error('\n๐Ÿ’ก Troubleshooting:'); console.error(' 1. Ensure Twenty server is running'); console.error(' 2. Ensure app is synced: npx twenty-cli app sync'); console.error(' 3. Check API key is valid'); console.error(' 4. Verify webhook endpoint exists'); process.exit(1); } }; main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });