0% found this document useful (0 votes)
8 views16 pages

WebSocket and HTTP Issues

Uploaded by

ptmdash
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views16 pages

WebSocket and HTTP Issues

Uploaded by

ptmdash
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

WebSocket and HTTP issues

import { createClient, AgentEvents } from '@deepgram/sdk';


import { WebSocketServer, WebSocket } from 'ws';
import fs from 'fs';
import https from 'https';
import http from 'http';
import 'dotenv/config';
import axios from 'axios';
import querystring from 'querystring';
import parsePhoneNumber from 'libphonenumber-js'

const deepgram = createClient(process.env.DEEPGRAM_API_KEY);


// let API_BASE_URL = "http://127.0.0.1:3000"
let API_BASE_URL = "https://sts-5000.wildcard.pickleballify.com"

function getNationalNumber(phone: unknown): string | undefined {


if (typeof phone === 'string') {
const phno = parsePhoneNumber(phone, 'US');
return phno ? phno.countryCallingCode + phno.nationalNumber : phone;
}
return undefined;
}
let restaurantNo: string | undefined = '';
let customerPhone: string | undefined = '';

let storedMenu: any = [];


let storedCustomer: any = null;

// const servers = https.createServer({


// cert: fs.readFileSync('./cert.pem'),
// key: fs.readFileSync('./key.pem'),
// }, (req, res) => {
// if (req.url === '/') {
// res.writeHead(200, { 'Content-Type': 'text/plain' });
// res.end('Hello');
// }
// else if (req.url === '/api/twilio' && req.method === 'POST') {
// let body = '';
// req.on('data', chunk => {
// body += chunk.toString(); // collect data
// });

// req.on('end', () => {
// console.log("Hello - Twilio POST received");

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 1/16
// const parsed = querystring.parse(body);
// restaurantNo = getNationalNumber(parsed.To);
// customerPhone = getNationalNumber(parsed.From);
// console.log( Restaurant: ${restaurantNo}, Customer: ${customerPhone});

// if (restaurantNo) {
// fetchMenu(restaurantNo).then(menu => storedMenu = menu
// ).catch(error => {
// console.error('Error fetching menu:', error);
// });
// }
// if (customerPhone) {
// findCustomer(customerPhone, restaurantNo).then(customerResult =>
storedCustomer = customerResult
// ).catch(error => {
// storedCustomer = null;
// console.error('Background customer fetch error:', error.data);
// });
// }

// // <Say voice="alice">Connecting your call.</Say>


// const twilioResponse = <?xml version="1.0" encoding="UTF-8"?>
// <Response>
// <Connect>
// <Stream url="wss://q6f1bmhp-443.inc1.devtunnels.ms"/>
// </Connect>
// </Response>;

// console.log('Twilio response:', twilioResponse);


// res.writeHead(200, { 'Content-Type': 'application/xml' });
// res.end(twilioResponse);
// });
// }
// else {
// res.writeHead(404);
// res.end();
// }
// });

const server = http.createServer((req, res) => {


console.log(req.url,"asd");

if (req.url === '/') {


res.writeHead(200, { 'Content-Type': 'text/plain' });
return res.end('Hello');
}
else if (req.url === '/twilio' && req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString(); // collect data
});
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 2/16
req.on('end', () => {
console.log("Hello - Twilio POST received");
const parsed = querystring.parse(body);
restaurantNo = getNationalNumber(parsed.To);
customerPhone = getNationalNumber(parsed.From);
console.log( Restaurant: ${restaurantNo}, Customer: ${customerPhone});

if (restaurantNo) {
fetchMenu(restaurantNo).then(menu => storedMenu = menu
).catch(error => {
console.error('Error fetching menu:', error);
});
}
if (customerPhone) {
findCustomer(customerPhone, restaurantNo).then(customerResult => storedCustomer
= customerResult
).catch(error => {
storedCustomer = null;
console.error('Background customer fetch error:', error.data);
});
}

// <Say voice="alice">Connecting your call.</Say>


const twilioResponse = <?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect>
<Stream url="wss://sts-5000.wildcard.pickleballify.com"/>
</Connect>
</Response>;

console.log('Twilio response:', twilioResponse);


res.writeHead(200, { 'Content-Type': 'application/xml' });
res.end(twilioResponse);
});
}
else {
res.writeHead(404);
res.end();
}
});

const wss = new WebSocketServer({ server });


let browserWs: WebSocket | null = null;
let streamSid: string | null = null;

async function fetchMenu(callId: string) {


try {

let url = ${API_BASE_URL}/api/menu-items?no=${restaurantNo}


console.log(Fetching menu for call ${callId}:, url);
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 3/16
const response: any = await axios.get(url);
const data = response.data;

return data;
} catch (error) {
console.error('Menu API Error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
data: []
};
}
}

async function findCustomer(phone: string | undefined, restaurantNo: string | undefined) {


try {
let url = ${API_BASE_URL}/api/customers?phone=${phone}&restaurantNo=${restaurantNo};
console.log(Looking up customer:, url);

const response: any = await axios.get(url);


const data = response.data;

return data || null;


} catch (error: any) {
console.error('Customer lookup Error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
data: null
};
}
}

const humanLikeFunctions = [
{
"name": "end_conversation",
"description": "End conversation naturally when customer indicates they're done (goodbye,
thanks, that's all, etc.)",
"parameters": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Why the conversation is ending"
}
},
"required": ["reason"]
}
},
{
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 4/16
"name": "placeOrder",
"description": "Submit the complete order to the kitchen - make sure everything is perfect!",
"parameters": {
"type": "object",
"properties": {
"customerName": {
"type": "string",
"description": "Customer's name (only needed for new customers)"
},
"customerPhone": {
"type": "string",
"description": "Phone number (usually already have this)"
},
"customerEmail": {
"type": "string",
"description": "Email if they want receipts/updates"
},
"orderType": {
"type": "string",
"enum": ["dine-in", "takeaway", "delivery"],
"description": "How they want their food - eating here, taking out, or delivery"
},
"tableNumber": {
"type": "string",
"description": "Table number for dine-in orders"
},
"deliveryAddress": {
"type": "object",
"description": "Where to deliver (only for delivery orders)",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipCode": { "type": "string" },
"specialInstructions": { "type": "string", "description": "Delivery notes like 'ring
doorbell', 'leave at door'" }
},
"required": ["street", "city", "zipCode"]
},
"orderItems": {
"type": "array",
"description": "Everything they want to eat - must include exact details from menu",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Menu item ID number (from fetchMenu)"
},
"menuItemName": {
"type": "string",
"description": "Exact name from menu (from fetchMenu)"
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 5/16
},
"quantity": {
"type": "integer",
"minimum": 1,
"description": "How many they want"
},
"price": {
"type": "number",
"description": "Price per item (from fetchMenu)"
},
"size": {
"type": "string",
"enum": ["small", "medium", "large", "extra-large"],
"description": "Size if applicable"
},
// "customizations": {
// "type": "array",
// "items": {"type": "string"},
// "description": "Special requests like 'no onions', 'extra cheese'"
// },
"notes": {
"type": "string",
"description": "Any special instructions for this item"
}
},
"required": ["id", "menuItemName", "quantity", "price"]
}
},
"specialRequests": {
"type": "string",
"description": "Any special notes for the whole order"
},
"isConfirmed": {
"type": "boolean",
"description": "Customer has confirmed they want to place this order"
}
},
"required": ["customerName","orderItems", "isConfirmed"]
}
}
];
function connectToAgent(prompt: string) {
try {
// Create an agent connection
const agent = deepgram.agent();

agent.on('Welcome', (data) => {


console.log('Server welcome message:', data);
// Uncomment the following lines if you want to handle Twilio's media events

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 6/16
agent.configure({
audio: {
input: {
encoding: 'mulaw',
sample_rate: 8000
},
output: {
encoding: 'mulaw',
sample_rate: 8000,
container: 'none'
}
// input: {
// encoding: 'linear16',
// sample_rate: 24000
// },
// output: {
// encoding: 'linear16',
// sample_rate: 24000,
// container: 'none'
// }
},
agent: {
listen: {
provider: {
type: 'deepgram',
model: 'nova-3'
}
},
think: {
provider: {
type: 'open_ai',
model: 'gpt-4o-mini'
},
// prompt: humanLikeRestaurantPrompt,
prompt,
functions:
humanLikeFunctions

},
speak: {
provider: {
type: 'deepgram',
model: 'aura-2-helena-en'
}
},
greeting: "Hello! Welcome to the Restaurant. How can I help you today?"
}
});

});
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 7/16
agent.on(AgentEvents.Audio, (audio: Buffer) => {
if (browserWs?.readyState === WebSocket.OPEN) {
try {
browserWs.send(JSON.stringify({
event: 'media',
streamSid: streamSid,
media: { payload: audio.toString('base64') }
}));
} catch (error) {
console.error('Audio error:', error instanceof Error ? error.message : 'Unknown
error');
}
}
});

agent.on(AgentEvents.FunctionCallRequest, async (functionCall) => {

const functionName = functionCall.functions[0].name;


console.log('Function call received:', functionCall.functions[0].name);
const functionId = functionCall.functions[0].id;

try {
let responseContent = {};

switch (functionName) {

case 'placeOrder':
// Your existing placeOrder logic here, but ensure consistent response format
const orderData = JSON.parse(functionCall.functions[0].arguments);

let finalCustomerData = {
customerName: orderData.customerName,
customerPhone: orderData.customerPhone || customerPhone,
customerEmail: orderData.customerEmail
};

if (storedCustomer && storedCustomer.success) {


finalCustomerData = {
customerName: storedCustomer.customer.name ||
orderData.customerName,
customerPhone: storedCustomer.customer.phone || customerPhone,
customerEmail: storedCustomer.customer.email || orderData.customerEmail
};
}

const validatedOrderData = {
customerName: finalCustomerData.customerName,
customerPhone: finalCustomerData.customerPhone,
customerEmail: finalCustomerData.customerEmail || null,
orderItems: orderData.orderItems || [],
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 8/16
specialRequests: orderData.specialRequests || null,
isConfirmed: orderData.isConfirmed || false,
restaurantNo: restaurantNo,
orderType: orderData.orderType || 'takeaway',
tableNumber: orderData.tableNumber || null,
deliveryAddress: orderData.deliveryAddress || null
};

console.log(validatedOrderData,"Asd");

const orderResponse = await fetch(${API_BASE_URL}/api/create-order, {


method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(validatedOrderData)
});

const orderResult = await orderResponse.json();

if (orderResponse.ok && orderResult.success) {


responseContent = {
success: true,
message: Order placed! Order #${orderResult.data?.id}. Total:
$${orderResult.data?.totalAmount},
orderId: orderResult.data?.id,
totalAmount: orderResult.data?.totalAmount
};
} else {
throw new Error(orderResult.message || 'Failed to place order');
}
break;

case 'end_conversation':
const endData = JSON.parse(functionCall.functions[0].arguments);
responseContent = {
success: true,
message: "Thank you for your order! Have a great day!",
reason: endData.reason
};

// Send response first, then handle cleanup


const endResponse = {
type: "FunctionCallResponse",
id: functionId,
name: functionName,
content: JSON.stringify(responseContent)
};

agent.send(JSON.stringify(endResponse));

// Delay cleanup to allow response to be processed


Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 9/16
setTimeout(() => {
agent.disconnect();
if (browserWs) browserWs.close();
}, 2000);

return; // Exit early for end_conversation


}

// Standardized response format for all functions


const standardResponse = {
type: "FunctionCallResponse",
id: functionId,
name: functionName,
content: JSON.stringify(responseContent)
};

console.log(Sending ${functionName} response:,


JSON.stringify(standardResponse).substring(0, 200) + '...');

// Use consistent send method


agent.send(JSON.stringify(standardResponse));

} catch (error) {
console.error(Error in ${functionName} handler:, error);

const errorResponse = {
type: "FunctionCallResponse",
id: functionId,
name: functionName,
content: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : "An unexpected error occurred"
})
};

agent.send(JSON.stringify(errorResponse));
}
});

return agent;
} catch (error) {
console.error('Error connecting to Deepgram:', error);
process.exit(1);
}
}

let readyAgent: any = null;

async function preloadAgent(prompt: string) {


readyAgent = await connectToAgent(prompt);
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 10/16
console.log("Agent preloaded and ready.");
}

// preloadAgent("Default warm-up prompt");

wss.on('connection', async (ws, request) => {


browserWs = ws;

const updatedPrompt =
Customer: ${storedCustomer.success? JSON.stringify(storedCustomer): 'No customer found'}
Menu: ${JSON.stringify(storedMenu.data)}, Total Menu Items: ${storedMenu.data.length}

You are a friendly restaurant assistant. Keep responses under 50 words.

CRITICAL:
- Check if customer exists: ${storedCustomer.success}, if false ALWAYS ask for name. if true, use
customer data.
- NEVER list multiple menu items at once
- Use the provided customer and menu data to personalize responses
- After placing an order, end the conversation and call end_conversation()
- Extra Details: Restro No. ${restaurantNo}, Customer Phone: ${customerPhone}

WORKFLOW:
1. Greet: "Hi! What can I get you?"
2. Take order: Ask for items, size, customizations
3. GATHER INFO NATURALLY:
- "Are you dining with us, taking it to go, or should we deliver?"
- For dine-in: "Perfect! Which table are you at?" (remember this!)
- For delivery: "Got it! What's the address?"
- For takeaway: "Sounds good!"
4. Complete: "So that's [items] for [type]". After this IMMEDIATELY Use placeOrder() then
end_conversation()

Be warm, efficient, and helpful. Ask one question at a time.


;

const agent = await connectToAgent(updatedPrompt);

ws.on('message', (message: Buffer) => {


try {
const data = JSON.parse(message.toString());

if (data.event === 'start') {


streamSid = data.start.streamSid;
return;
}

// Send media payload to Deepgram


if (data.event === 'media' && data.media?.payload) {
agent?.send(Buffer.from(data.media.payload, 'base64'));
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 11/16
} else {
agent?.send(message);
}
} catch (error) {
console.error('Message error:', error instanceof Error ? error.message : 'Unknown error');
}
});

ws.on('close', async () => {


if (agent) {
await agent.disconnect();
}
browserWs = null;
});

ws.on('error', (error) => {


console.error('WebSocket error:', error);
});
});

wss.on('listening', () => {
console.log('WebSocket server is listening on port 5000');
});

server.listen(5000, () => {
console.log('Listening on wss://localhost:5000');
});

// Handle graceful shutdown


process.on('SIGINT', async () => {
console.log('\nReceived SIGINT. Closing server gracefully...');

// Close all WebSocket connections


wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.close(1000, 'Server shutting down');
}
});

// Close WebSocket server


wss.close(() => {
console.log('WebSocket server closed');
});

// Close HTTPS server


server.close(() => {
console.log('HTTPS server closed');
process.exit(0);
});
});
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 12/16
process.on('SIGTERM', async () => {
console.log('\nReceived SIGTERM. Closing server gracefully...');

// Close all WebSocket connections


wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.close(1000, 'Server shutting down');
}
});

// Close WebSocket server


wss.close(() => {
console.log('WebSocket server closed');
});

// Close HTTPS server


server.close(() => {
console.log('HTTPS server closed');
process.exit(0);
});
});

// Handle uncaught exceptions


process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);

// Close all WebSocket connections


wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.close(1011, 'Server error');
}
});

// Close WebSocket server


wss.close(() => {
console.log('WebSocket server closed due to error');
});

// Close HTTPS server


server.close(() => {
console.log('HTTPS server closed due to error');
process.exit(1);
});
});

// Handle unhandled promise rejections


process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);

// Close all WebSocket connections


Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 13/16
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.close(1011, 'Server error');
}
});

// Close WebSocket server


wss.close(() => {
console.log('WebSocket server closed due to error');
});

// Close HTTPS server


server.close(() => {
console.log('HTTPS server closed due to error');
process.exit(1);
});
});

Failed to open a WebSocket connection: invalid Connection header: keep-alive.

You cannot access a WebSocket server directly with a browser. You need a WebSocket client.
not able to hit / path

I see what’s happening — there are two separate things going on here:

1. / path in HTTP not responding in your browser


2. WebSocket connection error: invalid Connection header: keep-alive

1. Why / isn’t working in browser

Right now you’re running:

ts

const server = http.createServer((req, res) => {


console.log(req.url, "asd");
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
return res.end('Hello');
}
// ...
});

If you start this and visit:

arduino

http://localhost:5000/

you should see Hello.


If it doesn’t load, the most likely causes are:

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 14/16
You're trying to use wss:// in your browser address bar — browsers won’t speak WebSocket
protocol directly via normal navigation.
The port is already in use, and the server didn’t bind correctly (check terminal for EADDRINUSE
errors).
Your server is HTTPS/WSS but you’re hitting HTTP.

For just hitting / in a browser, you must run plain HTTP:

arduino

http://localhost:5000/

If you want HTTPS, you’d need to uncomment your https.createServer() block and use:

arduino

https://localhost:5000/

and also trust the certificate.

2. Why you get invalid Connection header: keep-alive

That error comes from the WebSocket handshake.


A WebSocket connection must be made using a WebSocket client, like:

js

const ws = new WebSocket('ws://localhost:5000');

or in Node:

js

import WebSocket from 'ws';


const ws = new WebSocket('ws://localhost:5000');

When you open wss://localhost:5000 in a normal browser tab, the browser sends a plain HTTP GET
request, not a WebSocket upgrade request, so your ws server responds with an error like invalid
Connection header: keep-alive.

That’s why you can’t “visit” a WebSocket server the same way you do an HTTP server.

How to test both

If you want:

Normal HTTP path test → http://localhost:5000/ should return Hello


WebSocket test → Use something like:

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 15/16
js

const ws = new WebSocket('ws://localhost:5000');


ws.onopen = () => console.log('connected');
ws.onmessage = (msg) => console.log('message:', msg.data);

You could even have both HTTP routes and WebSocket handling together (your code already does
this) — just don’t try to access the WebSocket endpoint directly from the browser URL bar.

If you want, I can add a separate /health HTTP route so you can verify the server is alive without
touching the WebSocket logic. That way you can visit it in your browser while keeping the WS endpoint
separate.

Do you want me to add that?

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 16/16

You might also like