rkgk/static/session.js

201 lines
5.9 KiB
JavaScript

import { listen } from "./framework.js";
let loginStorage = JSON.parse(localStorage.getItem("rkgk.login") ?? "{}");
function saveLoginStorage() {
localStorage.setItem("rkgk.login", JSON.stringify(loginStorage));
}
let resolveLoggedInPromise;
let loggedInPromise = new Promise((resolve) => (resolveLoggedInPromise = resolve));
export function isUserLoggedIn() {
return loginStorage.userId != null;
}
export function getUserId() {
return loginStorage.userId;
}
export function waitForLogin() {
return loggedInPromise;
}
if (isUserLoggedIn()) {
resolveLoggedInPromise();
}
export async function registerUser(nickname) {
try {
let response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ nickname }),
headers: {
"Content-Type": "application/json",
},
});
if (response.status == 500) {
console.error("login service returned 500 status", response);
return {
status: "error",
message:
"We're sorry, but we ran into some trouble registering your account. Please try again.",
};
}
let responseText = await response.text();
let responseJson = JSON.parse(responseText);
if (responseJson.status != "ok") {
console.error("registering user failed", responseJson);
return {
status: "error",
message: "Something seems to have gone wrong. Please try again.",
};
}
console.log(responseJson);
loginStorage.userId = responseJson.userId;
console.info("user registered", loginStorage.userId);
saveLoginStorage();
resolveLoggedInPromise();
return { status: "ok" };
} catch (error) {
console.error("registering user failed", error);
return {
status: "error",
message: "Something seems to have gone wrong. Please try again.",
};
}
}
class Session extends EventTarget {
constructor(userId) {
super();
this.userId = userId;
}
async #recvJson() {
let event = await listen([this.ws, "message"]);
if (typeof event.data == "string") {
return JSON.parse(event.data);
} else {
throw new Error("received a binary message where a JSON text message was expected");
}
}
#sendJson(object) {
console.debug("sendJson", object);
this.ws.send(JSON.stringify(object));
}
#dispatchError(source, kind, message) {
this.dispatchEvent(
Object.assign(new Event("error"), {
source,
errorKind: kind,
message,
}),
);
}
async join(wallId) {
console.info("joining wall", wallId);
this.wallId = wallId;
this.ws = new WebSocket("/api/wall");
this.ws.addEventListener("error", (event) => {
console.error("WebSocket connection error", error);
this.dispatchEvent(Object.assign(new Event("error"), event));
});
this.ws.addEventListener("message", (event) => {
if (typeof event.data == "string") {
let json = JSON.parse(event.data);
if (json.error != null) {
console.error("received error from server:", json.error);
this.#dispatchError(json, "protocol", json.error);
}
}
});
try {
await listen([this.ws, "open"]);
await this.joinInner();
} catch (error) {
this.#dispatchError(error, "connection", `communication failed: ${error.toString()}`);
}
}
async joinInner() {
let version = await this.#recvJson();
console.info("protocol version", version.version);
// TODO: This should probably verify that the version is compatible.
// We don't have a way of sending Rust stuff to JavaScript just yet, so we don't care about it.
if (this.wallId == null) {
this.#sendJson({ login: "new", user: this.userId });
} else {
this.#sendJson({ login: "join", user: this.userId, wall: this.wallId });
}
let loginResponse = await this.#recvJson();
if (loginResponse.response == "loggedIn") {
this.wallId = loginResponse.wall;
this.wallInfo = loginResponse.wallInfo;
this.sessionId = loginResponse.sessionId;
console.info("logged in", this.wallId, this.sessionId);
console.info("wall info:", this.wallInfo);
} else {
this.#dispatchError(
loginResponse,
loginResponse.response,
"login failed; check error kind for details",
);
return;
}
}
async eventLoop() {
try {
while (true) {
let event = await listen([this.ws, "message"]);
if (typeof event.data == "string") {
await this.#processEvent(JSON.parse(event.data));
} else {
console.warn("binary event not yet supported");
}
}
} catch (error) {
this.#dispatchError(error, "protocol", `error in event loop: ${error.toString()}`);
}
}
async #processEvent(event) {
if (event.kind != null) {
this.dispatchEvent(
Object.assign(new Event("action"), {
sessionId: event.sessionId,
kind: event.kind,
}),
);
}
}
async reportCursor(x, y) {
this.#sendJson({
event: "cursor",
position: { x, y },
});
}
}
export async function newSession(userId, wallId) {
let session = new Session(userId);
await session.join(wallId);
return session;
}