You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sockets.js 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. var iolib = require("socket.io"),
  2. log = require("./log.js").log,
  3. BoardData = require("./boardData.js").BoardData,
  4. config = require("./configuration");
  5. /** Map from name to *promises* of BoardData
  6. @type {Object<string, Promise<BoardData>>}
  7. */
  8. var boards = {};
  9. /**
  10. * Prevents a function from throwing errors.
  11. * If the inner function throws, the outer function just returns undefined
  12. * and logs the error.
  13. * @template A
  14. * @param {A} fn
  15. * @returns {A}
  16. */
  17. function noFail(fn) {
  18. return function noFailWrapped(arg) {
  19. try {
  20. return fn(arg);
  21. } catch (e) {
  22. console.trace(e);
  23. }
  24. };
  25. }
  26. function startIO(app) {
  27. io = iolib(app);
  28. io.on("connection", noFail(socketConnection));
  29. return io;
  30. }
  31. /** Returns a promise to a BoardData with the given name
  32. * @returns {Promise<BoardData>}
  33. */
  34. function getBoard(name) {
  35. if (boards.hasOwnProperty(name)) {
  36. return boards[name];
  37. } else {
  38. var board = BoardData.load(name);
  39. boards[name] = board;
  40. return board;
  41. }
  42. }
  43. /**
  44. * Executes on every new connection
  45. * @param {iolib.Socket} socket
  46. */
  47. function socketConnection(socket) {
  48. /**
  49. * Function to call when an user joins a board
  50. * @param {string} name
  51. */
  52. async function joinBoard(name) {
  53. // Default to the public board
  54. if (!name) name = "anonymous";
  55. // Join the board
  56. socket.join(name);
  57. var board = await getBoard(name);
  58. board.users.add(socket.id);
  59. log("board joined", { board: board.name, users: board.users.size });
  60. return board;
  61. }
  62. socket.on(
  63. "error",
  64. noFail(function onError(error) {
  65. log("ERROR", error);
  66. })
  67. );
  68. socket.on("getboard", async function onGetBoard(name) {
  69. var board = await joinBoard(name);
  70. //Send all the board's data as soon as it's loaded
  71. socket.emit("broadcast", { _children: board.getAll() });
  72. });
  73. socket.on("joinboard", noFail(joinBoard));
  74. var lastEmitSecond = (Date.now() / config.MAX_EMIT_COUNT_PERIOD) | 0;
  75. var emitCount = 0;
  76. socket.on(
  77. "broadcast",
  78. noFail(function onBroadcast(message) {
  79. var currentSecond = (Date.now() / config.MAX_EMIT_COUNT_PERIOD) | 0;
  80. if (currentSecond === lastEmitSecond) {
  81. emitCount++;
  82. if (emitCount > config.MAX_EMIT_COUNT) {
  83. var request = socket.client.request;
  84. if (emitCount % 100 === 0) {
  85. log("BANNED", {
  86. user_agent: request.headers["user-agent"],
  87. original_ip:
  88. request.headers["x-forwarded-for"] ||
  89. request.headers["forwarded"],
  90. emit_count: emitCount,
  91. });
  92. }
  93. return;
  94. }
  95. } else {
  96. emitCount = 0;
  97. lastEmitSecond = currentSecond;
  98. }
  99. var boardName = message.board || "anonymous";
  100. var data = message.data;
  101. if (!socket.rooms.has(boardName)) socket.join(boardName);
  102. if (!data) {
  103. console.warn("Received invalid message: %s.", JSON.stringify(message));
  104. return;
  105. }
  106. if (
  107. !message.data.tool ||
  108. config.BLOCKED_TOOLS.includes(message.data.tool)
  109. ) {
  110. log("BLOCKED MESSAGE", message.data);
  111. return;
  112. }
  113. // Save the message in the board
  114. handleMessage(boardName, data, socket);
  115. //Send data to all other users connected on the same board
  116. socket.broadcast.to(boardName).emit("broadcast", data);
  117. })
  118. );
  119. socket.on("disconnecting", function onDisconnecting(reason) {
  120. socket.rooms.forEach(async function disconnectFrom(room) {
  121. if (boards.hasOwnProperty(room)) {
  122. var board = await boards[room];
  123. board.users.delete(socket.id);
  124. var userCount = board.users.size;
  125. log("disconnection", { board: board.name, users: board.users.size });
  126. if (userCount === 0) {
  127. board.save();
  128. delete boards[room];
  129. }
  130. }
  131. });
  132. });
  133. }
  134. function handleMessage(boardName, message, socket) {
  135. if (message.tool === "Cursor") {
  136. message.socket = socket.id;
  137. } else {
  138. saveHistory(boardName, message);
  139. }
  140. }
  141. async function saveHistory(boardName, message) {
  142. var id = message.id;
  143. if (!message.tool && !message._children) {
  144. console.error("Received a badly formatted message (no tool). ", message);
  145. }
  146. var board = await getBoard(boardName);
  147. if (message._children) {
  148. board.batch(message);
  149. }
  150. else {
  151. switch (message.type) {
  152. case "delete":
  153. if (id) board.delete(id);
  154. break;
  155. case "update":
  156. if (id) board.update(id, message);
  157. break;
  158. case "child":
  159. board.addChild(message.parent, message);
  160. break;
  161. default:
  162. //Add data
  163. if (!id) throw new Error("Invalid message: ", message);
  164. board.set(id, message);
  165. }
  166. }
  167. }
  168. function generateUID(prefix, suffix) {
  169. var uid = Date.now().toString(36); //Create the uids in chronological order
  170. uid += Math.round(Math.random() * 36).toString(36); //Add a random character at the end
  171. if (prefix) uid = prefix + uid;
  172. if (suffix) uid = uid + suffix;
  173. return uid;
  174. }
  175. if (exports) {
  176. exports.start = startIO;
  177. }