Spaces:
Runtime error
Runtime error
| // server.js (Node.js with Express and ws) | |
| const express = require('express'); | |
| const { WebSocketServer } = require('ws'); | |
| const cors = require('cors'); // CORS | |
| const { initializeApp, cert } = require('firebase-admin/app'); | |
| const { getFirestore } = require('firebase-admin/firestore'); | |
| var admin = require("firebase-admin"); | |
| const serviceAccount = require("./serviceAcc.json"); | |
| initializeApp({ | |
| credential: cert(serviceAccount) | |
| }); | |
| const db = getFirestore(); | |
| const app = express(); | |
| const port = process.env.PORT || 8080; | |
| const wss = new WebSocketServer({ noServer: true }); | |
| app.use(cors()); | |
| app.get('/', (req, res) => { | |
| res.send('Hello, World!'); // Replace with any response you want | |
| }); | |
| initialData = { devices: [], rooms: [], users: [] }; // Provide default empty data on error | |
| let currentDeviceStates = {}; | |
| // const fetchInitialData = async () => { | |
| // const devices = []; | |
| // const rooms = []; | |
| // const users = []; | |
| // try { | |
| // const deviceSnapshot = await db.collection('Devices').get(); | |
| // deviceSnapshot.forEach(doc => devices.push({ id: doc.id, ...doc.data() })); | |
| // const roomsSnapshot = await db.collectionGroup('Rooms').get(); // Get all rooms across hostels | |
| // roomsSnapshot.forEach(doc => rooms.push({ id: doc.id, ...doc.data(), hostelId: doc.ref.parent.parent.id })); // Include hostelId | |
| // const usersSnapshot = await db.collection('Users').get(); | |
| // usersSnapshot.forEach(doc => users.push({ id: doc.id, ...doc.data() })); | |
| // initialData = { devices, rooms, users }; | |
| // // console.log("Initial data fetched:", initialData); | |
| // //for each hostel id mantain a list of rooms with devices pertaining to that hostel | |
| // //setall to off | |
| // initialData.devices.forEach(device => { | |
| // device.state = false; | |
| // } ); | |
| // initialData.rooms.forEach(room => { | |
| // if (!currentDeviceStates[room.id]) { | |
| // currentDeviceStates[room.id] = []; | |
| // } | |
| // const roomDevices = initialData.devices.filter(device => device.hostelId === room.hostelId); | |
| // currentDeviceStates[room.id] = { ...room, devices: roomDevices }; | |
| // }); | |
| // } catch (error) { | |
| // console.error("Error fetching initial data:", error); | |
| // // return { devices: [], rooms: [], users: [] }; | |
| // initialData = { devices, rooms, users }; | |
| // } | |
| // }; | |
| // const fetchInitialData = async () => { | |
| // try { | |
| // const [deviceSnapshot, roomsSnapshot, usersSnapshot] = await Promise.all([ | |
| // db.collection('Devices').get(), | |
| // db.collectionGroup('Rooms').get(), | |
| // db.collection('Users').get() | |
| // ]); | |
| // const devices = deviceSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); | |
| // const rooms = roomsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), hostelId: doc.ref.parent.parent.id })); | |
| // const users = usersSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); | |
| // initialData = { devices, rooms, users }; | |
| // initialData.devices.forEach(device => { | |
| // device.state = false; | |
| // }); | |
| // initialData.rooms.forEach(room => { | |
| // const roomDevices = initialData.devices.filter(device => device.hostelId === room.hostelId); | |
| // currentDeviceStates[room.id] = { ...room, devices: roomDevices.map(device => ({...device})) }; // Deep copy to avoid modifying initialData | |
| // }); | |
| // console.log("Initial data fetched and processed."); | |
| // } catch (error) { | |
| // console.error("Error fetching initial data:", error); | |
| // initialData = { devices: [], rooms: [], users: [] }; // Provide default empty data on error | |
| // currentDeviceStates = {}; | |
| // } | |
| // }; | |
| // fetchInitialData().then(() => { | |
| // setupFirestoreListeners(); | |
| // }); | |
| // const updateInitialData = (collectionName, snapshot) => { | |
| // const changes = snapshot.docChanges(); | |
| // changes.forEach(change => { | |
| // const docData = { id: change.doc.id, ...change.doc.data() }; | |
| // console.log(change.type, collectionName, "\t"); | |
| // var index; | |
| // switch (change.type) { | |
| // case 'added': | |
| // index = initialData[collectionName]?.findIndex(item => item.id === change.doc.id); | |
| // if (index !== -1) { | |
| // initialData[collectionName][index] = docData; | |
| // } | |
| // else { | |
| // initialData[collectionName].push(docData); | |
| // } | |
| // console.log("Length of ", collectionName, " is ", initialData[collectionName].length); | |
| // console.log("DATA: ",docData); | |
| // break; | |
| // case 'modified': | |
| // index = initialData[collectionName].findIndex(item => item.id === change.doc.id); | |
| // if (index !== -1) { | |
| // initialData[collectionName][index] = docData; | |
| // for (const roomId in currentDeviceStates) { | |
| // const roomDevices = currentDeviceStates[roomId].devices; | |
| // const deviceIndex = roomDevices.findIndex(dev => dev.id === change.doc.id); | |
| // if (deviceIndex !== -1) { | |
| // currentDeviceStates[roomId].devices[deviceIndex] = {...docData, state: roomDevices[deviceIndex].state} | |
| // } | |
| // } | |
| // } | |
| // break; | |
| // case 'removed': | |
| // initialData[collectionName] = initialData[collectionName].filter(item => item.id !== change.doc.id); | |
| // break; | |
| // } | |
| // }); | |
| // console.log(`Updated ${collectionName} in initialData.\n\n`); | |
| // if (collectionName === 'devices' || collectionName === 'rooms') { | |
| // rebuildCurrentDeviceStates(); | |
| // } | |
| // }; | |
| // const setupFirestoreListeners = () => { | |
| // db.collection('Devices').onSnapshot(snapshot => { | |
| // updateInitialData('devices', snapshot); | |
| // }); | |
| // db.collectionGroup('Rooms').onSnapshot(snapshot => { | |
| // updateInitialData('rooms', snapshot); | |
| // }); | |
| // db.collection('Users').onSnapshot(snapshot => { | |
| // updateInitialData('users', snapshot); | |
| // }); | |
| // } | |
| // const rebuildCurrentDeviceStates = () => { | |
| // currentDeviceStates = {}; | |
| // initialData.rooms.forEach(room => { | |
| // const roomDevices = initialData.devices.filter(device => device.hostelId === room.hostelId); | |
| // currentDeviceStates[room.id] = { ...room, devices: roomDevices.map(device => ({ ...device})) }; | |
| // console.log("Rebuilt room: ", room.roomNumber, " with ", roomDevices.length, " devices.","all Devices", room.hostelId); | |
| // }); | |
| // console.log("currentDeviceStates rebuilt."); | |
| // } | |
| // // fetchInitialData().then(() => { | |
| // // setupFirestoreListeners(); | |
| // // }); | |
| // setupFirestoreListeners(); | |
| const fetchInitialData = async () => { | |
| try { | |
| const [deviceSnapshot, roomsSnapshot, usersSnapshot] = await Promise.all([ | |
| db.collection('Devices').get(), | |
| db.collectionGroup('Rooms').get(), | |
| db.collection('Users').get() | |
| ]); | |
| initialData.devices = deviceSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), state: false })); // Initialize state to false | |
| initialData.rooms = roomsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), hostelId: doc.ref.parent.parent.id })); | |
| initialData.users = usersSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); | |
| rebuildCurrentDeviceStates(); // Rebuild after fetching initial data | |
| console.log("Initial data fetched and processed."); | |
| } catch (error) { | |
| console.error("Error fetching initial data:", error); | |
| initialData = { devices: [], rooms: [], users: [] }; | |
| currentDeviceStates = {}; | |
| } | |
| }; | |
| const updateInitialData = (collectionName, snapshot) => { | |
| snapshot.docChanges().forEach(change => { | |
| let docData; | |
| if (collectionName === 'rooms') { | |
| docData = { id: change.doc.id, ...change.doc.data(), hostelId: change.doc.ref.parent.parent.id }; | |
| } else { | |
| docData = { id: change.doc.id, ...change.doc.data() }; | |
| } | |
| let index; | |
| switch (change.type) { | |
| case 'added': | |
| console.log('added', collectionName, docData); | |
| if (!initialData[collectionName].some(item => item.id === change.doc.id)) { | |
| initialData[collectionName].push(docData); | |
| if (collectionName === 'devices') { | |
| initialData[collectionName][initialData[collectionName].length - 1].state = false; | |
| } | |
| } | |
| break; | |
| case 'modified': | |
| console.log('modified', collectionName, docData); | |
| index = initialData[collectionName].findIndex(item => item.id === change.doc.id); | |
| if (index !== -1) { | |
| initialData[collectionName][index] = docData; | |
| if (collectionName === 'devices') { | |
| for (const roomId in currentDeviceStates) { | |
| const roomDevices = currentDeviceStates[roomId].devices; | |
| const deviceIndex = roomDevices?.findIndex(dev => dev.id === change.doc.id); | |
| if (deviceIndex !== -1) { | |
| console.log("Device found in room", roomId, "old state", roomDevices[deviceIndex].state, "new state", docData.state); | |
| currentDeviceStates[roomId].devices[deviceIndex] = { ...docData, state: roomDevices[deviceIndex].state }; | |
| console.log(currentDeviceStates[roomId]); | |
| } | |
| } | |
| } | |
| } | |
| break; | |
| case 'removed': | |
| console.log('removed', collectionName, docData); | |
| initialData[collectionName] = initialData[collectionName].filter(item => item.id !== change.doc.id); | |
| break; | |
| } | |
| }); | |
| console.log(`Updated ${collectionName} in initialData.\n\n`); | |
| if (collectionName === 'devices' || collectionName === 'rooms') { | |
| rebuildCurrentDeviceStates(); | |
| } | |
| }; | |
| const setupFirestoreListeners = () => { | |
| db.collection('Devices').onSnapshot(snapshot => updateInitialData('devices', snapshot)); | |
| db.collectionGroup('Rooms').onSnapshot(snapshot => updateInitialData('rooms', snapshot)); | |
| db.collection('Users').onSnapshot(snapshot => updateInitialData('users', snapshot)); | |
| }; | |
| const rebuildCurrentDeviceStates = () => { | |
| initialData.rooms.forEach(room => { | |
| if (!room.hostelId) { | |
| console.warn(`Room ${room.id} is missing hostelId`); | |
| return; | |
| } | |
| const roomDevices = initialData.devices.filter(device => device.hostelId === room.hostelId); | |
| if (!currentDeviceStates[room.id]) { | |
| currentDeviceStates[room.id] = { ...room, devices: roomDevices.map(device => ({ ...device })) }; | |
| } else { | |
| currentDeviceStates[room.id] = { | |
| ...room, | |
| devices: roomDevices.map(device => ({ | |
| ...device, | |
| state: currentDeviceStates[room.id].devices.find(dev => dev.id === device.id)?.state || false, | |
| })), | |
| }; | |
| } | |
| }); | |
| console.log("currentDeviceStates rebuilt."); | |
| broadcastRoomData(); | |
| }; | |
| const broadcastRoomData = () => { | |
| for (const roomId in connectedClients) { | |
| connectedClients[roomId].forEach(client => { | |
| if (currentDeviceStates[roomId]) { | |
| console.log(currentDeviceStates[roomId]); | |
| client.send(JSON.stringify({ type: 'roomData', roomData: currentDeviceStates[roomId] , roomId: roomId})); | |
| } | |
| }); | |
| } | |
| }; | |
| // Call fetchInitialData and then set up listeners | |
| fetchInitialData() | |
| .then(setupFirestoreListeners) // Set up listeners AFTER fetching initial data | |
| .catch(error => { | |
| console.error("Error initializing:", error); | |
| }); | |
| const connectedClients = {}; // Store connected clients per room | |
| wss.on('connection', (ws) => { | |
| ws.on('message', async (message) => { | |
| try { | |
| const parsedMessage = JSON.parse(message); | |
| const { roomId, userId, apiKey } = parsedMessage; | |
| // Helper function to check authorization | |
| const isAuthorized = (roomId, userId, apiKey) => { | |
| return initialData.users.some(user => user.id === userId && user.roomId === roomId) || apiKey === "masterPassword@tiet@123"; | |
| }; | |
| switch (parsedMessage.type) { | |
| case 'joinRoom': | |
| if (!roomId || !(userId || apiKey)) { | |
| ws.send(JSON.stringify({ error: 'Invalid room or user ID or apikey' })); | |
| return; | |
| } | |
| if (!isAuthorized(roomId, userId, apiKey)) { | |
| console.log("Unauthorized joinRoom attempt."); | |
| ws.send(JSON.stringify({ error: 'Unauthorized' })); | |
| return; | |
| } | |
| // Store the connected client | |
| if (!connectedClients[roomId]) { | |
| connectedClients[roomId] = []; | |
| } | |
| connectedClients[roomId].push(ws); | |
| ws.send(JSON.stringify({ type: 'roomData', roomData: currentDeviceStates[roomId], roomId: roomId })); | |
| break; | |
| case 'updateDeviceState': | |
| const { deviceId, state } = parsedMessage; | |
| if (!deviceId || state === undefined || !roomId || (!userId && !apiKey)) { // Check for undefined state or missing userId/apiKey | |
| ws.send(JSON.stringify({ error: 'Invalid device ID, state, room ID, user ID, or API key' })); | |
| return; | |
| } | |
| if (!isAuthorized(roomId, userId, apiKey)) { | |
| console.log("Unauthorized updateDeviceState attempt."); | |
| ws.send(JSON.stringify({ error: 'Unauthorized' })); | |
| return; | |
| } | |
| const room = currentDeviceStates[roomId]; | |
| if (room) { // Check if the room exists | |
| const device = room.devices.find(dev => dev.id === deviceId); | |
| if (device) { // Check if the device exists | |
| device.state = state; | |
| // Broadcast to all clients in the room | |
| if (connectedClients[roomId]) { | |
| connectedClients[roomId].forEach(client => { | |
| client.send(JSON.stringify({ type: 'deviceUpdate', deviceId, state, roomId: roomId })); | |
| }); | |
| } | |
| } else { | |
| ws.send(JSON.stringify({ error: 'Device not found' })); | |
| } | |
| } else { | |
| ws.send(JSON.stringify({ error: 'Room not found' })); | |
| } | |
| break; | |
| default: | |
| console.log('Unknown message type:', parsedMessage.type); | |
| } | |
| } catch (error) { | |
| console.error('Error handling message:', error); | |
| ws.send(JSON.stringify({ error: 'Invalid message format' })); // Send error back to client | |
| } | |
| }); | |
| ws.on('close', () => { | |
| for (const roomId in connectedClients) { | |
| connectedClients[roomId] = connectedClients[roomId].filter(client => client !== ws); | |
| } | |
| }); | |
| ws.on('error', (error) => { | |
| console.error('WebSocket error:', error); | |
| }); | |
| }); | |
| console.log('WebSocket server started on port 8080'); | |
| const server = app.listen(port, () => console.log(`Listening on port ${port}`)); | |
| server.on('upgrade', (request, socket, head) => { | |
| wss.handleUpgrade(request, socket, head, ws => { | |
| wss.emit('connection', ws, request); | |
| }); | |
| }); |