improvements

This commit is contained in:
Mateusz Gruszczyński
2026-08-05 09:53:28 +02:00
parent 248f4a3977
commit a9911da9a3
42 changed files with 1667 additions and 728 deletions
+103 -5
View File
@@ -25,6 +25,12 @@ const shareToken = new URLSearchParams(location.search).get("share");
let accessToken = shareToken || getAccessToken("workspace", slug);
let nickname = getNickname();
getGuestId();
const workspaceWatchClientId = `workspace_watch_${crypto.randomUUID()}`;
let workspaceWatchSocket;
let workspaceWatchPingTimer;
let workspaceWatchReconnectTimer;
let workspaceWatchIntentionalClose = false;
let workspaceLockedForPassword = false;
const dialog = document.querySelector("#password-dialog");
const identityDialog = document.querySelector("#identity-dialog");
const workspaceContent = document.querySelector("#workspace-content");
@@ -45,6 +51,86 @@ function updateWorkspacePasswordControl() {
workspacePasswordForm.hidden = Boolean(info?.protected || !info?.can_set_password);
}
function stopWorkspaceWatch() {
clearInterval(workspaceWatchPingTimer);
clearTimeout(workspaceWatchReconnectTimer);
workspaceWatchPingTimer = undefined;
workspaceWatchReconnectTimer = undefined;
if (workspaceWatchSocket) {
workspaceWatchIntentionalClose = true;
workspaceWatchSocket.close();
workspaceWatchSocket = undefined;
}
}
function lockWorkspaceForPassword(message = "A password was set for this workspace. Enter it to continue.") {
workspaceLockedForPassword = true;
info = { ...info, protected: true, access_level: "none", can_set_password: false };
stopWorkspaceWatch();
workspaceContent.hidden = true;
document.querySelector("#password-error").textContent = message;
if (!dialog.open) dialog.showModal();
document.querySelector("#open-password")?.focus();
}
function scheduleWorkspaceWatchReconnect() {
clearTimeout(workspaceWatchReconnectTimer);
if (workspaceLockedForPassword || !nickname) return;
workspaceWatchReconnectTimer = window.setTimeout(connectWorkspaceWatch, 1500);
}
function connectWorkspaceWatch() {
if (workspaceLockedForPassword || !nickname) return;
if (workspaceWatchSocket?.readyState === WebSocket.OPEN || workspaceWatchSocket?.readyState === WebSocket.CONNECTING) return;
clearTimeout(workspaceWatchReconnectTimer);
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const socket = new WebSocket(`${protocol}//${location.host}/ws/watch/workspace/${encodeURIComponent(slug)}`);
workspaceWatchSocket = socket;
workspaceWatchIntentionalClose = false;
socket.addEventListener("open", () => {
if (socket !== workspaceWatchSocket) return;
socket.send(JSON.stringify({
type: "authenticate",
access_token: accessToken || null,
client_id: workspaceWatchClientId,
}));
clearInterval(workspaceWatchPingTimer);
workspaceWatchPingTimer = window.setInterval(() => {
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "ping", nonce: Date.now() }));
}, 10000);
});
socket.addEventListener("message", event => {
if (socket !== workspaceWatchSocket) return;
let message;
try { message = JSON.parse(event.data); } catch { return; }
if (message.type === "password_required") {
workspaceWatchIntentionalClose = true;
lockWorkspaceForPassword();
return;
}
if (message.type === "password_changed") {
workspaceWatchIntentionalClose = true;
socket.close();
return;
}
if (message.type === "error") {
workspaceWatchIntentionalClose = true;
socket.close();
if (/password/i.test(message.message || "")) lockWorkspaceForPassword(message.message);
}
});
socket.addEventListener("close", () => {
if (socket !== workspaceWatchSocket) return;
clearInterval(workspaceWatchPingTimer);
workspaceWatchPingTimer = undefined;
workspaceWatchSocket = undefined;
if (!workspaceWatchIntentionalClose) scheduleWorkspaceWatchReconnect();
});
socket.addEventListener("error", () => {
if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) socket.close();
});
}
function escapeHtml(v) { const e = document.createElement("div"); e.textContent = v; return e.innerHTML; }
function formatBytes(value) {
const bytes = Math.max(0, Number(value) || 0);
@@ -143,11 +229,13 @@ async function openWorkspace() {
renderNotes();
renderNotesPagination(data.pagination);
updateWorkspacePasswordControl();
workspaceLockedForPassword = false;
workspaceContent.hidden = false;
if (dialog.open) dialog.close();
connectWorkspaceWatch();
} catch (e) {
if (info?.protected || e.message.toLowerCase().includes("password")) {
document.querySelector("#password-error").textContent = e.message;
if (!dialog.open) dialog.showModal();
lockWorkspaceForPassword(e.message);
} else if (e.status === 403 || e.status === 404) {
await showSystemNotFound();
} else document.querySelector("#workspace-error").textContent = e.message;
@@ -160,7 +248,8 @@ async function init() {
document.querySelector("#workspace-title").textContent = info.title;
document.querySelector("#workspace-url").textContent = location.pathname;
updateWorkspacePasswordControl();
if (info.protected && info.access_level === "none") dialog.showModal(); else openWorkspace();
if (info.protected && info.access_level === "none") lockWorkspaceForPassword("Enter the workspace password to continue.");
else await openWorkspace();
} catch (e) {
if (e.status === 403 || e.status === 404) await showSystemNotFound();
else document.querySelector("#workspace-error").textContent = e.message;
@@ -175,7 +264,7 @@ document.querySelector("#password-form").addEventListener("submit", async e => {
accessToken = getAccessToken("workspace", slug);
document.querySelector("#open-password").value = "";
document.querySelector("#password-error").textContent = "";
openWorkspace();
await openWorkspace();
} catch (error) { document.querySelector("#password-error").textContent = error.message; }
});
workspacePasswordForm.addEventListener("submit", async event => {
@@ -191,7 +280,7 @@ workspacePasswordForm.addEventListener("submit", async event => {
try {
await api(`/api/workspaces/${encodeURIComponent(slug)}/password`, {
method: "POST",
body: JSON.stringify({ password }),
body: JSON.stringify({ password, client_id: workspaceWatchClientId }),
});
const result = await api("/api/access-token", {
method: "POST",
@@ -278,5 +367,14 @@ identityDialog.addEventListener("close", () => {
});
});
dialog.addEventListener("cancel", event => {
if (workspaceLockedForPassword) {
event.preventDefault();
document.querySelector("#open-password")?.focus();
}
});
window.addEventListener("beforeunload", stopWorkspaceWatch);
setNotesView(notesView);
startAuthorizedWorkspace();