feat: notification bridge + webhook system

- New: android-notification-listener/ — Kotlin app vangt WhatsApp-notificaties
- New: POST /webhooks/whatsapp/incoming — ontvangt berichten van de Android-app
- New: POST /webhooks/subscribe + GET /webhooks/subscribers — fan-out
- Updated: docker-compose.yml met WEBHOOK_SHARED_SECRET
- Updated: README met volledige documentatie
This commit is contained in:
Eddy de Vink 2026-07-22 20:57:57 +02:00
commit 5b7bc23648

View file

@ -4,6 +4,7 @@ const { remote } = require('webdriverio');
const APPIUM_HOST = process.env.APPIUM_HOST || '127.0.0.1';
const APPIUM_PORT = parseInt(process.env.APPIUM_PORT || '4723', 10);
const PORT = parseInt(process.env.PORT || '3010', 10);
const WEBHOOK_SECRET = process.env.WEBHOOK_SHARED_SECRET || 'dev-secret-change-me';
const app = express();
app.use(express.json({ limit: '10mb' }));
@ -245,7 +246,60 @@ app.post('/client/sendMessage/:session', async (req, res) => {
}
});
// ── Webhook subscriber store (in-memory) ─────────────────────
const subscribers = [];
function fanout(event) {
for (const sub of subscribers) {
fetch(sub.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event),
}).then((r) => {
console.log(` webhook fanout to ${sub.url}: ${r.status}`);
}).catch((e) => {
console.log(` webhook fanout to ${sub.url} failed: ${e.message}`);
});
}
}
// ── Webhook endpoints ─────────────────────────────────────────
app.post('/webhooks/whatsapp/incoming', (req, res) => {
const secret = req.headers['x-webhook-secret'];
if (!secret || secret !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'invalid or missing webhook secret' });
}
const { sender, text, chatId, timestamp } = req.body;
if (!sender || !text || !chatId) {
return res.status(400).json({ error: 'sender, text, and chatId are required' });
}
const event = { sender, text, chatId, timestamp: timestamp || Date.now(), receivedAt: new Date().toISOString() };
fanout(event);
res.json({ status: 'ok' });
});
app.post('/webhooks/subscribe', (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).json({ error: 'url required' });
if (!subscribers.find((s) => s.url === url)) {
subscribers.push({ url, createdAt: new Date().toISOString() });
}
res.json({ status: 'subscribed', url, activeSubscribers: subscribers.length });
});
app.get('/webhooks/subscribers', (_req, res) => {
res.json({ subscribers: subscribers.map((s) => ({ url: s.url, createdAt: s.createdAt })) });
});
// ── Server start ─────────────────────────────────────────────
app.listen(PORT, '0.0.0.0', () => {
console.log(`wa-appium-api listening on http://0.0.0.0:${PORT}`);
console.log(` Appium server: ${APPIUM_HOST}:${APPIUM_PORT}`);
console.log(` Webhook secret: ${WEBHOOK_SECRET === 'dev-secret-change-me' ? 'WARNING: using dev default' : 'configured'}`);
});