選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

sockets.js 4.9KB

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