logging
This commit is contained in:
+11
-2
@@ -1,19 +1,28 @@
|
||||
import { logDebug, logError, logWarn } from "./logger.js";
|
||||
|
||||
export async function api(path, options = {}) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 12000);
|
||||
try {
|
||||
const headers = new Headers(options.headers || {});
|
||||
if (!(options.body instanceof FormData) && !headers.has("content-type")) headers.set("content-type", "application/json");
|
||||
const started = performance.now();
|
||||
logDebug("api.request", { method: options.method || "GET", path });
|
||||
const response = await fetch(path, { ...options, headers, signal: controller.signal });
|
||||
const durationMs = Math.round(performance.now() - started);
|
||||
logDebug("api.response", { method: options.method || "GET", path, status: response.status, durationMs });
|
||||
const contentType = response.headers.get("content-type") || "";
|
||||
const data = contentType.includes("application/json") ? await response.json().catch(() => ({})) : {};
|
||||
if (!response.ok) {
|
||||
const defaults = { 400: "Invalid request.", 401: "Authentication required.", 403: "Access denied.", 404: "The requested resource was not found.", 405: "This operation is not allowed.", 409: "The requested change conflicts with existing data.", 413: "The uploaded data is too large.", 429: "Too many requests. Try again later.", 500: "Server error. Try again later.", 503: "Service temporarily unavailable." };
|
||||
throw new Error(data.error || defaults[response.status] || `Request failed (${response.status}).`);
|
||||
const requestError = new Error(data.error || defaults[response.status] || `Request failed (${response.status}).`);
|
||||
logWarn("api.failed", { method: options.method || "GET", path, status: response.status, message: requestError.message });
|
||||
throw requestError;
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") throw new Error("Timed out");
|
||||
if (error.name === "AbortError") { logWarn("api.timeout", { method: options.method || "GET", path }); throw new Error("Timed out"); }
|
||||
logError("api.network_error", error, { method: options.method || "GET", path });
|
||||
throw error;
|
||||
} finally { clearTimeout(timeout); }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { bindIdentityDialog, handleResetToken, logoutCurrentSession, validateCurrentSession } from "./auth-ui.js";
|
||||
import { api } from "@rustpad/api";
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const PREFIX = "[RustPad]";
|
||||
const LEVELS = Object.freeze({ off: 0, error: 1, warn: 2, info: 3, debug: 4 });
|
||||
|
||||
function configuredLevel() {
|
||||
const configured = window.__RUSTPAD_CONFIG__?.frontendLogLevel;
|
||||
return Object.prototype.hasOwnProperty.call(LEVELS, configured) ? configured : "warn";
|
||||
}
|
||||
|
||||
function enabled(level) {
|
||||
return LEVELS[configuredLevel()] >= LEVELS[level];
|
||||
}
|
||||
|
||||
function safeDetails(details) {
|
||||
if (!details || typeof details !== "object") return details;
|
||||
const blocked = /password|token|secret|authorization|cookie|email|content/i;
|
||||
return Object.fromEntries(Object.entries(details).map(([key, value]) => [key, blocked.test(key) ? "[redacted]" : value]));
|
||||
}
|
||||
|
||||
export function logInfo(event, details = {}) { if (enabled("info")) console.info(PREFIX, event, safeDetails(details)); }
|
||||
export function logWarn(event, details = {}) { if (enabled("warn")) console.warn(PREFIX, event, safeDetails(details)); }
|
||||
export function logError(event, error, details = {}) {
|
||||
if (enabled("error")) console.error(PREFIX, event, { ...safeDetails(details), error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
export function logDebug(event, details = {}) { if (enabled("debug")) console.debug(PREFIX, event, safeDetails(details)); }
|
||||
|
||||
export function installGlobalDiagnostics() {
|
||||
logInfo("frontend.initialized", { path: location.pathname, assetVersion: document.body?.dataset.assetVersion || "unknown", logLevel: configuredLevel() });
|
||||
window.addEventListener("error", (event) => logError("frontend.uncaught_error", event.error || event.message, { file: event.filename, line: event.lineno, column: event.colno }));
|
||||
window.addEventListener("unhandledrejection", (event) => logError("frontend.unhandled_rejection", event.reason));
|
||||
window.addEventListener("online", () => logInfo("network.online"));
|
||||
window.addEventListener("offline", () => logWarn("network.offline"));
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { renderMarkdown } from "@rustpad/markdown";
|
||||
|
||||
+4
-2
@@ -1,11 +1,13 @@
|
||||
import { logDebug, logError, logInfo, logWarn } from "./logger.js";
|
||||
|
||||
export class NoteSocket {
|
||||
constructor({ workspaceSlug, noteSlug, password, nickname, sessionToken, onStatus, onAuthenticated, onDocument, onError }) { Object.assign(this, { workspaceSlug, noteSlug, password, nickname, sessionToken, onStatus, onAuthenticated, onDocument, onError }); this.socket=null; this.timer=null; this.closed=false; }
|
||||
connect() { clearTimeout(this.timer); this.closed=false; this.onStatus?.("connecting"); const protocol=location.protocol==="https:"?"wss:":"ws:"; this.socket=new WebSocket(`${protocol}//${location.host}/ws/${encodeURIComponent(this.workspaceSlug)}/${encodeURIComponent(this.noteSlug)}`); this.socket.addEventListener("open",()=>this.socket.send(JSON.stringify({type:"authenticate",password:this.password||null,nickname:this.nickname||null,session_token:this.sessionToken||null}))); this.socket.addEventListener("message",event=>{const m=JSON.parse(event.data); if(m.type==="error"){this.onError?.(m.message);this.closed=true;this.socket.close();} if(m.type==="authenticated"){this.onStatus?.("online");this.onAuthenticated?.(m);} if(m.type==="document")this.onDocument?.(m);}); this.socket.addEventListener("close",()=>{if(!this.closed){this.onStatus?.("offline");this.timer=setTimeout(()=>this.connect(),1500);}}); this.socket.addEventListener("error",()=>{this.onError?.("Failed to connect to the WebSocket server");this.socket.close();}); }
|
||||
connect() { clearTimeout(this.timer); this.closed=false; this.onStatus?.("connecting"); const protocol=location.protocol==="https:"?"wss:":"ws:"; this.socket=new WebSocket(`${protocol}//${location.host}/ws/${encodeURIComponent(this.workspaceSlug)}/${encodeURIComponent(this.noteSlug)}`); this.socket.addEventListener("open",()=>{logInfo("websocket.open",{kind:"note",workspace:this.workspaceSlug,note:this.noteSlug});this.socket.send(JSON.stringify({type:"authenticate",password:this.password||null,nickname:this.nickname||null,session_token:this.sessionToken||null}));}); this.socket.addEventListener("message",event=>{const m=JSON.parse(event.data); if(m.type==="error"){this.onError?.(m.message);this.closed=true;this.socket.close();} if(m.type==="authenticated"){logInfo("websocket.authenticated",{kind:"note",workspace:this.workspaceSlug,note:this.noteSlug});this.onStatus?.("online");this.onAuthenticated?.(m);} if(m.type==="document")this.onDocument?.(m);}); this.socket.addEventListener("close",event=>{logWarn("websocket.close",{kind:"note",code:event.code,reason:event.reason||"",intentional:this.closed});if(!this.closed){this.onStatus?.("offline");this.timer=setTimeout(()=>this.connect(),1500);}}); this.socket.addEventListener("error",event=>{logError("websocket.error",event,{kind:"note"});this.onError?.("Failed to connect to the WebSocket server");this.socket.close();}); }
|
||||
update(content, ownerMap="[]") { if(this.socket?.readyState===WebSocket.OPEN)this.socket.send(JSON.stringify({type:"update",content,owner_map:ownerMap})); }
|
||||
stop(){this.closed=true;clearTimeout(this.timer);this.socket?.close();}
|
||||
}
|
||||
export class PadSocket {
|
||||
constructor({slug,password,nickname,sessionToken,onStatus,onAuthenticated,onDocument,onError}){Object.assign(this,{slug,password,nickname,sessionToken,onStatus,onAuthenticated,onDocument,onError});this.socket=null;this.timer=null;this.closed=false;}
|
||||
connect(){clearTimeout(this.timer);this.closed=false;this.onStatus?.("connecting");const protocol=location.protocol==="https:"?"wss:":"ws:";this.socket=new WebSocket(`${protocol}//${location.host}/ws/p/${encodeURIComponent(this.slug)}`);this.socket.addEventListener("open",()=>this.socket.send(JSON.stringify({type:"authenticate",password:this.password||null,nickname:this.nickname||null,session_token:this.sessionToken||null})));this.socket.addEventListener("message",e=>{const m=JSON.parse(e.data);if(m.type==="error"){this.onError?.(m.message);this.closed=true;this.socket.close();}if(m.type==="authenticated"){this.onStatus?.("online");this.onAuthenticated?.(m);}if(m.type==="document")this.onDocument?.(m);});this.socket.addEventListener("close",()=>{if(!this.closed){this.onStatus?.("offline");this.timer=setTimeout(()=>this.connect(),1500);}});this.socket.addEventListener("error",()=>{this.onError?.("Failed to connect to the WebSocket server");this.socket.close();});}
|
||||
connect(){clearTimeout(this.timer);this.closed=false;this.onStatus?.("connecting");const protocol=location.protocol==="https:"?"wss:":"ws:";this.socket=new WebSocket(`${protocol}//${location.host}/ws/p/${encodeURIComponent(this.slug)}`);this.socket.addEventListener("open",()=>{logInfo("websocket.open",{kind:"note",workspace:this.workspaceSlug,note:this.noteSlug});this.socket.send(JSON.stringify({type:"authenticate",password:this.password||null,nickname:this.nickname||null,session_token:this.sessionToken||null}));});this.socket.addEventListener("message",e=>{const m=JSON.parse(e.data);if(m.type==="error"){this.onError?.(m.message);this.closed=true;this.socket.close();}if(m.type==="authenticated"){logInfo("websocket.authenticated",{kind:"note",workspace:this.workspaceSlug,note:this.noteSlug});this.onStatus?.("online");this.onAuthenticated?.(m);}if(m.type==="document")this.onDocument?.(m);});this.socket.addEventListener("close",event=>{logWarn("websocket.close",{kind:"note",code:event.code,reason:event.reason||"",intentional:this.closed});if(!this.closed){this.onStatus?.("offline");this.timer=setTimeout(()=>this.connect(),1500);}});this.socket.addEventListener("error",event=>{logError("websocket.error",event,{kind:"note"});this.onError?.("Failed to connect to the WebSocket server");this.socket.close();});}
|
||||
update(content,ownerMap="[]"){if(this.socket?.readyState===WebSocket.OPEN)this.socket.send(JSON.stringify({type:"update",content,owner_map:ownerMap}));} stop(){this.closed=true;clearTimeout(this.timer);this.socket?.close();}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { getNickname, getPassword, setPassword } from "@rustpad/session";
|
||||
|
||||
Reference in New Issue
Block a user