Rocket/examples/chat/static/script.js

173 lines
5.0 KiB
JavaScript

let roomListDiv = document.getElementById('room-list');
let messagesDiv = document.getElementById('messages');
let newMessageForm = document.getElementById('new-message');
let newRoomForm = document.getElementById('new-room');
let statusDiv = document.getElementById('status');
let roomTemplate = document.getElementById('room');
let messageTemplate = document.getElementById('message');
let messageField = newMessageForm.querySelector("#message");
let usernameField = newMessageForm.querySelector("#username");
let roomNameField = newRoomForm.querySelector("#name");
var STATE = {
room: "lobby",
rooms: {},
connected: false,
}
// Generate a color from a "hash" of a string. Thanks, internet.
function hashColor(str) {
let hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
return `hsl(${hash % 360}, 100%, 70%)`;
}
// Add a new room `name` and change to it. Returns `true` if the room didn't
// already exist and false otherwise.
function addRoom(name) {
if (STATE[name]) {
changeRoom(name);
return false;
}
var node = roomTemplate.content.cloneNode(true);
var room = node.querySelector(".room");
room.addEventListener("click", () => changeRoom(name));
room.textContent = name;
room.dataset.name = name;
roomListDiv.appendChild(node);
STATE[name] = [];
changeRoom(name);
return true;
}
// Change the current room to `name`, restoring its messages.
function changeRoom(name) {
if (STATE.room == name) return;
var newRoom = roomListDiv.querySelector(`.room[data-name='${name}']`);
var oldRoom = roomListDiv.querySelector(`.room[data-name='${STATE.room}']`);
if (!newRoom || !oldRoom) return;
STATE.room = name;
oldRoom.classList.remove("active");
newRoom.classList.add("active");
messagesDiv.querySelectorAll(".message").forEach((msg) => {
messagesDiv.removeChild(msg)
});
STATE[name].forEach((data) => addMessage(name, data.username, data.message))
}
// Add `message` from `username` to `room`. If `push`, then actually store the
// message. If the current room is `room`, render the message.
function addMessage(room, username, message, push = false) {
if (push) {
STATE[room].push({ username, message })
}
if (STATE.room == room) {
var node = messageTemplate.content.cloneNode(true);
node.querySelector(".message .username").textContent = username;
node.querySelector(".message .username").style.color = hashColor(username);
node.querySelector(".message .text").textContent = message;
messagesDiv.appendChild(node);
}
}
// Subscribe to the event source at `uri` with exponential backoff reconnect.
function subscribe(uri) {
var retryTime = 1;
function connect(uri) {
const events = new EventSource(uri);
events.addEventListener("message", (ev) => {
console.log("raw data", JSON.stringify(ev.data));
console.log("decoded data", JSON.stringify(JSON.parse(ev.data)));
const msg = JSON.parse(ev.data);
if (!"message" in msg || !"room" in msg || !"username" in msg) return;
addMessage(msg.room, msg.username, msg.message, true);
});
events.addEventListener("open", () => {
setConnectedStatus(true);
console.log(`connected to event stream at ${uri}`);
retryTime = 1;
});
events.addEventListener("error", () => {
setConnectedStatus(false);
events.close();
let timeout = retryTime;
retryTime = Math.min(64, retryTime * 2);
console.log(`connection lost. attempting to reconnect in ${timeout}s`);
setTimeout(() => connect(uri), (() => timeout * 1000)());
});
}
connect(uri);
}
// Set the connection status: `true` for connected, `false` for disconnected.
function setConnectedStatus(status) {
STATE.connected = status;
statusDiv.className = (status) ? "connected" : "reconnecting";
}
// Let's go! Initialize the world.
function init() {
// Initialize some rooms.
addRoom("lobby");
addRoom("rocket");
changeRoom("lobby");
addMessage("lobby", "Rocket", "Hey! Open another browser tab, send a message.", true);
addMessage("rocket", "Rocket", "This is another room. Neat, huh?", true);
// Set up the form handler.
newMessageForm.addEventListener("submit", (e) => {
e.preventDefault();
const room = STATE.room;
const message = messageField.value;
const username = usernameField.value || "guest";
if (!message || !username) return;
if (STATE.connected) {
fetch("/message", {
method: "POST",
body: new URLSearchParams({ room, username, message }),
}).then((response) => {
if (response.ok) messageField.value = "";
});
}
})
// Set up the new room handler.
newRoomForm.addEventListener("submit", (e) => {
e.preventDefault();
const room = roomNameField.value;
if (!room) return;
roomNameField.value = "";
if (!addRoom(room)) return;
addMessage(room, "Rocket", `Look, your own "${room}" room! Nice.`, true);
})
// Subscribe to server-sent events.
subscribe("/events");
}
init();