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
| Service | Purpose |
|---|---|
languageService | Translations for error messages |
storeService | Local storage access |
cacheService | Database abstraction for caching |
adhocService | HTTP request handling |
stateService | Application state management |
clockService | Time and refresh ticks |
toastService | User-facing notifications |
identificationService | Device identification |
alertService | Modal alerts |
Properties and Observables
| Property / Observable | Type | Description |
|---|---|---|
session | string | Current session ID |
device | string | Current device ID |
user / place / qrcode | string | null | Current entity IDs for connection triggers |
OnReleased | Observable<void> | Connection was released |
OnExpired | Observable<void> | Session expired |
IsReady | Observable<void> | Initial sync completed |
OnChange | Observable<void> | Data changed (from server or local commit) |
Lifecycle
| Method | Description |
|---|---|
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:
| Trigger | Value | Purpose |
|---|---|---|
SESSION | Session ID | Always present; identifies the client session |
USER | User objid | Present in LOGIN mode; subscribes to user-scoped data |
PLACE | Place objid | Present when a place is selected; subscribes to place data |
QRCODE | QrCode objid | Present in GUEST mode; subscribes to table-scoped data |
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
connection/create | POST | Create a new connection with initial triggers |
connection/update | POST | Update triggers (e.g., when place changes) |
connection/delete | POST | Delete the connection (on logout/stop) |
connection/cancel | POST | Cancel 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:
- Invokes
Syncronizator.DoRefresh(). - On completion (success or failure), schedules the next refresh.
- The cycle continues until the connection is stopped.
Connection Status
| Status | Meaning |
|---|---|
RUNNING | Connection is active and refreshing |
STOPPED | Connection has been stopped |
EXPIRED | Session 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:
-
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.
-
Build request: Creates an
UpdateRequestwith: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
CommitChangeobjects.
-
Send POST to
/sync: The single endpoint handles both sending changes and receiving updates. -
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.
- UUID resolution: Maps temporary client UUIDs to server-assigned objids via
-
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:
- Merge: Combines new changes with any existing queued changes (from previous flush calls that haven't been sent yet).
- CheckChanges: Sorts changes by dependency order and validates that all dependencies are met.
- Persist locally: Stores changes via
SetStoredChangesfor crash recovery — if the app closes before the changes are sent, they can be recovered on restart. - 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):
| Priority | Tables | Rationale |
|---|---|---|
| 1.0–1.9 | Session, FCM, User, Staff, Place | Core entities loaded first (synchronous) |
| 2.0–2.9 | Address, Stripe, PayAccount, RaspPi, PlaceOpt, PlaceLink | Infrastructure dependencies |
| 3.0–3.9 | Product, Offer, Extra, Discount, Category, Preselect, ProductOpt, and their sub-objects | Catalog data |
| 4.0–4.9 | Family, FamilyProduct, FamilyPeriod | Family groupings (depend on products) |
| 5.0–5.9 | Ticket, TicketProduct, TicketOffer, TicketExtra, TicketDiscount, TicketChange, etc. | Ticket data (depends on catalog) |
| 6.0–6.9 | PlaceInfo, InvoiceBusiness, Audit | Lower-priority place metadata |
| 8.0–8.9 | Repeated/lower priority items | Catch-all for retries |
Processing Algorithm
Updates are dequeued and applied in blocks:
- Block size: Between 500 and 5,000 items per block.
- Time budget: Maximum 50ms per block to avoid blocking the UI thread.
- Yield: Between blocks, control is yielded via
setTimeout(0)to allow UI rendering. - Stage tracking: Processing is divided into stages (0–9).
OnRefreshPercentemits progress as a percentage.
Conflict Resolution
_MergeChange(existing, incoming) resolves conflicts when the same object has both a local and server update:
- Compares
updatedtimestamps. - The version with the later timestamp wins.
- If timestamps are equal, the incoming (server) version takes precedence.
Enqueue and Dequeue
| Method | Description |
|---|---|
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:
| Method | Description |
|---|---|
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
| Cache | Storage Key | Contents |
|---|---|---|
| Session cache | upp-stored-session | SessionMeta + session-scoped data |
| Place cache | upp-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:
- Opens the storage database via
cacheService. - Reads and validates metadata (device ID, session ID, expiration).
- If valid, reads all cached data items.
- Enqueues cached data into
PendingQueueviaConcat.
Save:
- Opens the storage database.
- Begins a transaction.
- Writes metadata.
- Writes all data items.
- 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
| Method | Description |
|---|---|
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
Mapobjects (_itemsmeta,_itemsdata) for in-memory storage. - Backed by
storeService(which wrapslocalStorage) 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 usingupdatedtimestamps for conflict resolution, similar toPendingQueue._MergeChange.
loginService
Injectable: providedIn: 'root'
Handles user authentication, session management, and guest access.
Authentication Methods
| Method | Parameters | Description |
|---|---|---|
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=trueduring 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:
| Event | Action |
|---|---|
OnSessionChanged | Updates SessionToken with new session data |
OnPlaceChanged | Updates SessionToken with new place data |
OnSessionReleased | Triggers 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.