diff --git a/.gitignore b/.gitignore index e1da6ae8..9c97bbd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,76 +1,3 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file +node_modules +dist .env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -# FuseBox cache -.fusebox/ diff --git a/main.js b/main.js deleted file mode 100644 index 9b8ee74a..00000000 --- a/main.js +++ /dev/null @@ -1,83 +0,0 @@ -const http = require("http"); -const express = require("express"); -const app = express(); - -app.use(express.static("public")); -// require("dotenv").config(); - -const serverPort = process.env.PORT || 3000; -const server = http.createServer(app); -const WebSocket = require("ws"); - -let keepAliveId; - -const wss = - process.env.NODE_ENV === "production" - ? new WebSocket.Server({ server }) - : new WebSocket.Server({ port: 5001 }); - -server.listen(serverPort); -console.log(`Server started on port ${serverPort} in stage ${process.env.NODE_ENV}`); - -wss.on("connection", function (ws, req) { - console.log("Connection Opened"); - console.log("Client size: ", wss.clients.size); - - if (wss.clients.size === 1) { - console.log("first connection. starting keepalive"); - keepServerAlive(); - } - - ws.on("message", (data) => { - let stringifiedData = data.toString(); - if (stringifiedData === 'pong') { - console.log('keepAlive'); - return; - } - broadcast(ws, stringifiedData, false); - }); - - ws.on("close", (data) => { - console.log("closing connection"); - - if (wss.clients.size === 0) { - console.log("last client disconnected, stopping keepAlive interval"); - clearInterval(keepAliveId); - } - }); -}); - -// Implement broadcast function because of ws doesn't have it -const broadcast = (ws, message, includeSelf) => { - if (includeSelf) { - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(message); - } - }); - } else { - wss.clients.forEach((client) => { - if (client !== ws && client.readyState === WebSocket.OPEN) { - client.send(message); - } - }); - } -}; - -/** - * Sends a ping message to all connected clients every 50 seconds - */ - const keepServerAlive = () => { - keepAliveId = setInterval(() => { - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send('ping'); - } - }); - }, 50000); -}; - - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); diff --git a/render.yaml b/render.yaml new file mode 100644 index 00000000..19df7bac --- /dev/null +++ b/render.yaml @@ -0,0 +1,12 @@ +services: + - name: websocket-server + type: web + runtime: node + buildCommand: npm install + startCommand: node server.js + plan: free + envVars: + - key: NODE_ENV + value: production + - key: PORT + value: 3001 diff --git a/server.ts b/server.ts new file mode 100644 index 00000000..efeddf99 --- /dev/null +++ b/server.ts @@ -0,0 +1,115 @@ +import express from 'express'; +import { WebSocketServer, WebSocket } from 'ws'; +import { createServer } from 'http'; + +const app = express(); +const server = createServer(app); +const wss = new WebSocketServer({ + server, + // Add CORS headers for WebSocket + verifyClient: (info, cb) => { + const origin = info.origin || info.req.headers.origin; + cb(true); // Accept all origins in development + } +}); + +// Enable CORS for Express +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); +}); + +const rooms = new Map(); + +function heartbeat() { + this.isAlive = true; +} + +wss.on('connection', (ws) => { + console.log('Client connected'); + let currentRoom = null; + + ws.isAlive = true; + ws.on('pong', heartbeat); + + ws.on('message', (message) => { + try { + const data = JSON.parse(message.toString()); + console.log('Received message:', data); + + switch (data.type) { + case 'create': + currentRoom = data.room; + console.log('Creating room:', currentRoom); + if (!rooms.has(currentRoom)) { + rooms.set(currentRoom, new Set()); + } + rooms.get(currentRoom).add(ws); + ws.send(JSON.stringify({ type: 'connected' })); + break; + + case 'join': + currentRoom = data.room; + console.log('Joining room:', currentRoom); + if (rooms.has(currentRoom)) { + rooms.get(currentRoom).add(ws); + ws.send(JSON.stringify({ type: 'connected' })); + } + break; + + case 'transcript': + if (currentRoom && rooms.has(currentRoom)) { + console.log('Broadcasting transcript in room:', currentRoom); + rooms.get(currentRoom).forEach((client) => { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify({ + type: 'transcript', + text: data.text + })); + } + }); + } + break; + } + } catch (error) { + console.error('Error processing message:', error); + } + }); + + ws.on('close', () => { + console.log('Client disconnected from room:', currentRoom); + if (currentRoom && rooms.has(currentRoom)) { + rooms.get(currentRoom).delete(ws); + if (rooms.get(currentRoom).size === 0) { + rooms.delete(currentRoom); + } + } + }); + + ws.on('error', (error) => { + console.error('WebSocket error:', error); + }); +}); + +const interval = setInterval(() => { + wss.clients.forEach((ws) => { + if (ws.isAlive === false) { + console.log('Terminating inactive client'); + return ws.terminate(); + } + + ws.isAlive = false; + ws.ping(); + }); +}, 30000); + +wss.on('close', () => { + clearInterval(interval); +}); + +const PORT = process.env.PORT || 3001; +server.listen(PORT, () => { + console.log(`WebSocket server is running on port ${PORT}`); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9ded1f28 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "./", + "strict": true + } + } + \ No newline at end of file