const WebSocket = require("ws"); const wss = new WebSocket.Server({port: 3001}); function users() { results = []; wss.clients.forEach(function(client) { // goes inside "data" results.push({ "user": client.user, "status": client.readyState, }); }); return results; } function send(payload) { let message = JSON.stringify({ "to": payload.to, "from": payload.from, "type": payload.type, "subject": payload.subject || "", "body": payload.body, "data": payload.data || {}, "date": Date.now() }); // work as 'array', not 'set' let clients = Array.from(wss.clients); if(!clients.length) return false; let to = clients.find(function(client) { console.log('user', client.user); if(client.user === payload.to) { client.send(message); return true; } }); // carbon copy if(to && payload.type === 'message') { let from = clients.find(function(client) { if(client.user === payload.from) { client.send(message); return true; } }); } console.log('sent', !!to); return !!to; } function message(payload) { return { "type": payload.type || "message", "to": payload.to, "from": payload.from || "", "subject": payload.subject || "", "body": payload.body || "", "data": payload.data || {}, "date": Date.now() } } function broadcast(data) { wss.clients.forEach(function(client) { client.send(JSON.stringify(data)); }); } wss.on('connection', function(client) { console.log('client connected'); client.on('message', function(payload) { console.log('RECIEVED:', payload); try { payload = JSON.parse(payload); switch(payload.type) { case 'register': // attach user id client.user = payload.user; broadcast({type: "clients", data: users(), "date": Date.now()}); console.log('CLIENT REGISTERED', client.user); break; case 'clients': // update clients on page break; case 'message': console.log('message', payload); return send(payload); case 'broadcast': payload.date = Date.now(); broadcast(payload); case 'notification': return send(payload); } } catch(err) { console.log(err); } }); client.on('close', function(e) { console.log('client left', e); broadcast({type: "clients", data: users(), "date": Date.now()}); }); client.on('error', function(e) { console.log('client error', e); }); }); wss.on('error', function(err) { console.log('ERROR', err); }); wss.on('close', function(e) { console.log('close', e); }); process.on('SIGINT', function () { console.log("Caught interrupt signal"); broadcast({type: "offline", "date": Date.now()}); process.exit(); }); function opcodes() { /* 0 Continuation Frame [RFC6455] 1 Text Frame [RFC6455] 2 Binary Frame [RFC6455] 3-7 Unassigned 8 Connection Close Frame [RFC6455] 9 Ping Frame [RFC6455] 10 Pong Frame [RFC6455] 11-15 Unassigned */ } function close(code) { switch(code) { case 1000: return 'Normal Closure'; case 1001: return 'Going Away'; case 1002: return 'Protocol Error'; case 1003: return 'Unsupported Data'; case 1004: return 'Reserved'; case 1005: return 'No Status Rcvd'; case 1006: return 'Abnormal Closure'; case 1007: return 'Invalid frame payload data'; case 1008: return 'Policy Violation'; case 1009: return 'Message Too Big'; case 1010: return 'Mandatory Ext.'; case 1011: return 'Internal Error'; case 1012: return 'Service Restart'; case 1013: return 'Try Again Later'; case 1014: return 'Invalid Proxy/Gateway Response'; case 1015: return 'TLS Handshake'; default: return 'Reserved for Private Use'; } } class SocketServer extends WebSocket.Server { constructor(opts) { super(opts); } users() { results = []; this.clients.forEach(function(client) { // goes inside "data" results.push({ "user": client.user, "status": client.readyState, }); }); return results; } }