@tukios/snapdragon-realtime
v0.1.3
Published
Browser client for authenticated Snapdragon realtime updates.
Readme
@tukios/snapdragon-realtime
Framework-neutral browser client for product applications that consume Snapdragon print-job and printer-set updates.
The package contains no product credentials or tenant secrets. A product backend must authenticate its own user and tenant, proxy the Snapdragon session and snapshot routes, and keep its platform API key and printer-set token on the server.
Minimal consumer integration
import { createSnapdragonRealtimeClient } from "@tukios/snapdragon-realtime";
const realtime = createSnapdragonRealtimeClient({
getSession: async () => fetch("/api/snapdragon/realtime-session", {
method: "POST",
credentials: "same-origin",
}).then(requireJSON),
getSnapshot: async () => {
const response = await fetch("/api/snapdragon/realtime-snapshot", {
credentials: "same-origin",
}).then(requireJSON);
const snapshot = response.snapshot ?? response;
// If cache reconciliation is asynchronous, await it here before returning.
await printStore.reconcilePrinters(snapshot.printer_set);
return response;
},
onSnapshot: () => printStore.invalidateCanonicalJobs(),
onEvent: (event) => {
if (event.type === "print_job.changed.v1") {
printStore.invalidateCanonicalJobs();
}
},
onStatusChange: ({ status, healthy }) => printStore.setRealtimeStatus({ status, healthy }),
onError: (error) => reportRecoverableRealtimeError(error),
});
await realtime.start();
// When the signed-in user, shop, or page lifecycle changes:
realtime.stop();
async function requireJSON(response) {
if (!response.ok) {
throw new Error(`Snapdragon request failed (${response.status})`);
}
return response.json();
}start() begins a managed lifecycle. Use onStatusChange, not the resolved
start promise, as the current connection-health signal: an initial connection
failure is reported and scheduled for retry rather than thrown to the caller.
Package owns
- AppSync WebSocket connection, subscription acknowledgements, and keepalive.
- Reconnect and jittered retry after transport or authorization changes.
- Server-scheduled channel renewal, overlap, cutover, and stale-channel removal.
- Authoritative snapshots after subscription, reconnection, channel changes, printer-set invalidation, and projection-generation changes.
- Event buffering while a snapshot is being reconciled.
- Event-ID deduplication and projection generation/resource revision guards.
- Five-minute snapshot fallback only after realtime has been unavailable for 30 seconds, with automatic exit after subscription and snapshot recovery.
Consumers should not add parallel renewal, reconnect, retry, fallback polling, event deduplication, revision storage, or generic realtime infrastructure around this package.
Consumer owns
- Same-origin backend session and snapshot proxies with product-user and tenant authorization.
- Product-local rich job records, history, titles, external references, and customer-facing failure details.
- Idempotent reconciliation of printer state and invalidation/refetching of canonical product queries.
- Starting and stopping one client with the active browser tab's authenticated user and shop lifecycle.
- Product UI for connection health when that status is useful to staff.
Callback ordering
Successful connection and reconciliation proceeds in this order:
getSession()obtains short-lived authorization and server-selected channel timings.- The package connects and subscribes to current data and control channels.
- The package begins buffering incoming events.
getSnapshot()obtains authoritative state.- The package seeds projection-generation and resource-revision guards.
onSnapshot(snapshot, context)runs synchronously.- Buffered events newer than the snapshot are filtered and delivered through
onEvent(). - The connection becomes healthy.
getSnapshot() is called by the package at every package-owned reconciliation
point. onSnapshot() may therefore run more than once and must be idempotent.
Its return value is not awaited; asynchronous normalization that must finish
before buffered events are delivered belongs inside getSnapshot().
A printer_set.snapshot.invalidated.v1 notification is delivered through
onEvent() and automatically schedules a fresh snapshot. Burst invalidations
are coalesced into one follow-up reconciliation. Product adapters do not need
to fetch printers from onEvent() themselves.
Snapshot authority
printer_set.stationsandprinter_set.printersare the complete public station and printer set for the link. Reconcile these collections authoritatively.attention.jobsis only the current remote attention subset. It is not print history, and absence must not delete product-local recent/history records.attention.job_revisionsseeds stale-event guards. It can include recently recovered jobs that are no longer inattention.jobs; it is not history.- Public job snapshots and events are intentionally sparse. They omit product-owned titles, external references, detailed failure text, documents, artifact fields, and product capabilities.
When rich job data changes, invalidate or fetch the product's canonical query. Do not manufacture missing fields from a sparse Snapdragon event.
Session and outage behavior
The package calls getSession() for initial connection, reconnect,
authorization refresh, and server-scheduled overlap/cutover. Changed data and
control channels move together. Old channels stay subscribed until the server
confirms the boundary and a new snapshot has reconciled.
Healthy steady state does not poll Snapdragon. After a confirmed 30-second
outage, the package calls getSnapshot() every five minutes while continuing
to reconnect. Fallback stops only after both the realtime subscription and its
post-subscription snapshot succeed.
Timing options exist primarily for tests and controlled operational tuning. Product consumers should normally retain the defaults.
Low-level helpers
connectionProtocols() and parseEvents() are exported for protocol tests and
nonstandard environments. Normal product integrations should use
createSnapdragonRealtimeClient() and leave protocol handling to the package.
