PageRenderTime 29ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/http/socket.js

https://github.com/uniteddiversity/scrollback
JavaScript | 478 lines | 382 code | 53 blank | 43 comment | 100 complexity | e33afdc33989e96923ac490a1b5f8c03 MD5 | raw file
  1. /*
  2. Scrollback: Beautiful text chat for your community.
  3. Copyright (c) 2014 Askabt Pte. Ltd.
  4. This program is free software: you can redistribute it and/or modify it
  5. under the terms of the GNU Affero General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or any
  7. later version.
  8. This program is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  10. or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
  11. License for more details.
  12. You should have received a copy of the GNU Affero General Public License
  13. along with this program. If not, see http://www.gnu.org/licenses/agpl.txt
  14. or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  15. Boston, MA 02111-1307 USA.
  16. */
  17. /*
  18. Websockets gateway
  19. */
  20. /* global require, module, exports, console, setTimeout */
  21. var sockjs = require("sockjs"),core,
  22. cookie = require("cookie"),
  23. log = require("../lib/logger.js"),
  24. config = require("../config.js"),
  25. EventEmitter = require("events").EventEmitter,
  26. session = require("./session.js"),
  27. guid = require("../lib/guid.js"),
  28. crypto = require("crypto"),
  29. names = require("../lib/names.js");
  30. var rConns = {}, users = {};
  31. var pid = guid(8);
  32. var sock = sockjs.createServer();
  33. sock.on('connection', function (socket) {
  34. var conn = { socket: socket };
  35. socket.on('data', function(d) {
  36. try { d = JSON.parse(d); log ("Socket received ", d); }
  37. catch(e) { log("ERROR: Non-JSON data", d); return; }
  38. switch(d.type) {
  39. case 'init': init(d.data, conn); break;
  40. case 'message': message(d.data, conn); break;
  41. case 'messages': messages(d.data, conn); break;
  42. case 'room': room(d.data, conn); break;
  43. case 'rooms': rooms(d.data, conn); break;
  44. case 'getUsers': getUsers(d.data, conn); break;
  45. case 'getRooms': getRooms(d.data, conn); break;
  46. case 'edit': edit(d.data, conn); break;
  47. }
  48. });
  49. conn.send = function(type, data) {
  50. data = JSON.stringify({type: type, data: data});
  51. log("Sending", data.length, 'bytes: ', data.substr(0,50));
  52. socket.write(data);
  53. };
  54. socket.on('close', function() { close(conn); });
  55. });
  56. exports.init = function(server, coreObject) {
  57. core = coreObject;
  58. sock.installHandlers(server, {prefix: '/socket'});
  59. };
  60. function init(data, conn) {
  61. var user, sid = data.sid, nick = data.nick, i;
  62. session.get({ sid:sid, suggestedNick:data.nick }, function(err, sess) {
  63. var rooms = sess.user.rooms;
  64. var i;
  65. conn.sid = sid;
  66. conn.rooms = [];
  67. if (sess.user.id.indexOf("guest-")===0 && data.nick) sess.user.id="guest-"+data.nick;
  68. if(sess.pid !== pid) {
  69. sess.pid = pid;
  70. for(i in rooms) {
  71. if(rooms.hasOwnProperty(i)) rooms[i] = 0;
  72. }
  73. }
  74. var query=[];
  75. if (sess.user.id.indexOf('guest-')!==0) {
  76. query.user=sess.user.id;
  77. }
  78. core.emit("members", query,function(err,d) {
  79. var m={};
  80. if (d) {
  81. for (i=0;i<d.length;i++) {
  82. m[d[i].room]=true;
  83. }
  84. }
  85. sess.user.membership = Object.keys(m);//Room added to user object
  86. conn.send('init', {
  87. sid: sess.cookie.value,
  88. user: sess.user,
  89. clientTime: data.clientTime,
  90. serverTime: new Date().getTime()
  91. });
  92. //Temp for now.
  93. core.emit("init", {
  94. from: sess.user.id,
  95. session: "web:"+conn.socket.remoteAddress+":"+conn.sid,
  96. time: new Date().getTime()
  97. });
  98. session.set(conn.sid, sess);
  99. });
  100. });
  101. }
  102. function close(conn) {
  103. if(!conn.sid) return;
  104. session.get({sid: conn.sid}, function(err, sess) {
  105. if(err) {
  106. log("Couldn't find session to close.");
  107. return;
  108. }
  109. var user = sess.user;
  110. conn.rooms.forEach(function(room) {
  111. log("Closed connection, removing", user.id, room);
  112. userAway(user, room, conn);
  113. });
  114. session.set(conn.sid, sess);
  115. });
  116. }
  117. function userAway(user, room, conn) {
  118. if(rConns[room]) rConns[room].splice(rConns[room].indexOf(conn), 1);
  119. conn.rooms.splice(conn.rooms.indexOf(room), 1);
  120. setTimeout(function() {
  121. session.get({sid: conn.sid}, function(err, sess) {
  122. var user = sess.user;
  123. if (typeof user.rooms[room] !== "undefined" && user.rooms[room]<=1) {
  124. delete user.rooms[room];
  125. core.emit("message", { type: 'away', from: user.id, to: room,
  126. time: new Date().getTime(), session:"web:"+conn.socket.remoteAddress+":"+ conn.sid,origin : {gateway : "web", location : "", ip : conn.socket.remoteAddress}}, function(err, m) {
  127. log(err, m);
  128. });
  129. if(!Object.keys(user.rooms).length) {
  130. delete users[user.id];
  131. }
  132. }
  133. else {
  134. user.rooms[room]--;
  135. }
  136. session.set(conn.sid, sess);
  137. });
  138. }, 30*1000);
  139. return false; // never send an away message immediately. Wait.
  140. }
  141. function userBack(user, room, conn) {
  142. if(rConns[room]) rConns[room].push(conn);
  143. else rConns[room] = [conn];
  144. conn.rooms.push(room);
  145. users[user.id] = user;
  146. if(typeof user.rooms[room] !== "undefined" && user.rooms[room]>0) {
  147. user.rooms[room]++;
  148. return false; // we've already sent a back message for this user for this room.
  149. }
  150. user.rooms[room] = 1;
  151. return true;
  152. }
  153. function messages (query, conn) {
  154. core.emit("messages", query, function(err, m) {
  155. if(err) {
  156. log("MESSAGES error", query, err);
  157. conn.send('error', err);
  158. return;
  159. }
  160. conn.send('messages', { query: query, messages: m} );
  161. });
  162. }
  163. function edit(action, conn) {
  164. session.get({sid: conn.sid}, function(err, sess) {
  165. var user = sess.user;
  166. action.from = user.id;
  167. action.session = "web:"+conn.socket.remoteAddress+":"+conn.sid;
  168. core.emit("edit",action, function(err, data){
  169. });
  170. });
  171. }
  172. function message (m, conn) {
  173. var sendTo;
  174. if(!conn.sid) return;
  175. session.get({sid: conn.sid}, function(err, sess) {
  176. var user = sess.user, tryingNick, roomName;
  177. roomName = m.to;
  178. m.from = user.id;
  179. m.time = new Date().getTime();
  180. m.session = "web:"+conn.socket.remoteAddress+":"+ conn.sid;
  181. if (m.origin) m.origin.ip = conn.socket.remoteAddress;
  182. else{
  183. m.origin = {gateway: "web", ip: conn.socket.remoteAddress, location:"unknown"};
  184. }
  185. if(m.to && typeof m.to == "string") {
  186. m.to = [m.to]
  187. }
  188. if(!m.to || !m.to.length) {
  189. if(Object.keys(user.rooms).length) {
  190. m.to = Object.keys(user.rooms);
  191. }else {
  192. m.to = [];
  193. }
  194. }
  195. if (m.type == 'back') {
  196. m.to.forEach(function(room) {
  197. sendTo = []
  198. // it returns false if the back message for this user is already sent
  199. if(userBack(user, room, conn)) {
  200. sendTo.push(room);
  201. }
  202. });
  203. session.set(conn.sid, sess);
  204. m.to = sendTo;
  205. } else if (m.type == 'away') {
  206. if(!userAway(user, m.to, conn)) {
  207. session.set(conn.sid, sess);
  208. return;
  209. }
  210. // it returns false if the away message for this user is not to be sent yet
  211. } else if(m.type == 'nick') {
  212. //validating nick name on server side
  213. if(m.ref && m.ref !== "guest-" && !validateNick(m.ref.substring(6))) {
  214. return conn.send('error', {id:m.id , message: "INVALID_NAME"});
  215. }
  216. if(m.ref && users[m.ref] )
  217. return conn.send('error', {id: m.id, message: "DUP_NICK"});
  218. if(m.user){
  219. if(!m.user.id) return conn.send("error", {id: m.id, message: "INVALID_NAME"} );
  220. m.user.originalId = user.id;
  221. if (!m.user.originalId.match(/^guest/)) {
  222. return;
  223. }
  224. if(!m.user.accounts){m.user.accounts=[];}
  225. m.user.accounts[0] = user.accounts[0];
  226. }
  227. }
  228. function sendMessage() {
  229. core.emit("message", m, function (err, m) {
  230. var i, user = sess.user;
  231. if(err && err.message == "GUEST_CANNOT_HAVE_MEMBERSHIP"){
  232. return conn.send('error', {id: m.id, message: err.message});
  233. }
  234. //for audience mismatch error.
  235. if(err && err.message && err.message.indexOf("AUTH_FAIL")>0) {
  236. return conn.send('error', {id: m.id, message: err.message});
  237. }
  238. if (!user || !user.id) {
  239. return;
  240. }
  241. if(m.type == 'join'){
  242. //check for user login as well
  243. sess.user.membership.push(roomName);
  244. session.set(conn.sid, sess);
  245. }
  246. if(m.type == 'part'){
  247. //check for user login as well
  248. sess.user.membership.splice(sess.user.membership.indexOf(roomName),1);
  249. session.set(conn.sid, sess);
  250. }
  251. if(m && m.type && m.type == 'nick') {
  252. //in case of logout.
  253. if(/^guest-/.test(m.ref) && !/^guest-/.test(m.from)){
  254. sess.user.id = m.ref;
  255. sess.user.picture = "//s.gravatar.com/avatar/" + crypto.createHash('md5').update(sess.user.id).digest('hex') + "/?d=identicon&s=48";
  256. sess.user.accounts = [];
  257. sess.user.membership = [];
  258. session.set(conn.sid, sess);
  259. conn.send('init', {
  260. sid: sess.cookie.value,
  261. user: sess.user
  262. });
  263. if(/^guest-/.test(m.ref)){
  264. core.emit("init",{
  265. type:"init",
  266. from:m.ref,
  267. time: new Date().getTime()
  268. });
  269. }
  270. }
  271. if(m.user) {
  272. /* why shallow copy? why not sess.user = m.user?
  273. copying the property like accounts to the session, but the user will not send other properties.
  274. */
  275. for(i in m.user) if(m.user.hasOwnProperty(i)) {
  276. user[i] = m.user[i];
  277. }
  278. } else if(!err){
  279. user.id = m.ref;
  280. }
  281. session.set(conn.sid, sess);
  282. var query=[];
  283. if (sess.user.id.indexOf('guest-')!==0) {
  284. query.user=sess.user.id;
  285. }
  286. core.emit("members", query,function(err,d){
  287. var m={};
  288. if (d) {
  289. for (i=0;i<d.length;i++) {
  290. m[d[i].room]=true;
  291. }
  292. }
  293. sess.user.membership = Object.keys(m);
  294. if(!err){
  295. conn.send('init', {
  296. sid: sess.cookie.value,
  297. user: sess.user
  298. });
  299. }
  300. session.set(conn.sid, sess);
  301. });
  302. if(m.ref) {
  303. users[m.ref] = users[user.from] || {};
  304. if(users[m.from]) delete users[m.from];
  305. if(m.ref.indexOf("guest-") !== 0) {
  306. users["guest-"+m.from]=users[user.from];
  307. }
  308. }
  309. }
  310. /*
  311. Why this is not at the top?
  312. this thing should be at the bottom because we need the error AUTH_UNREGISTERED to be handled properly before sending the response.
  313. */
  314. if (err) {
  315. return conn.send('error', {id: m.id, message: err.message});
  316. }
  317. });
  318. }
  319. if(m.type=="nick" && m.ref!="guest-" &&( m.ref || m.user)) {
  320. tryingNick = m.ref || m.user.id;
  321. core.emit("rooms",{id:tryingNick.replace(/^guest-/,"")},function(err,data){
  322. if(err) return conn.send('error', {id: m.id, message: err.message});
  323. if((data.length>0) || data.id) return conn.send('error', {id: m.id, message: "DUP_NICK"});
  324. sendMessage();
  325. });
  326. } else {
  327. sendMessage();
  328. }
  329. });
  330. }
  331. function room (r, conn) {
  332. var user;
  333. session.get({sid: conn.sid}, function(err, sess) {
  334. if(!conn.sid) return;
  335. if(typeof r === 'object') {
  336. user = sess.user;
  337. r.owner = user.id;
  338. }
  339. core.emit("room", r, function(err, data) {
  340. if(err) {
  341. log("ROOM ERROR", r, err);
  342. r= {
  343. queryId : r.queryId
  344. };
  345. r.message = err.message;
  346. conn.send('error', r);
  347. }else{
  348. data.query= {
  349. queryId : r.queryId
  350. };
  351. conn.send('room', data);
  352. }
  353. });
  354. session.set(conn.sid, sess);
  355. });
  356. }
  357. function getRooms(query, conn) {
  358. core.emit("getRooms", query, function(err, data) {
  359. if(err) {
  360. query.message = err.message;
  361. conn.send('error',query);
  362. return;
  363. }else {
  364. conn.send('getRooms', { query: query, data: data} );
  365. //conn.send('rooms', data);
  366. }
  367. });
  368. }
  369. function getUsers(query, conn) {
  370. core.emit("getUsers", query, function(err, data) {
  371. if(err) {
  372. query.message = err.message;
  373. conn.send('error',query);
  374. return;
  375. }else {
  376. conn.send('getUsers', { query: query, data: data} );
  377. //conn.send('rooms', data);
  378. }
  379. });
  380. }
  381. function rooms(query, conn) {
  382. core.emit("rooms", query, function(err, data) {
  383. if(err) {
  384. log("ROOMS ERROR", query, err);
  385. query.message = err.message;
  386. conn.send('error',query);
  387. return;
  388. }else {
  389. conn.send('rooms', { query: query, data: data} );
  390. //conn.send('rooms', data);
  391. }
  392. });
  393. }
  394. function validateNick(nick){
  395. if (nick.indexOf("guest-")===0) return false;
  396. return (nick.match(/^[a-z][a-z0-9\_\-\(\)]{2,32}$/i)?nick!='img'&&nick!='css'&&nick!='sdk':false);
  397. }
  398. // ----- Outgoing send ----
  399. exports.send = function (message, rooms) {
  400. message.text = message.text || "";
  401. log("Socket sending", message, "to", rooms);
  402. rooms.map(function(room) {
  403. var location, to = message.to;
  404. if(message.origin) {
  405. location= message.origin;
  406. delete message.origin;
  407. }
  408. //if(message.type == "text") core.occupants(message.to, function(err, data){console.log(err, data);});
  409. if(rConns[room]) rConns[room].map(function(conn) {
  410. message.to = room;
  411. conn.send('message', message);
  412. });
  413. if(location) message.origin = location;
  414. message.to = to;
  415. });
  416. };
  417. exports.emit = function(type, action, room) {
  418. if(rConns[room]) rConns[room].map(function(conn) {
  419. action.to = room;
  420. conn.send(type, action);
  421. });
  422. };