roomsync / index.js
merasabkuch's picture
Update index.js
0f7cf98 verified
// server.js (Node.js with Express and ws)
const express = require('express');
const { WebSocketServer } = require('ws');
const cors = require('cors'); // Import 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());
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);
});
});