200 lines
5.9 KiB
JavaScript
200 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;
|
|
}
|