Skip to main content

Sync and Cache

This document covers the three service-layer modules that manage server communication: syncService (real-time synchronization), cacheService (local persistence), and loginService (authentication and session management).

syncService​

Injectable: providedIn: 'root'

The sync service manages bidirectional data synchronization between the client and the server. It maintains a persistent connection, sends local changes (commits), receives server updates, and caches data locally for offline resilience.

Architecture​

Constructor Dependencies​

ServicePurpose
languageServiceTranslations for error messages
storeServiceLocal storage access
cacheServiceDatabase abstraction for caching
adhocServiceHTTP request handling
stateServiceApplication state management
clockServiceTime and refresh ticks
toastServiceUser-facing notifications
identificationServiceDevice identification
alertServiceModal alerts

Properties and Observables​

Property / ObservableTypeDescription
sessionstringCurrent session ID
devicestringCurrent device ID
user / place / qrcodestring | nullCurrent entity IDs for connection triggers
OnReleasedObservable<void>Connection was released
OnExpiredObservable<void>Session expired
IsReadyObservable<void>Initial sync completed
OnChangeObservable<void>Data changed (from server or local commit)

Lifecycle​

MethodDescription
Start(session)Gets device ID via identificationService, initializes Syncronizator, creates Connection
Stop()Releases connection, clears session data
SetUser(user)Updates connection trigger for USER
SetPlace(place)Updates connection trigger for PLACE
SetQrCode(qrcode)Updates connection trigger for QRCODE
Flush(changes, force)Delegates to Syncronizator.FlushChanges
ResetStage()Cancels current refresh, resets the PendingQueue stage

Connection Class​

Manages the HTTP connection to the server that enables synchronization. The connection carries "triggers" — entity IDs that tell the server which data the client needs.

Trigger System​

Triggers are key-value pairs sent with connection operations:

TriggerValuePurpose
SESSIONSession IDAlways present; identifies the client session
USERUser objidPresent in LOGIN mode; subscribes to user-scoped data
PLACEPlace objidPresent when a place is selected; subscribes to place data
QRCODEQrCode objidPresent in GUEST mode; subscribes to table-scoped data

Guest sync (datamodel.json target table) starts at QRSCAN and loads this table's QRCODE. Tickets in that graph are open tickets of the table (waiter-created bills a diner can pay with Stripe) plus recently closed tickets of this session. ASKWAITER rows are this session or REFUND on the table. The catalog walk includes PRODUCT → CATEGORY / PRODUCTOPT and PRODUCT → PRESELECT → PRESELECTOPT so QRA can list option shortcuts. POS (place) still receives the full locale ticket set. QrCodeView.tickets reads tickets related on the QR object, not place.tickets.

Endpoints​

EndpointMethodPurpose
connection/createPOSTCreate a new connection with initial triggers
connection/updatePOSTUpdate triggers (e.g., when place changes)
connection/deletePOSTDelete the connection (on logout/stop)
connection/cancelPOSTCancel an ongoing refresh cycle

UpdateTriggers(triggers)​

Called whenever entity IDs change. Decides whether to create, update, or ignore:

  • If no connection exists, calls connection/create.
  • If triggers have changed, calls connection/update.
  • Triggers a refresh after the update completes.

Refresh Cycle​

repeatRefresh() uses setTimeout at AppConstants.MainRefresh interval. Each refresh call:

  1. Invokes Syncronizator.DoRefresh().
  2. On completion (success or failure), schedules the next refresh.
  3. The cycle continues until the connection is stopped.

Connection Status​

StatusMeaning
RUNNINGConnection is active and refreshing
STOPPEDConnection has been stopped
EXPIREDSession has expired; triggers OnExpired

Syncronizator Class​

The core sync engine that handles the actual data exchange with the server.

DoRefresh(reset, forced)​

The main refresh cycle:

  1. Prepare changes (_prepare_syncronize):

    • Collects pending local changes from the flush queue.
    • Resolves any temporary UUIDs that have been assigned objids by previous responses.
    • Sets server reference timestamps for conflict detection.
  2. Build request: Creates an UpdateRequest with:

    • access: Access mode and device information.
    • timestamp: Last known server timestamp (for delta updates).
    • target: Connection triggers (session, user, place, qrcode).
    • Request body contains serialized CommitChange objects.
  3. Send POST to /sync: The single endpoint handles both sending changes and receiving updates.

  4. Process response (UpdateResponse):

    • UUID resolution: Maps temporary client UUIDs to server-assigned objids via ResolvedMap.
    • Timestamp update: Advances the local timestamp and reference.
    • Enqueue updates: Server-side changes are fed into PendingQueue.
    • Cache save: Updated data is persisted to local cache via CacheManager.
  5. Error handling:

    • Server errors: Up to 5 retries.
    • HTTP/network errors: Up to 50 retries with backoff.
    • On final failure: Shows a toast notification and optionally triggers session expiration.

FlushChanges(changes, force)​

Sends local changes to the server:

  1. Merge: Combines new changes with any existing queued changes (from previous flush calls that haven't been sent yet).
  2. CheckChanges: Sorts changes by dependency order and validates that all dependencies are met.
  3. Persist locally: Stores changes via SetStoredChanges for crash recovery — if the app closes before the changes are sent, they can be recovered on restart.
  4. Trigger refresh: Cancels any ongoing refresh and starts a new one (optionally forced for immediate sending).

Stored Changes (Crash Recovery)​

The _storedsynckey system persists pending changes to local storage:

  • Before sending, changes are serialized and saved.
  • After successful send, they are cleared.
  • On startup, any stored changes are loaded and re-queued for sending.

PendingQueue​

A priority-based queue that processes server updates in the correct order.

Priority System​

Updates are processed in priority order (lower number = higher priority):

PriorityTablesRationale
1.0–1.9Session, FCM, User, Staff, PlaceCore entities loaded first (synchronous)
2.0–2.9Address, Stripe, PayAccount, RaspPi, PlaceOpt, PlaceLinkInfrastructure dependencies
3.0–3.9Product, Offer, Extra, Discount, Category, Preselect, ProductOpt, QrCode, QrScan, and their sub-objectsCatalog and table data
4.0–4.9Family, FamilyProduct, FamilyPeriodFamily groupings (depend on products)
5.0–5.9Ticket, TicketProduct, TicketOffer, TicketExtra, TicketDiscount, TicketChange, etc.Ticket data (depends on catalog)
6.0–6.9PlaceInfo, InvoiceBusiness, Audit, Payment, SwinvoiceLower-priority place metadata; license Softinvoice pairs with payment
8.0–8.9Repeated/lower priority itemsCatch-all for retries

Processing Algorithm​

Updates are dequeued and applied in blocks:

  1. Block size: Between 500 and 5,000 items per block.
  2. Time budget: Maximum 50ms per block to avoid blocking the UI thread.
  3. Yield: Between blocks, control is yielded via setTimeout(0) to allow UI rendering.
  4. Stage tracking: Processing is divided into stages (0–9). OnRefreshPercent emits progress as a percentage.

Conflict Resolution​

_MergeChange(existing, incoming) resolves conflicts when the same object has both a local and server update:

  • Compares updated timestamps.
  • The version with the later timestamp wins.
  • If timestamps are equal, the incoming (server) version takes precedence.

Enqueue and Dequeue​

MethodDescription
Enqueue(updates)Adds updates to the queue, merging with existing entries for the same object
Concat(updates)Bulk-adds updates, used when loading from cache
Dequeue()Returns the next batch of updates sorted by priority

ResolvedMap​

A simple Map<string, string> tracking UUID → objid mappings:

MethodDescription
Set(uuid, objid)Stores a resolution
Get(uuid)Returns the resolved objid, or null
Has(uuid)Checks if a UUID has been resolved
Clear()Empties the map

When the server responds to a commit, it returns the assigned objids for each submitted object. The ResolvedMap stores these so that subsequent references (from other objects' relations) can be resolved before sending.

CacheManager​

Persists synchronization data locally for fast startup and offline resilience.

Cache Structure​

CacheStorage KeyContents
Session cacheupp-stored-sessionSessionMeta + session-scoped data
Place cacheupp-stored-placeinfo-{placeid}PlaceMeta + place-scoped data

SessionMeta​

interface SessionMeta {
deviceid: string;
sessionid: string;
expiration: number; // timestamp
}

PlaceMeta​

interface PlaceMeta {
placeid: string;
expiration: number; // timestamp
}

Operations​

Load:

  1. Opens the storage database via cacheService.
  2. Reads and validates metadata (device ID, session ID, expiration).
  3. If valid, reads all cached data items.
  4. Enqueues cached data into PendingQueue via Concat.

Save:

  1. Opens the storage database.
  2. Begins a transaction.
  3. Writes metadata.
  4. Writes all data items.
  5. Flushes the transaction.

Data is stored encrypted via StorageData._Encrypt (Base64 + character obfuscation).

cacheService​

Injectable: providedIn: 'root'

Factory service that provides StorageDDBB instances based on browser capabilities.

Storage Selection​

Open(storagekey): StorageDDBB​

Creates and caches a StorageDDBB instance for the given key. Returns an existing instance if one is already open.

StorageDDBB Implementations​

Abstract Interface​

MethodDescription
Open()Opens the database
Close()Closes the database
Clean()Removes all data
Get(type, key)Retrieves a single item
Set(type, key, value)Stores a single item
Del(type, key)Deletes a single item
GetAll(type)Retrieves all items of a type
Begin()Starts a write transaction
Flush()Commits the current transaction

Storage types: META (metadata) and DATA (actual data).

IndexedDDBB​

Full IndexedDB implementation:

  • OpenDatabase: Creates or opens an IndexedDB database with object stores for META and DATA.
  • CloseDatabase: Closes the IDB connection.
  • RunInStore: Executes operations within IDB transactions (readonly or readwrite).
  • Begin/Flush: Uses IDB transactions for atomic writes.
  • Provides the best performance and storage capacity for large datasets.

CacheDatabase​

In-memory fallback implementation:

  • Uses Map objects (_itemsmeta, _itemsdata) for in-memory storage.
  • Backed by storeService (which wraps localStorage) for persistence across page reloads.
  • Suitable for smaller datasets or browsers without IndexedDB support.

StorageData (Encryption)​

Data items are stored encrypted:

  • _Encrypt(data): Serializes to JSON, converts to Base64, applies character-level obfuscation.
  • _Decrypt(data): Reverses the obfuscation, decodes Base64, parses JSON.
  • _Merge(existing, incoming): Merges data items using updated timestamps for conflict resolution, similar to PendingQueue._MergeChange.

loginService​

Injectable: providedIn: 'root'

Handles user authentication, session management, and guest access.

Authentication Methods​

MethodParametersDescription
DoLogin(username, password, remember, singlecrc)Standard login. POSTs to login endpoint. If remember=true, stores credentials locally.
DoStored()Attempts login from stored credentials in localStorage or from a URL token. Called on app startup.
DoToken(token)Recovers a session from a URL-encoded token (e.g., deep links).
DoRegister(username, password)Creates a new user account via the register endpoint.
DoRecover(username)Initiates password recovery via the recover endpoint.
DoAccess(table, auth, token)Guest access via QR code scan. Uses the access endpoint.
DoLogout(expired)Stops the data service, clears tokens. If expired=true, attempts DoStored() for automatic re-login.
DoPassword(old, new)Changes the user's password.

Login Flow​

Session Persistence​

StoredLogin (localStorage):

  • Stored when remember=true during login.
  • Contains encrypted credentials for automatic re-login.

SessionToken (URL query parameters + localStorage):

  • Stored on every successful login.
  • Contains session ID for session recovery.
  • Updated when session, place, or QR code changes.

Event Subscriptions​

The loginService subscribes to dataService events to maintain token freshness:

EventAction
OnSessionChangedUpdates SessionToken with new session data
OnPlaceChangedUpdates SessionToken with new place data
OnSessionReleasedTriggers logout flow

Password Encoding​

_passwordEncode(password) computes a CRC32 hash of the password. The hash (not the raw password) is sent to the server.

Guest Access Flow​

Guest access is initiated by scanning a QR code at a table. The server returns a session with qrscan (central). Start binds it the same way LOGIN binds session.user: SetQrScan(session.qrscan), which applies the tenant mesa via SetQrCode(qrscan.qrcode). /sync uses QRSCAN.objid as table.

Waiting Helpers​

  • _waitForDevice(): Ensures the device ID is available before making login requests.
  • _waitForSession(): Ensures the session is established before making session-dependent requests.