Files

295 lines
9.9 KiB
JavaScript
Raw Permalink Normal View History

2025-11-02 16:06:31 -05:00
#!/usr/bin/env node
/**
* Webhook Test Script
*
* This script creates a local webhook receiver and tests the webhook system.
*
* Usage:
* 1. Start your backend server (docker-compose up or npm run dev)
* 2. Run this script: node test-webhook.js
* 3. The script will:
* - Start a local webhook receiver on port 3001
* - Create a webhook in the API
* - Send a test webhook
* - Wait for incoming webhooks
*/
const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');
// Configuration
const API_URL = process.env.API_URL || 'http://localhost:5000';
const WEBHOOK_PORT = process.env.WEBHOOK_PORT || 3001;
const WEBHOOK_SECRET = 'test_secret_' + Math.random().toString(36).substring(7);
// You need to set this - get it from: curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d '{"key":"YOUR_ADMIN_KEY"}'
const JWT_TOKEN = process.env.JWT_TOKEN || '';
if (!JWT_TOKEN) {
console.error('\n❌ ERROR: JWT_TOKEN not set!');
console.error('\nPlease set your JWT token:');
console.error(' 1. Get token: curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d \'{"key":"YOUR_ADMIN_KEY"}\'');
console.error(' 2. Run: JWT_TOKEN="your_token_here" node test-webhook.js');
console.error(' OR: export JWT_TOKEN="your_token_here" && node test-webhook.js\n');
process.exit(1);
}
let webhookId = null;
let receivedWebhooks = [];
// Create Express app for webhook receiver
const app = express();
// Important: Parse JSON and keep raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
// Webhook receiver endpoint
app.post('/webhook/jackbox', (req, res) => {
console.log('\n📨 Webhook received!');
console.log('Headers:', {
'x-webhook-signature': req.headers['x-webhook-signature'],
'x-webhook-event': req.headers['x-webhook-event'],
'user-agent': req.headers['user-agent']
});
const signature = req.headers['x-webhook-signature'];
// Verify signature
if (!signature || !signature.startsWith('sha256=')) {
console.log('❌ Missing or invalid signature format');
return res.status(401).send('Missing or invalid signature');
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
// Timing-safe comparison
let isValid = false;
try {
isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (err) {
console.log('❌ Signature verification failed:', err.message);
return res.status(401).send('Invalid signature');
}
if (!isValid) {
console.log('❌ Signature mismatch!');
console.log(' Expected:', expectedSignature);
console.log(' Received:', signature);
return res.status(401).send('Invalid signature');
}
console.log('✅ Signature verified!');
console.log('\nPayload:', JSON.stringify(req.body, null, 2));
if (req.body.event === 'game.added') {
const game = req.body.data.game;
console.log(`\n🎮 Game Added: ${game.title} from ${game.pack_name}`);
console.log(` Players: ${game.min_players}-${game.max_players}`);
console.log(` Session ID: ${req.body.data.session.id}`);
console.log(` Games Played: ${req.body.data.session.games_played}`);
}
receivedWebhooks.push(req.body);
res.status(200).send('OK');
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
webhooksReceived: receivedWebhooks.length
});
});
// Start webhook receiver
const server = app.listen(WEBHOOK_PORT, async () => {
console.log(`\n🚀 Webhook receiver started on http://localhost:${WEBHOOK_PORT}`);
console.log(`📍 Webhook endpoint: http://localhost:${WEBHOOK_PORT}/webhook/jackbox`);
console.log(`🔐 Secret: ${WEBHOOK_SECRET}\n`);
// Wait a moment for server to be ready
await new Promise(resolve => setTimeout(resolve, 500));
// Run tests
await runTests();
});
async function runTests() {
console.log('═══════════════════════════════════════════════════════');
console.log('Starting Webhook Tests');
console.log('═══════════════════════════════════════════════════════\n');
try {
// Test 1: Create webhook
console.log('📝 Test 1: Creating webhook...');
const createResponse = await fetch(`${API_URL}/api/webhooks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Test Webhook',
url: `http://localhost:${WEBHOOK_PORT}/webhook/jackbox`,
secret: WEBHOOK_SECRET,
events: ['game.added']
})
});
if (!createResponse.ok) {
const error = await createResponse.text();
throw new Error(`Failed to create webhook: ${createResponse.status} ${error}`);
}
const webhook = await createResponse.json();
webhookId = webhook.id;
console.log(`✅ Webhook created with ID: ${webhookId}\n`);
// Test 2: List webhooks
console.log('📝 Test 2: Listing webhooks...');
const listResponse = await fetch(`${API_URL}/api/webhooks`, {
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!listResponse.ok) {
throw new Error(`Failed to list webhooks: ${listResponse.status}`);
}
const webhooks = await listResponse.json();
console.log(`✅ Found ${webhooks.length} webhook(s)\n`);
// Test 3: Send test webhook
console.log('📝 Test 3: Sending test webhook...');
const testResponse = await fetch(`${API_URL}/api/webhooks/test/${webhookId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!testResponse.ok) {
throw new Error(`Failed to send test webhook: ${testResponse.status}`);
}
console.log('✅ Test webhook sent\n');
// Wait for webhook to be received
console.log('⏳ Waiting for webhook delivery (5 seconds)...');
await new Promise(resolve => setTimeout(resolve, 5000));
if (receivedWebhooks.length > 0) {
console.log(`✅ Received ${receivedWebhooks.length} webhook(s)!\n`);
} else {
console.log('⚠️ No webhooks received yet. Check webhook logs.\n');
}
// Test 4: Check webhook logs
console.log('📝 Test 4: Checking webhook logs...');
const logsResponse = await fetch(`${API_URL}/api/webhooks/${webhookId}/logs?limit=10`, {
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!logsResponse.ok) {
throw new Error(`Failed to get webhook logs: ${logsResponse.status}`);
}
const logs = await logsResponse.json();
console.log(`✅ Found ${logs.length} log entries\n`);
if (logs.length > 0) {
console.log('Recent webhook deliveries:');
logs.forEach((log, i) => {
console.log(` ${i + 1}. Event: ${log.event_type}, Status: ${log.response_status || 'pending'}, Time: ${log.created_at}`);
if (log.error_message) {
console.log(` Error: ${log.error_message}`);
}
});
console.log('');
}
// Summary
console.log('═══════════════════════════════════════════════════════');
console.log('Test Summary');
console.log('═══════════════════════════════════════════════════════');
console.log(`✅ Webhook created: ID ${webhookId}`);
console.log(`✅ Webhooks received: ${receivedWebhooks.length}`);
console.log(`✅ Webhook logs: ${logs.length} entries`);
console.log('═══════════════════════════════════════════════════════\n');
console.log('🎉 All tests completed!');
console.log('\n💡 Next steps:');
console.log(' 1. Add a game to an active session in the Picker page');
console.log(' 2. Watch for the webhook to be received here');
console.log(' 3. Press Ctrl+C to cleanup and exit\n');
} catch (error) {
console.error('\n❌ Test failed:', error.message);
console.error('\nMake sure:');
console.error(' - Backend server is running (http://localhost:5000)');
console.error(' - JWT_TOKEN is valid');
console.error(' - Port 3001 is available\n');
await cleanup();
process.exit(1);
}
}
async function cleanup() {
console.log('\n🧹 Cleaning up...');
if (webhookId) {
try {
console.log(` Deleting webhook ${webhookId}...`);
const deleteResponse = await fetch(`${API_URL}/api/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (deleteResponse.ok) {
console.log(' ✅ Webhook deleted');
} else {
console.log(' ⚠️ Could not delete webhook (you may need to delete it manually)');
}
} catch (err) {
console.log(' ⚠️ Error during cleanup:', err.message);
}
}
console.log(' Stopping webhook receiver...');
server.close(() => {
console.log(' ✅ Server stopped');
console.log('\n👋 Goodbye!\n');
process.exit(0);
});
}
// Handle Ctrl+C
process.on('SIGINT', async () => {
console.log('\n\n⚠ Received interrupt signal');
await cleanup();
});
// Handle errors
process.on('uncaughtException', async (err) => {
console.error('\n❌ Uncaught exception:', err);
await cleanup();
});