[eric] service layer touchups

This commit is contained in:
ciregenz
2026-05-06 00:03:15 -07:00
parent c64bf65c66
commit d8a1e333be
2 changed files with 30 additions and 2 deletions
+7
View File
@@ -233,6 +233,12 @@ def sync(data: dict | None = None) -> None:
Accepts any dict — the cloud determines what it is from the shape.
The desktop has no knowledge of event types, schemas, or routing.
Each call carries:
- `t`: client-side timestamp at submit time (unix seconds, float).
- `submission_id`: uuid generated per call. The cloud uses
(install_id, submission_id) as an idempotency key, so a retry
from the offline spool is a no-op rather than a double-write.
Fire-and-forget; never raises.
"""
payload = data or {}
@@ -242,6 +248,7 @@ def sync(data: dict | None = None) -> None:
"client_state": _envelope(),
"d": payload,
"t": time.time(),
"submission_id": uuid4().hex,
}
_log("s", payload)
if _test_sink is not None:
+23 -2
View File
@@ -6,6 +6,19 @@
import { API_BASE } from './config';
/** Generate an id per submit() call. Used so retries (network blip,
* page reload mid-flush, etc.) are deduplicated downstream rather than
* inserted as separate rows. Falls back to a Math.random() id on
* ancient browsers without crypto.randomUUID. */
function _newSubmissionId(): string {
try {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
} catch { /* fall through */ }
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
}
let _lastTs = Date.now();
let _appStart = Date.now();
@@ -51,12 +64,20 @@ function _flush(): void {
export function sync(data: Record<string, unknown> = {}, opts: { immediate?: boolean } = {}): void {
_lastTs = Date.now();
// Stamp a submission id + client timestamp so the cloud can deduplicate
// retries and order events by the moment they happened, not by the
// moment they landed.
const stamped: Record<string, unknown> = {
...data,
submission_id: typeof data.submission_id === 'string' ? data.submission_id : _newSubmissionId(),
t: typeof data.t === 'number' ? data.t : Date.now(),
};
if (opts.immediate) {
_queue.push(data);
_queue.push(stamped);
_flush();
return;
}
_queue.push(data);
_queue.push(stamped);
if (_flushTimer == null) {
_flushTimer = setTimeout(() => {
_flushTimer = null;