Pepguy commited on
Commit
59b509f
·
verified ·
1 Parent(s): 389518d

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +129 -128
app.js CHANGED
@@ -1,133 +1,134 @@
1
- // server.js (Bun)
2
- const rooms = new Map(); // roomId ⇒ Set<ServerWebSocket>
3
- const VALID_TOKEN = "mysecrettoken"; // ← your hard-coded secret
4
-
5
- Bun.serve({
6
- port: Number(Bun.env.PORT) || 7860,
7
-
8
- fetch(req, server) {
9
- const url = new URL(req.url);
10
-
11
- // Handle CORS preflight
12
- if (req.method === "OPTIONS") {
13
- return new Response(null, { status: 204, headers: {
14
- "Access-Control-Allow-Origin": "*",
15
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
16
- "Access-Control-Allow-Headers": "Content-Type, Upgrade",
17
- "Access-Control-Allow-Credentials": "true",
18
- }});
19
- }
20
-
21
- // Upgrade to WebSocket if requested
22
- if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
23
- // **AUTH CHECK**: require ?token=VALID_TOKEN
24
- const clientToken = url.searchParams.get("token");
25
- if (clientToken !== VALID_TOKEN) {
26
- return new Response("Unauthorized", { status: 401 });
27
- }
28
-
29
- // Accept the upgrade with CORS headers on the handshake
30
- const upgradeRes = server.upgrade(req, {
31
- headers: {
32
- "Access-Control-Allow-Origin": "*",
33
- "Access-Control-Allow-Credentials": "true",
34
- },
35
- });
36
- return upgradeRes ?? new Response("Upgrade failed", { status: 500 });
37
- }
38
-
39
- // … your existing HTML serve code unchanged …
40
- if (url.pathname === "/") {
41
- return new Response(`<!doctype html>
42
- <html><head><title>Dubem Realtime Rooms</title><meta charset="utf-8"/></head><body>
43
- <h2>Join a Room & Send Messages</h2>
44
- <label><input type="checkbox" id="authToggle" checked> Send Auth Token?</label><br/>
45
- <input id="room" placeholder="Room ID"/><button onclick="joinRoom()">Join Room</button>
46
- <div id="log"></div>
47
- <input id="msg" placeholder="Type a message" style="width:80%;"/><button onclick="sendMsg()">Send</button>
48
- <script>
49
- const VALID_TOKEN = "mysecrettoken"; // must match backend
50
- let includeAuth = true; //false;
51
- document.getElementById("authToggle").addEventListener("change", e => {
52
- includeAuth = e.target.checked;
53
- connect(); // reconnect on toggle
54
- });
55
 
56
- let ws, currentRoom;
57
- function connect() {
58
- if (ws) ws.close();
59
- const scheme = location.protocol === 'https:' ? 'wss' : 'ws';
60
- let socketUrl = scheme + '://' + location.host + '/';
61
- if (includeAuth) socketUrl += "?token=" + VALID_TOKEN;
62
- ws = new WebSocket(socketUrl);
63
- ws.onopen = () => log('🔌 Connected');
64
- ws.onmessage = ev => { const m = JSON.parse(ev.data); log('['+m.roomId+'] '+m.message); };
65
- ws.onerror = () => log('⚠️ WebSocket error');
66
- ws.onclose = c => log('❌ Disconnected (code='+c.code+')');
67
- }
68
- window.addEventListener('load', connect);
69
-
70
- function joinRoom(){
71
- const id = document.getElementById('room').value.trim();
72
- if(!id) return alert('Enter room ID');
73
- ws.send(JSON.stringify({ action:'join', roomId:id }));
74
- currentRoom = id;
75
- log('➡️ Joined '+id);
76
- }
77
- function sendMsg(){
78
- const t = document.getElementById('msg').value.trim();
79
- if(!t) return;
80
- if(!currentRoom) return alert('Join a room first');
81
- ws.send(JSON.stringify({ action:'post', roomId:currentRoom, message:t }));
82
- document.getElementById('msg').value = '';
83
- }
84
- function log(txt){
85
- const e = document.getElementById('log');
86
- e.innerHTML += '<div>'+txt+'</div>';
87
- e.scrollTop = e.scrollHeight;
88
- }
89
- </script>
90
- </body></html>`, {
91
- headers: {
92
- "Content-Type": "text/html; charset=utf-8",
93
- "Access-Control-Allow-Origin": "*",
94
- }
95
- });
96
- }
97
-
98
- return new Response("Not Found", {
99
- status: 404,
100
- headers: { "Access-Control-Allow-Origin": "*" },
101
  });
102
- },
103
-
104
- websocket: {
105
- message(ws, raw) {
106
- let msg;
107
- try { msg = JSON.parse(raw); } catch { return; }
108
- if (msg.action === "join" && msg.roomId) {
109
- for (const set of rooms.values()) set.delete(ws);
110
- let set = rooms.get(msg.roomId);
111
- if (!set) { set = new Set(); rooms.set(msg.roomId, set); }
112
- set.add(ws);
113
- }
114
- if (msg.action === "post" && msg.roomId && msg.message) {
115
- const set = rooms.get(msg.roomId);
116
- if (!set) return;
117
- const payload = JSON.stringify({
118
- roomId: msg.roomId,
119
- message: msg.message,
120
- timestamp: Date.now(),
121
- });
122
- for (const client of set) {
123
- if (client.readyState === 1) client.send(payload);
124
- }
125
- }
126
- },
127
- close(ws) {
128
- for (const set of rooms.values()) set.delete(ws);
129
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  });
132
 
133
- console.log("✅ Bun realtime server running on port " + (Bun.env.PORT || 7860));
 
 
 
 
 
 
 
1
+ // paystack-webhook.js
2
+ // Node >= 14, install dependencies:
3
+ // npm install express firebase-admin
4
+
5
+ const express = require('express');
6
+ const crypto = require('crypto');
7
+ const admin = require('firebase-admin');
8
+
9
+ const app = express();
10
+
11
+ // -------------------------------------------------
12
+ // Environment variables (set these in your env/secrets)
13
+ // -------------------------------------------------
14
+ // - FIREBASE_CREDENTIALS: stringified JSON service account
15
+ // - PAYSTACK_SECRET: your Paystack webhook secret
16
+ // - PORT (optional)
17
+ // -------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ const {
20
+ FIREBASE_CREDENTIALS,
21
+ PAYSTACK_SECRET,
22
+ PORT = 3000,
23
+ } = process.env;
24
+
25
+ if (!PAYSTACK_SECRET) {
26
+ console.warn('WARNING: PAYSTACK_SECRET is not set. Webhook signature verification will fail.');
27
+ }
28
+
29
+ // Initialize Firebase Admin using credentials read from env
30
+ if (FIREBASE_CREDENTIALS) {
31
+ try {
32
+ const serviceAccount = JSON.parse(FIREBASE_CREDENTIALS);
33
+ admin.initializeApp({
34
+ credential: admin.credential.cert(serviceAccount),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  });
36
+ console.log('Firebase admin initialized from FIREBASE_CREDENTIALS env var.');
37
+ } catch (err) {
38
+ console.error('Failed to parse FIREBASE_CREDENTIALS JSON:', err);
39
+ process.exit(1);
40
+ }
41
+ } else {
42
+ console.warn('FIREBASE_CREDENTIALS not provided. Firebase admin not initialized.');
43
+ }
44
+
45
+ // Important: use raw body parser to verify Paystack signature.
46
+ // Paystack signs the raw request body (HMAC SHA512) and puts signature in header `x-paystack-signature`
47
+ app.use(
48
+ express.raw({
49
+ type: 'application/json',
50
+ limit: '1mb',
51
+ })
52
+ );
53
+
54
+ // Utility: verify x-paystack-signature
55
+ function verifyPaystackSignature(rawBodyBuffer, signatureHeader) {
56
+ if (!PAYSTACK_SECRET) return false;
57
+ const hmac = crypto.createHmac('sha512', PAYSTACK_SECRET);
58
+ hmac.update(rawBodyBuffer);
59
+ const expected = hmac.digest('hex');
60
+ // Paystack header is hex string; compare in constant-time
61
+ return crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(signatureHeader || '', 'utf8'));
62
+ }
63
+
64
+ // Helper: safe JSON parse
65
+ function safeParseJSON(raw) {
66
+ try {
67
+ return JSON.parse(raw);
68
+ } catch (err) {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ // Webhook endpoint
74
+ app.post('/webhook/paystack', (req, res) => {
75
+ const raw = req.body; // Buffer because we used express.raw
76
+ const signature = req.get('x-paystack-signature') || req.get('X-Paystack-Signature');
77
+
78
+ // Verify signature
79
+ if (!signature || !verifyPaystackSignature(raw, signature)) {
80
+ console.warn('Paystack webhook signature verification failed. Signature header:', signature);
81
+ // Respond 400 to indicate invalid signature
82
+ return res.status(400).json({ ok: false, message: 'Invalid signature' });
83
  }
84
+
85
+ // Parse payload
86
+ const bodyStr = raw.toString('utf8');
87
+ const payload = safeParseJSON(bodyStr);
88
+
89
+ if (!payload) {
90
+ console.warn('Could not parse webhook JSON payload.');
91
+ return res.status(400).json({ ok: false, message: 'Invalid JSON' });
92
+ }
93
+
94
+ // Paystack typically uses an "event" field: e.g. "charge.success", "refund.create", ...
95
+ const event = (payload.event || payload.type || '').toString();
96
+
97
+ // Identify refund events and payment events (first-time or recurring)
98
+ const isRefund = /refund/i.test(event); // matches refund.* etc.
99
+ const isChargeSuccess = /charge\.success/i.test(event); // common payment event
100
+ // add some broad matches for invoice/subscription events that might indicate recurring payments
101
+ const isInvoiceOrSubscription = /invoice\.(payment_succeeded|paid)|subscription\./i.test(event);
102
+
103
+ const isPayment = isChargeSuccess || isInvoiceOrSubscription;
104
+
105
+ // Only interested in refunds and payments
106
+ if (isRefund || isPayment) {
107
+ console.log('--- PAYSTACK WEBHOOK (INTERESTING EVENT) ---');
108
+ console.log('event:', event);
109
+ console.log('payload:', JSON.stringify(payload, null, 2));
110
+
111
+ // TODO: add your business logic here:
112
+ // - update Firestore / user subscription state
113
+ // - create refund records, notify user, retry flows, etc.
114
+ // Example (pseudo):
115
+ // const db = admin.firestore();
116
+ // await db.collection('paystack-webhooks').add({ receivedAt: admin.firestore.FieldValue.serverTimestamp(), event, payload });
117
+
118
+ // If you want to persist the webhook to Firestore for auditing, uncomment above and ensure Firebase is initialized
119
+ } else {
120
+ // For debugging, you can log minimal info for non-interesting events
121
+ console.log(`Received Paystack webhook for event "${event}" — ignored (not refund/payment).`);
122
+ }
123
+
124
+ // Acknowledge the webhook promptly with 200 OK so Paystack stops retrying
125
+ return res.status(200).json({ ok: true });
126
  });
127
 
128
+ // Simple health check
129
+ app.get('/health', (_req, res) => res.json({ ok: true }));
130
+
131
+ app.listen(PORT, () => {
132
+ console.log(`Paystack webhook server listening on port ${PORT}`);
133
+ console.log('POST /webhook/paystack to receive Paystack callbacks');
134
+ });