ChatEngine
The chat state machine: bootstrap → connect → ready → frames, plus every host-facing action. Pure Kotlin/JVM; the Android module wires storage, lifecycle and UI around it.
All work runs on one confined coroutine (a single-parallelism view of dispatcher) inside scope's lifecycle, so the engine needs no locks. Observe state and events; call actions from any thread. Suspending actions finish when the work is done (a send, once acknowledged or failed); the non-suspending ones are queued in call order.
Constructors
Properties
How long after going to the background the socket is closed.
Delay before a failed bootstrap is retried (transient errors only).
Host-facing events (not replayed: subscribe before start).
Open the chat when the team starts a conversation with open: true (see openRequests).
Conversation ids the team opened proactively with open: true; the UI layer opens the screen.
Idle time after the last keystroke before typing off.
Functions
Sends a file, optionally with a caption. It shows at once as a pending bubble with upload progress; when the bytes are on the server the message goes out like text (same retry by client id). Files over 10 MB or of a type the gateway refuses fail immediately with a transient error.
Dismisses the pre-chat form without sending.
Unregisters the push token (DELETE devices), e.g. on sign-out.
Clears the error banner.
Attaches an identity. Returns once the gateway acknowledged it (or, on a merge, once the new session started). Throws HatangException (closed, timeout, or the gateway's code).
Loads the page before the oldest loaded item.
The composer text changed: sends one typing on, then off after typingIdle without keystrokes.
Back to the visitor's current conversation (the newest open one), or an empty chat when there is none, so the next message starts a new one.
Shows conversation id: its latest 50 items, live updates while it is on screen, earlier ones through loadEarlier. Writing into a resolved conversation reopens it; a closed one is read-only. summary (from listConversations) avoids a flash of the wrong status. Before the session is ready the request is remembered and honoured on ready (a notification tap on a cold start).
Signed attachment links expire after a day. When one fails to load, fetch that message again for fresh links (once per message per session).
Reports a finished store purchase: with the Play purchaseToken Hatang verifies it with Google and records it for this person; the team also gets an internal note in the conversation. False without a session, on an older gateway, or when the store refuses it.
Swaps a magic-link token (…/c/<token> from a "we replied" email) for that session.
The app moved to the foreground or background. In the background the gateway is told at once (app.state) and the socket closes after backgroundCloseDelay; back in the foreground it reconnects.
Switches the UI language; the socket re-authenticates so the gateway learns it too.
Remembers the push token and registers it (PUT devices) once a visitor token exists.
The conversation is (not) on screen. Visible clears unread and sends a read receipt for everything loaded.
Inserts a local-only bot bubble (never sent, never persisted).
Inline email request answered: identifies the visitor by email, then sends the held-back draft.
Offline pre-chat form: posts the first message (or the held-back draft) with name and email.