Co-authored-by: Claude (Opus 4.6) <claude@hermes.local> Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
91 lines
2.3 KiB
JavaScript
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)
|
|
)
|
|
);
|
|
},
|
|
};
|