Skip to main content

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';
ModeDescription
GUESTAnonymous access via QR code scan. Limited to a single place.
LOGINAuthenticated access via username/password. Can manage multiple places.

UserSession

type UserSession = {
session: string | null;
device: string | null;
};

Properties and Observables

PropertyTypeObservableDescription
AccessUserMode | nullCurrent access mode (GUEST or LOGIN)
sessionstring | nullOnSessionSession ID. URL-encoded. Emits when changed.
devicestring | nullOnDeviceDevice UUID. URL-encoded. Emits when changed.
placestring | nullCurrent place objid (for URL params in API calls)
IsExpiredbooleanOnExpiredSession expiration flag. Setting to true emits.
IsReadybooleanOnReadySystem ready flag. Setting to true emits.

Session Lifecycle

  1. App starts: stateService is created with all values null.
  2. Device identification: syncService resolves the device ID via identificationService.DeviceId() and sets state.device.
  3. Session created: After login or QR scan, syncService.Start(session) sets state.session.
  4. Place selected: When a user picks a place, state.place is set so API calls include the place parameter.
  5. Expiration: Server returns error code 2/3 → state.IsExpired = trueOnExpired emits → UI reacts.
  6. Logout: syncService.Stop() clears state.session.

viewService

Located in libs/upp-base/src/modules/view.ts. Manages all UI-related state.

ViewMode

type ViewMode = 'LOGIN' | 'USER' | 'PLACE' | 'GUEST';
ModeScreenWhen
LOGINLogin formInitial state for LOGIN access
USERUser dashboard (place selection)After successful login
PLACEPlace management / POSAfter place selection, or GUEST access
GUESTGuest (QR) viewAlias — sets View to PLACE with GUEST access

EditMode

type EditMode = 'VIEW' | 'EDIT' | 'CART';
ModePurpose
VIEWViewing place activity (tickets, orders)
EDITConfiguring place parameters (catalog, settings)
CARTCreating a new ticket (adding products)

Properties and Observables

PropertyTypeObservableDescription
ViewViewMode | nullOnViewChangedCurrent screen. Setting emits a change event.
AccessUserMode | nullDelegates to stateService.Access. Setting also updates View.
ModeEditMode | nullOnViewChangedCurrent edit mode within the place screen.
Theme'dark' | 'light'OnThemeColor theme. Guests are locked to dark.
MobilebooleanTrue if device is mobile.
DesktopbooleanTrue if device is desktop.
LegacybooleanTrue if device uses a legacy browser.
KioskbooleanOnKioskKiosk mode (virtual keyboard). Persisted in local config.
IsPOSbooleanPOS-specific behavior toggle. Persisted in local config.
MainTabstring | nullOnTabChangedPrimary tab selection (per edit mode).
ScndTabstring | nullOnTabChangedSecondary tab selection.
PanelArgany | nullOnTabChangedArguments for the secondary panel.
DefaultPanelstring | nullOnRightChangedRight panel visibility/content.
PanelPlacePanel | nullCurrent panel configuration.
IsOnlinebooleanOnOnlineNetwork connectivity status.
SwUpdatebooleanOnViewChangedService worker update available.
CanZoombooleanWhether 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:

  1. Before changing views, call GrantViewChange() which returns a Promise<boolean>.
  2. It emits OnCloseRequest to all subscribers.
  3. Each subscriber calls OnCloseResponse(allow).
  4. The promise resolves to true only 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();
});
}