State Management
Application state is managed through two complementary services in libs/upp-base/src/modules/: stateService for session and identity data, and viewService for UI state. Both are singleton services (providedIn: 'root').
stateService
Located in libs/upp-base/src/modules/state.ts. Manages the core identity and session state.
UserMode
type UserMode = 'GUEST' | 'LOGIN';
| Mode | Description |
|---|---|
GUEST | Anonymous access via QR code scan. Limited to a single place. |
LOGIN | Authenticated access via username/password. Can manage multiple places. |
UserSession
type UserSession = {
session: string | null;
device: string | null;
};
Properties and Observables
| Property | Type | Observable | Description |
|---|---|---|---|
Access | UserMode | null | — | Current access mode (GUEST or LOGIN) |
session | string | null | OnSession | Session ID. URL-encoded. Emits when changed. |
device | string | null | OnDevice | Device UUID. URL-encoded. Emits when changed. |
place | string | null | — | Current place objid (for URL params in API calls) |
IsExpired | boolean | OnExpired | Session expiration flag. Setting to true emits. |
IsReady | boolean | OnReady | System ready flag. Setting to true emits. |
Session Lifecycle
- App starts:
stateServiceis created with all valuesnull. - Device identification:
syncServiceresolves the device ID viaidentificationService.DeviceId()and setsstate.device. - Session created: After login or QR scan,
syncService.Start(session)setsstate.session. - Place selected: When a user picks a place,
state.placeis set so API calls include the place parameter. - Expiration: Server returns error code 2/3 →
state.IsExpired = true→OnExpiredemits → UI reacts. - Logout:
syncService.Stop()clearsstate.session.
viewService
Located in libs/upp-base/src/modules/view.ts. Manages all UI-related state.
ViewMode
type ViewMode = 'LOGIN' | 'USER' | 'PLACE' | 'GUEST';
| Mode | Screen | When |
|---|---|---|
LOGIN | Login form | Initial state for LOGIN access |
USER | User dashboard (place selection) | After successful login |
PLACE | Place management / POS | After place selection, or GUEST access |
GUEST | Guest (QR) view | Alias — sets View to PLACE with GUEST access |
EditMode
type EditMode = 'VIEW' | 'EDIT' | 'CART';
| Mode | Purpose |
|---|---|
VIEW | Viewing place activity (tickets, orders) |
EDIT | Configuring place parameters (catalog, settings) |
CART | Creating a new ticket (adding products) |
Properties and Observables
| Property | Type | Observable | Description |
|---|---|---|---|
View | ViewMode | null | OnViewChanged | Current screen. Setting emits a change event. |
Access | UserMode | null | — | Delegates to stateService.Access. Setting also updates View. |
Mode | EditMode | null | OnViewChanged | Current edit mode within the place screen. |
Theme | 'dark' | 'light' | OnTheme | Color theme. Guests are locked to dark. |
Mobile | boolean | — | True if device is mobile. |
Desktop | boolean | — | True if device is desktop. |
Legacy | boolean | — | True if device uses a legacy browser. |
Kiosk | boolean | OnKiosk | Kiosk mode (virtual keyboard). Persisted in local config. |
IsPOS | boolean | — | POS-specific behavior toggle. Persisted in local config. |
MainTab | string | null | OnTabChanged | Primary tab selection (per edit mode). |
ScndTab | string | null | OnTabChanged | Secondary tab selection. |
PanelArg | any | null | OnTabChanged | Arguments for the secondary panel. |
DefaultPanel | string | null | OnRightChanged | Right panel visibility/content. |
Panel | PlacePanel | null | — | Current panel configuration. |
IsOnline | boolean | OnOnline | Network connectivity status. |
SwUpdate | boolean | OnViewChanged | Service worker update available. |
CanZoom | boolean | — | Whether zoom is safe (disabled on iOS Safari). |
Access / View Relationship
Setting Access automatically updates View:
set Access(value: UserMode) {
this.state.Access = value;
switch(value) {
case 'GUEST': this.View = 'PLACE'; break;
case 'LOGIN': this.View = 'LOGIN'; break;
}
}
This ensures the correct initial screen is shown for each access mode.
Panel Close Protocol
The viewService implements a cooperative close protocol for panels that may have unsaved changes:
- Before changing views, call
GrantViewChange()which returns aPromise<boolean>. - It emits
OnCloseRequestto all subscribers. - Each subscriber calls
OnCloseResponse(allow). - The promise resolves to
trueonly if all subscribers allow the change.
Subscribers are tracked via a counter, and the unsubscribe method is wrapped to decrement it automatically.
Tab Management
Tabs are stored per EditMode, so switching between VIEW/EDIT/CART preserves the selected tab in each mode:
private _maintab = new Map<EditMode, string | null>();
get MainTab(): string | null {
if (!this.Mode) return null;
return this._maintab.get(this.Mode) || null;
}
SetMainTab(mode: EditMode, value: string | null) {
this._maintab.set(mode, value);
if (mode == this.Mode) {
this._onTabChanged.next(value);
}
}
Configuration Persistence
View settings like Kiosk and IsPOS are persisted through configService:
get Kiosk(): boolean {
return this.configuration.get(UppViewConfiguration.kiosk) as boolean
|| this.platform._isKiosk;
}
set Kiosk(value: boolean) {
if (this.platform._isKiosk != value) {
this.configuration.set(UppViewConfiguration.kiosk, value);
this.platform._isKiosk = value;
}
}
This ensures settings survive page reloads.
State Flow in the Application
Device Identification
The identificationService in libs/upp-base/src/modules/device.ts generates or retrieves a unique device UUID. This UUID is:
- Persisted in local storage
- Sent with every API request via
stateService.device - Used by the server to associate connections and ticket numbering with a specific device
The syncService constructor waits for device identification to complete before emitting IsReady:
constructor(...) {
this.deviceid.DeviceId().then((deviceid) => {
this.state.device = deviceid;
this._isReady.next(true);
this._isReady.complete();
});
}