replit + Node.jsで簡易オンラインチャット

[replit](https://replit.com/)で簡易的なオンラインチャットを作成できたのでメモ。

~~ここで実際の動作を見れます。~~

[ruiwenge2氏のガイド](https://replit.com/talk/learn/SocketIO-Tutorial-What-its-for-and-how-to-use/143781)を参照して作成。より単純なオンラインの例が載ってある。

15:36追記：最新チャットが上になるように変更。
~~16:28追記：repl再起動後、それ以前にチャットに参加した人の名前が`undefined`になる不具合アリ。~~
再起動前に参加したか確認し、その場合は自動でリロードするよう修正。
~~19:36追記：開発者ツールを用いれば改行が使えるらしい。要修正~~
21:05追記
・改行を無効化。
・なぜか日本時間じゃなかったので修正。

# index.js (サーバー)
```js:index.js
const express = require("express"); // use express
const app = express(); // create instance of express
const server = require("http").Server(app); // create server
const io = require("socket.io")(server); // create instance of socketio

app.use(express.static("public")); // use "public" directory for static files
const users = {};
const textlog = [];

io.on("connection", socket => {
  socket.on("setup", () => {
    if (textlog.length>0){
      io.emit("updatelog",textlog);
    }
  });
  socket.on("joined", (name) => { // when server recieves the "joined" message
    username = name.split('\n').join(" ")
    users[socket.id] = username; // add user to dictionary
    updatetextlog(getnowtime() + " " + username + " joined")
    console.log(users);
    io.emit("updatelog",textlog);
  });
  socket.on("sendmes", (text) => {
    if (socket.id in users){
      username = users[socket.id];
      updatetextlog(getnowtime() + " " + username + " : "+text.split('\n').join(" "));
      io.emit("updatelog",textlog);
    }else{
      io.to(socket.id).emit("reload")
    }
  });
  socket.on("disconnect", () => { // when someone closes the tab
    if (socket.id in users){
      username = users[socket.id]
      delete users[socket.id]; // remove user from dictionary
      console.log(users);
      updatetextlog(getnowtime() + " " + username + " leaved")
      io.emit("updatelog",textlog);
    }
  });
});

function updatetextlog(newtext){
  textlog.unshift(newtext)
  if (textlog.length>100){
    N=textlog.length-100
    for (let i=0;i<N;i++){
      textlog.pop();
    }
  }
}

function getnowtime(){
  date = new Date();
  text = "";
  textlist = [(date.getHours()+9)%24,date.getMinutes(),date.getSeconds()];
  for (let i=0;i<3;i++){
    if (textlist[i]<10){
      text += "0" + textlist[i]
    }else{
      text += textlist[i]
    }if (i<2){
      text+=":"
    }
  }
  return text
}

server.listen(3000); // run server
```
・`io.emit("関数名", 変数)`でクライアント側の`socket.on("関数名",処理)`を実行する。
・クライアント側の`socket.emit("関数名", 変数)`が実行された際、こちらの`socket.on("関数名",処理)`が実行される。

# index.html / script.js (クライアント)
```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>chat test</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script> <!-- use the library -->
<script src="/script.js"></script> <!-- use "script.js" file -->
<div id="chatbox"><input type="text" id="textbox" placeholder="type your name"><input type="button" value="join the chat" onclick="join()"></div>
<div id="logbox">chat log</div>
</body>
</html>
```
```js:script.js
const socket = io(); // create new instance

socket.emit("setup");

socket.on("updatelog", (textlog) => {
  log=document.getElementById("logbox");
  log.innerHTML = ""
  for (let i=0;i<textlog.length;i++){
    log.innerText += textlog[i]+"\n"
  }
});

socket.on("reload",()=>{location.reload();})

function join(){
  text=document.getElementById("textbox");
  if (text.value!==""){
    socket.emit("joined",text.value);
    chat=document.getElementById("chatbox");
    chat.innerHTML = '<input type="text" id="textbox" size="600"><input type="button" value="send" onclick="send()">';
  }
}

function send(){
  text=document.getElementById("textbox");
  if (text.value!==""){
    socket.emit("sendmes",text.value);
    text.value="";
  }
}
```
・`socket.emit("関数名", 変数)`でサーバー側の`socket.on("関数名",処理)`を実行する。
・サーバー側の`io.emit("関数名", 変数)`が実行された際、こちらの`socket.on("関数名",処理)`が実行される。

# 追記
[Replit Database](https://docs.replit.com/hosting/database-faq)を用いれば、repl再起動後もチャットを保存できる。

:::note warn
以下の例では、テキスト中の`\n`(≠`\\n`)に反応してログが崩れる可能性がある。
:::

```diff_javascript:index.js
const express = require("express"); // use express
const app = express(); // create instance of express
const server = require("http").Server(app); // create server
const io = require("socket.io")(server); // create instance of socketio
+ const Database = require("@replit/database")
+ const db = new Database()

app.use(express.static("public")); // use "public" directory for static files
const users = {};
const textlog = [];
+ db.get("textlog").then(value => {Array.prototype.push.apply(textlog, value.split("\n"))});

io.on("connection", socket => {
  socket.on("setup", () => {
    if (textlog.length>0){
      io.emit("updatelog",textlog);
    }
  });
  socket.on("joined", (name) => { // when server recieves the "joined" message
    username = name.split('\n').join(" ")
    users[socket.id] = username; // add user to dictionary
    updatetextlog(getnowtime() + " " + username + " joined")
    console.log(users);
    io.emit("updatelog",textlog);
  });
  socket.on("sendmes", (text) => {
    if (socket.id in users){
      username = users[socket.id];
      updatetextlog(getnowtime() + " " + username + " : "+text.split('\n').join(" "));
      io.emit("updatelog",textlog);
    }else{
      io.to(socket.id).emit("reload")
    }
  });
  socket.on("disconnect", () => { // when someone closes the tab
    if (socket.id in users){
      username = users[socket.id]
      delete users[socket.id]; // remove user from dictionary
      console.log(users);
      updatetextlog(getnowtime() + " " + username + " leaved")
      io.emit("updatelog",textlog);
    }
  });
});

function updatetextlog(newtext){
  textlog.unshift(newtext)
  if (textlog.length>100){
    N=textlog.length-100
    for (let i=0;i<N;i++){
      textlog.pop();
    }
  }
+ db.set("textlog", textlog.join("\n")).then(() => {});
}

function getnowtime(){
  date = new Date();
  text = "";
  textlist = [(date.getHours()+9)%24,date.getMinutes(),date.getSeconds()];
  for (let i=0;i<3;i++){
    if (textlist[i]<10){
      text += "0" + textlist[i]
    }else{
      text += textlist[i]
    }if (i<2){
      text+=":"
    }
  }
  return text
}

server.listen(3000); // run server
```