Error when trying to create a chatroom

When I try to create a chatroom on my chat app, it fails to do so and throws an error. I check the logs and it says:

Chat running on port 3000
undefined
undefined
file:///app/server.js:149
          data[user].chatrooms.push(id);
                     ^
TypeError: Cannot read property 'chatrooms' of undefined
at file:///app/server.js:149:22
    at FSReqCallback.readFileAfterClose [as oncomplete] (internal/fs/read_file_context.js:71:3)

The two undefined in the logs are an attempt to console.log the user and the user object from the users.json file, which both return undefined. Therefore, because data[user] is undefined, it fails to successfully create the chatroom, and the chatroom is only partially created.

I am pretty sure I have checked if the user’s SID is valid in public/home.js, and also passed all the required body parameters to /api/create-chatroom. So what’s going on?

server.js

app.post("/api/create-chatroom", jsonParser, (req, res) => {
  const id = randomUUID();
  let user = sessions[req.body.sid];
  
  let jsonData = {
    id,
    name: req.body.name,
    messages: [],
    owner: user
  };
  
  fs.writeFile(`chatrooms/${id}.json`, JSON.stringify(jsonData), (err) => {
    if(err) {
      res.json({ success: false, error: err });
    } else {
      fs.readFile("users.json", "utf8", (erro, data) => {
        if(erro) {
          res.json({ success: false, error: erro });
        } else {
          data = JSON.parse(data);
          
          console.log(user)
          console.log(data[user])
          data[user].chatrooms.push(id);
          
          fs.writeFile("users.json", JSON.stringify(data), (error) => {
            if(error) {
              res.json({ success: false, error });
            } else {
              res.json({ success: true, id });
            }
          });
        }
      });
    }
  });
});

public/home.js

function parseCookies() {
  let cookieStr = document.cookie.split(";");
  let cookies = {};
  
  for(let i = 0; i < cookieStr.length; i++) {
    cookieStr[i] = cookieStr[i].split("=");
    
    cookies[cookieStr[i][0]] = cookieStr[i][1];
  }
  
  return cookies;
}

async function checkSid() {
  let sid;

  if("sid" in parseCookies()) {
    let res = await fetch(`/api/sid-exists?sid=${parseCookies().sid}`);
    res = await res.json();
    if(res.exists) {
      sid = parseCookies().sid;
    } else {
      location.href = "/login";
    }
  } else {
    location.href = "/login";
  }
  
  return sid;
}

const sid = checkSid();

/*************************/

function createChatroomModal() {
  document.getElementById("modal-content").innerHTML = `
    <center>
      <h1>Create a chatroom</h1>
      <label for="name">Name:</label>
      <input name="name" id="name" /><br/>
      <button style="margin:5px;" onclick="createChatroom();">Create</button>
    </center>
  `;
  document.getElementById("modal").style.display = "block";
}

async function createChatroom() {
  let res = await fetch("/api/create-chatroom", {
    method: "POST",
    body: {
      name: document.getElementById("name"),
      sid
    }
  });
  res = await res.json();
  if("error" in res) {
    if(res.error == "Invalid SID") {
      location.href = "/login"
    }
  }
}

After a lot of headache, I figured it out.
There were actually a few problems which made it not work.

Problem #1

The sid variable for the client was actually stored as a Promise.
Because you were assigning is with const sid = checkSid();, and checkSid is an asynchronous function, it returns a promise.

This can all be fixed using:

let sid;
checkSid().then(newSid => sid = newSid);

This set’s sid to be the value you want.

Problem #2

The body for your /api/create-chatroom request was invalid. The jsonParser doesn’t know that it’s a JSON.
You need to add a Content-Type header to it.
Fix:

  let res = await fetch("/api/create-chatroom", {
    method: "POST",
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      name: document.getElementById("name").value,
      sid
    })
  });

Outro

I believe that’s all I that I change, hope that is starts working for you!

If you want to see the full code for the changes I’ve made, click here. (EDIT: Site is archived, no longer works)

3 Likes

An untested alternate fix for Problem #2 is that you can set

app.use(express.json())

immediately after declaring express at the top of server.js to automatically parse incoming JSON. In my experience, it has worked without having to explicitly mention a Content-Type on the client-side.

So does this mean that any async function I have will return a promise?

Asynchronous functions are run separately from other code. So if run an async function, then other code, they will kinda run at the same time.

Example:

async function test() {
    for (let i = 0; i < 1000; i++) {}

    console.log("Finished async!");
}

test();
console.log("Finished Base!");

It should log:

Finished Base!
Finished async!

See more

Asynchronous functions return a promise as a way to initiate callbacks.
Using <<YOUR PROMISE>>.then(() => {}) runs the callback when your promise finishes.

I hope that explains it.
(It was hard type all that on Mobile.)

2 Likes

Ok, thanks for the information.

I just realized I had another problem, however: the name of the chatroom, when processed using body-parser, returns {}, meaning that the chatroom doesn’t have a name. What’s going on now?

You had missed one change I had made.

In public/home.js, you need to change

document.getElementById("name")

to

document.getElementById("name").value

when calling createChatroom.

Sorry for responding so late…

2 Likes