@wrongstack/webui 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-q1lcmxqy.js → index-3Lo5wUfj.js} +35 -35
- package/dist/index.html +1 -1
- package/dist/index.js +28 -6
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +80 -2
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +80 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -5
package/dist/server/index.js
CHANGED
|
@@ -302,6 +302,20 @@ async function startWebUI(opts = {}) {
|
|
|
302
302
|
});
|
|
303
303
|
const wssSecondary = wsHost === "127.0.0.1" ? new WebSocketServer({ port: wsPort, host: "::1", verifyClient }) : null;
|
|
304
304
|
const clients = /* @__PURE__ */ new Map();
|
|
305
|
+
const RATE_LIMIT_MESSAGES = 60;
|
|
306
|
+
const RATE_LIMIT_WINDOW_MS = 6e4;
|
|
307
|
+
const rateLimits = /* @__PURE__ */ new Map();
|
|
308
|
+
function checkRateLimit(ws) {
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
const limit = rateLimits.get(ws);
|
|
311
|
+
if (!limit || now > limit.resetAt) {
|
|
312
|
+
rateLimits.set(ws, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
if (limit.count >= RATE_LIMIT_MESSAGES) return false;
|
|
316
|
+
limit.count++;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
305
319
|
let runLock = null;
|
|
306
320
|
console.log(
|
|
307
321
|
`[WebUI] WebSocket server running on ws://${wsHost}:${wsPort}` + (wssSecondary ? ` (and ws://[::1]:${wsPort})` : "")
|
|
@@ -415,13 +429,23 @@ async function startWebUI(opts = {}) {
|
|
|
415
429
|
}
|
|
416
430
|
}
|
|
417
431
|
const handleConnection = (ws) => {
|
|
418
|
-
const client = { ws, sessionId: session.id };
|
|
432
|
+
const client = { ws, sessionId: session.id, connectedAt: Date.now() };
|
|
419
433
|
clients.set(ws, client);
|
|
420
434
|
console.log("[WebUI] Client connected, total:", clients.size);
|
|
421
435
|
void sessionStartPayload().then((payload) => {
|
|
422
436
|
send(ws, { type: "session.start", payload });
|
|
423
437
|
});
|
|
424
438
|
ws.on("message", async (data) => {
|
|
439
|
+
if (!checkRateLimit(ws)) {
|
|
440
|
+
send(ws, {
|
|
441
|
+
type: "error",
|
|
442
|
+
payload: {
|
|
443
|
+
phase: "rate_limit",
|
|
444
|
+
message: "Too many messages. Please wait before sending more."
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
425
449
|
try {
|
|
426
450
|
const msg = JSON.parse(data.toString());
|
|
427
451
|
await handleMessage(ws, client, msg);
|
|
@@ -431,6 +455,7 @@ async function startWebUI(opts = {}) {
|
|
|
431
455
|
});
|
|
432
456
|
ws.on("close", () => {
|
|
433
457
|
clients.delete(ws);
|
|
458
|
+
rateLimits.delete(ws);
|
|
434
459
|
console.log("[WebUI] Client disconnected, total:", clients.size);
|
|
435
460
|
if (pendingConfirms.size > 0) {
|
|
436
461
|
for (const [id, resolve] of pendingConfirms) {
|
|
@@ -1007,11 +1032,64 @@ async function startWebUI(opts = {}) {
|
|
|
1007
1032
|
break;
|
|
1008
1033
|
}
|
|
1009
1034
|
case "todos.clear": {
|
|
1010
|
-
context.
|
|
1035
|
+
context.state.replaceTodos([]);
|
|
1011
1036
|
sendResult(ws, true, "Todos cleared");
|
|
1012
1037
|
broadcast({ type: "todos.updated", payload: { todos: [] } });
|
|
1013
1038
|
break;
|
|
1014
1039
|
}
|
|
1040
|
+
case "plan.get": {
|
|
1041
|
+
const planPath = context.meta["plan.path"];
|
|
1042
|
+
if (typeof planPath === "string" && planPath) {
|
|
1043
|
+
try {
|
|
1044
|
+
const { loadPlan } = await import("@wrongstack/core");
|
|
1045
|
+
const plan = await loadPlan(planPath);
|
|
1046
|
+
send(ws, {
|
|
1047
|
+
type: "plan.updated",
|
|
1048
|
+
payload: { plan: plan ?? { version: 1, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }
|
|
1049
|
+
});
|
|
1050
|
+
} catch {
|
|
1051
|
+
send(ws, {
|
|
1052
|
+
type: "plan.updated",
|
|
1053
|
+
payload: { plan: { version: 1, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
send(ws, {
|
|
1058
|
+
type: "plan.updated",
|
|
1059
|
+
payload: { plan: null, error: "Plan storage is not configured for this session." }
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
break;
|
|
1063
|
+
}
|
|
1064
|
+
case "plan.template_use": {
|
|
1065
|
+
const { template } = msg.payload;
|
|
1066
|
+
const planPath = context.meta["plan.path"];
|
|
1067
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
1068
|
+
sendResult(ws, false, "Plan storage is not configured for this session.");
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem, formatPlan } = await import("@wrongstack/core");
|
|
1073
|
+
const tpl = getPlanTemplate(template);
|
|
1074
|
+
if (!tpl) {
|
|
1075
|
+
sendResult(ws, false, `Unknown template "${template}".`);
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
let plan = await loadPlan(planPath) ?? emptyPlan(session.id);
|
|
1079
|
+
for (const item of tpl.items) {
|
|
1080
|
+
({ plan } = addPlanItem(plan, item.title, item.details));
|
|
1081
|
+
}
|
|
1082
|
+
await savePlan(planPath, plan);
|
|
1083
|
+
sendResult(ws, true, `Applied template "${tpl.name}" \u2014 ${tpl.items.length} items added.`);
|
|
1084
|
+
broadcast({
|
|
1085
|
+
type: "plan.updated",
|
|
1086
|
+
payload: { plan }
|
|
1087
|
+
});
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
1090
|
+
}
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1015
1093
|
case "files.list": {
|
|
1016
1094
|
const payload = msg.payload ?? {};
|
|
1017
1095
|
const query = (payload.query ?? "").toLowerCase();
|