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

flowchart TB
subgraph syncService
CONN[Connection]
SYNC[Syncronizator]
PQ[PendingQueue]
RM[ResolvedMap]
CM[CacheManager]
end

subgraph Server
API[sync endpoint]
end

subgraph dataStorage
DS[resolved / awaiting]
end

CONN -->|triggers refresh| SYNC
SYNC -->|POST changes + request| API
API -->|response with updates| SYNC
SYNC -->|enqueue updates| PQ
SYNC -->|uuid → objid| RM
SYNC -->|persist locally| CM
PQ -->|dequeue + apply| DS
RM -->|resolve awaiting objects| DS
CM -->|load cached data| PQ

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

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

sequenceDiagram
participant Timer
participant Connection
participant Syncronizator
participant Server

Timer->>Connection: repeatRefresh() [every MainRefresh ms]
Connection->>Syncronizator: DoRefresh()
Syncronizator->>Server: POST /sync
Server-->>Syncronizator: UpdateResponse
Syncronizator->>Syncronizator: Process response
Syncronizator->>Connection: Schedule next refresh

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, and their sub-objectsCatalog 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, AuditLower-priority place metadata
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

flowchart TD
A[cacheService.Open] --> B{IndexedDB available?}
B -->|Yes| C[Create IndexedDDBB]
B -->|No| D[Create CacheDatabase]
C --> E[Return StorageDDBB]
D --> E

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

sequenceDiagram
participant UI
participant loginService
participant Server
participant dataService
participant syncService

UI->>loginService: DoLogin(user, pass, remember)
loginService->>loginService: _passwordEncode(pass) [CRC32]
loginService->>Server: POST /login
Server-->>loginService: { session, user, place? }
loginService->>loginService: Store SessionToken (URL + localStorage)
loginService->>loginService: Store login if remember=true
loginService->>dataService: Start(session, LOGIN)
dataService->>syncService: Start(session)
syncService->>Server: connection/create
dataService->>dataService: SetUser(user)
dataService->>dataService: SetPlace(place) [if provided]
loginService-->>UI: Login complete

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

sequenceDiagram
participant Guest
participant loginService
participant Server
participant dataService

Guest->>loginService: DoAccess(table, auth, token)
loginService->>Server: POST /access
Server-->>loginService: { session, qrcode }
loginService->>dataService: Start(session, GUEST)
dataService->>dataService: SetQrCode(qrcode)
loginService-->>Guest: Access granted

Guest access is initiated by scanning a QR code at a table. The server returns a session scoped to the specific QR code/table, with limited permissions compared to a full login.

Waiting Helpers

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