-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.mjs
More file actions
151 lines (123 loc) · 4.9 KB
/
Copy pathproxy.mjs
File metadata and controls
151 lines (123 loc) · 4.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import express from 'express';
import fetch from 'node-fetch';
import crypto from 'crypto';
const app = express();
app.use(express.json());
const CONFIG = {
VAPI_API_KEY: process.env.VAPI_API_KEY,
VAPI_WEBHOOK_SECRET: process.env.VAPI_WEBHOOK_SECRET, // Required for security
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID,
PORT: process.env.PORT || 3334,
DEBUG: process.env.DEBUG === 'true'
};
// Verify Vapi webhook signature for security
function verifyVapiSignature(req) {
if (!CONFIG.VAPI_WEBHOOK_SECRET) {
console.error('⚠️ VAPI_WEBHOOK_SECRET not configured - webhook is insecure!');
return false;
}
const signature = req.headers['x-vapi-signature'];
if (!signature) {
console.error('Missing x-vapi-signature header');
return false;
}
const expectedSignature = crypto
.createHmac('sha256', CONFIG.VAPI_WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
// Store pending questions
const pendingQuestions = new Map();
// Webhook endpoint for Vapi tool calls
app.post('/vapi', async (req, res) => {
try {
// Verify webhook signature for security
if (!verifyVapiSignature(req)) {
console.error('⚠️ Invalid webhook signature - rejecting request');
return res.status(401).json({ error: 'Unauthorized' });
}
const { call, tool } = req.body;
if (tool?.function?.name === 'ask_human') {
const { question, context } = tool.function.arguments;
const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Log safely (no sensitive data)
console.log(`[${questionId}] Received ask_human request from call ${call?.id || 'unknown'}`);
if (CONFIG.DEBUG) {
console.log(`[${questionId}] Question: ${question}`);
}
// Send question to Telegram
const telegramText = `📞 Question from call:\n\n**${question}**\n\n${context ? `Context: ${context}\n\n` : ''}Reply to this message to answer.`;
const telegramResponse = await fetch(`https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: CONFIG.TELEGRAM_CHAT_ID,
text: telegramText,
parse_mode: 'Markdown'
})
});
const telegramResult = await telegramResponse.json();
if (!telegramResult.ok) {
console.error('Failed to send Telegram message:', telegramResult);
return res.json({ result: "Sorry, I couldn't get that information right now." });
}
const messageId = telegramResult.result.message_id;
// Store the question and start polling
pendingQuestions.set(questionId, {
telegramMessageId: messageId,
question,
timestamp: Date.now()
});
// Poll for response
const answer = await pollForAnswer(questionId, messageId);
pendingQuestions.delete(questionId);
return res.json({ result: answer });
}
res.json({ result: "Tool not recognized" });
} catch (error) {
console.error('Webhook error:', error);
res.json({ result: "Sorry, there was an error getting that information." });
}
});
async function pollForAnswer(questionId, telegramMessageId) {
const maxWaitTime = 120000; // 2 minutes
const pollInterval = 2000; // 2 seconds
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
try {
// Check for replies to our message
const updatesResponse = await fetch(`https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/getUpdates?offset=-10&limit=10`);
const updates = await updatesResponse.json();
if (updates.ok && updates.result) {
for (const update of updates.result) {
const message = update.message;
if (message?.reply_to_message?.message_id === telegramMessageId) {
console.log(`[${questionId}] Got answer from user`);
if (CONFIG.DEBUG) {
console.log(`[${questionId}] Answer: ${message.text}`);
}
return message.text;
}
}
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
} catch (error) {
console.error('Polling error:', error);
}
}
console.log(`[${questionId}] Timeout waiting for answer`);
return "I didn't get a response in time. Let me continue without that information.";
}
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() });
});
app.listen(CONFIG.PORT, () => {
console.log(`Vapi webhook proxy listening on port ${CONFIG.PORT}`);
});