Files
Timmy-time-dashboard/static/world/queue.js
Claude (Opus 4.6) a5b95356dd
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
[claude] Add offline message queue for Workshop panel (#913) (#1205)
Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
2026-03-23 22:16:27 +00:00

91 lines
2.3 KiB
JavaScript

/**
* Offline message queue for Workshop panel.
*
* Persists undelivered job submissions to localStorage so they survive
* page refreshes and are replayed when the server comes back online.
*/
const _QUEUE_KEY = "timmy_workshop_queue";
const _MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours — auto-expire old items
export const STATUS = {
QUEUED: "queued",
DELIVERED: "delivered",
FAILED: "failed",
};
function _load() {
try {
const raw = localStorage.getItem(_QUEUE_KEY);
return raw ? JSON.parse(raw) : [];
} catch {
return [];
}
}
function _save(items) {
try {
localStorage.setItem(_QUEUE_KEY, JSON.stringify(items));
} catch {
/* localStorage unavailable — degrade silently */
}
}
function _uid() {
return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}
/** LocalStorage-backed message queue for Workshop job submissions. */
export const messageQueue = {
/** Add a payload. Returns the created item (with id and status). */
enqueue(payload) {
const item = {
id: _uid(),
payload,
queuedAt: new Date().toISOString(),
status: STATUS.QUEUED,
};
const items = _load();
items.push(item);
_save(items);
return item;
},
/** Mark a message as delivered and remove it from storage. */
markDelivered(id) {
_save(_load().filter((i) => i.id !== id));
},
/** Mark a message as permanently failed (kept for 24h for visibility). */
markFailed(id) {
_save(
_load().map((i) =>
i.id === id ? { ...i, status: STATUS.FAILED } : i
)
);
},
/** All messages waiting to be delivered. */
getPending() {
return _load().filter((i) => i.status === STATUS.QUEUED);
},
/** Total queued (QUEUED status only) count. */
pendingCount() {
return this.getPending().length;
},
/** Drop expired failed items (> 24h old). */
prune() {
const cutoff = Date.now() - _MAX_AGE_MS;
_save(
_load().filter(
(i) =>
i.status === STATUS.QUEUED ||
(i.status === STATUS.FAILED &&
new Date(i.queuedAt).getTime() > cutoff)
)
);
},
};