Files
jackboxpartypack-gamepicker/test-webhook.js
2025-11-02 16:06:31 -05:00

295 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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();
});