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