Documentation

AppEngine integration

How the widget talks to ChatGateway — auth, queue routing, presence.

Every persistent piece of the widget — messages, conversations, agent assignments, presence, AI streaming — lives in AppEngine. The widget is the read/write surface; AppEngine's chat module owns the data and the routing logic. This page documents the wire protocol so you can build companion services, audit traffic, or extend the widget.

Two channels

The widget uses two parallel channels:

  1. REST — a small set of HTTP calls used at boot and for occasional one-shot operations.
  2. Socket.IO — the persistent connection that carries every message, typing event, presence change, and AI stream chunk.

The REST helper lives inside the chat-client bundle (search src/utils/request.ts and similar in chat-client/src/ for the actual helper name in your build). The socket connection is in src/components/chat-socket.tsx and the actions in src/chat-store.ts.

REST endpoints used

GET/repository/get/chat-config/{configId}No auth

Loads the chat config record on boot. Sends orgid: <orgId> header. The widget reads data.content (or data directly, depending on schema version) for theming, AI settings, availability, and offline behaviour.

POST/profile/customer/register-guestNo auth

Creates a guest customer for an unauthenticated visitor. Body: { email, name, phone }. Returns { token, guest } where token is a chat-scoped JWT and guest is the new customer record.

The widget calls this from the registration form. It stores the result under localStorage.appmint-chat-session.

Endpoint name

The exact route depends on the AppEngine deployment. Some installs expose this as /profile/customer/signup with a guest flag, others as /profile/customer/register-guest. Confirm against /discover/01-auth on your AppEngine instance.

POST/repository/file/uploadJWT

Multipart upload for attachments. Form fields: location (folder, e.g. <orgId>/chat/<chatId>) and file (the binary). Returns { path, signedUrl }.

GET/chat/history/{chatId}JWT

Loads the message history for a conversation when the visitor opens an existing chat. Some deployments use /repository/find/chat-message with a query instead — check the version of AppEngine you're targeting.

Socket.IO connection

The widget connects to AppEngine's ChatGateway. The connection URL is derived from the AppEngine base URL — by default wss://appengine.appmint.io.

Auth payload:

io(url, {
  auth: {
    orgid: orgId,
    token: chatToken,
    appId: appId,
    chatId: currentChatId,   // when restoring an existing conversation
  },
});

The gateway validates the token, joins the visitor to the org's room and the conversation room, then starts pushing events.

Events the widget emits

EventPayloadPurpose
message{ chatId, content, files?, type, sentTime }Send a chat message
typing / stop-typing{ chatId }Composer activity
request-agent{ skill?, priority?, context? }Ask to be routed to a human agent
update-context{ url, referrer, title }Notify the agent the visitor changed page
message-status{ messageId, status: 'delivered' | 'read' }Mark inbound messages as read
peer-message{ to, content, files? }Direct DM to another participant (rarely used)
peer-status{ to, statusType, message? }Status update to a specific peer

Events the widget receives

EventHandlerEffect
messageonMessageAppend to messages, play sound, fire onMessageReceived callback
typing / stop-typinginlineToggle isTyping
presenceonPresenceChangeUpdate presenceMap
statusonStatusUpdate conversationStatus
chat-assignedonChatAssignedSet assignedAgent, stop queue polling
chat-transferredonChatTransferredUpdate assignedAgent
agent-changedonAgentChangedMid-conversation agent swap
message-status-updateonMessageStatusUpdateMark a message delivered or read
ai-stream-startonAiStream* familyBegin streaming buffer
ai-stream-chunkonAiStreamChunkAppend text
ai-stream-tool-useonAiStreamToolUseShow tool-call breadcrumb
ai-stream-tool-resultonAiStreamToolResultShow tool result
ai-stream-endonAiStreamEndFinalise message
ai-stream-erroronAiStreamErrorShow error
engagehandleEngageRender engage toast (deduped via seenEngageIds)
broadcast-start / broadcast-endinlineShow / hide broadcast overlay
queue-statusinlineUpdate queueStatus while waiting

Queue routing

When the visitor's conversation needs a human agent, the widget emits request-agent with optional skill tags and priority. AppEngine's queue routing logic decides who picks up:

  1. Agents matching the skill tag, ordered by priority and current load.
  2. Failover to general-skill agents if no specialist is online.
  3. If no one is online and availability is configured, falls through to the offline form.

The widget polls queueStatus every few seconds while waiting. Once an agent accepts, AppEngine emits chat-assigned and the polling stops.

Presence

Presence is tracked per participant by the gateway. The widget joins a room scoped to the conversation; agents joining or leaving emit presence events with { email, status, role, name, timestamp }. Possible statuses: online, away, offline. Agents who have explicit office-hours availability follow the same flow — the gateway flips them to offline outside their window.

Disconnection and reconnection

If the socket drops, socket.io-client retries with exponential backoff. The widget bumps socketGeneration in the store to force a clean reconnect on auth changes (e.g., the visitor's token expires and gets refreshed). Messages sent while disconnected are queued in messages with status: 'pending' and retried on reconnect — the gateway dedupes by messageId.

Where to read more

  • AppEngine chat module: /Users/imzee/projects/appengine/src/modules/chat/ (server-side).
  • Discovery: curl https://appengine.appmint.io/discover/list then fetch the chat-related topic for the live schema.
  • Widget side: src/chat-store.ts (actions) and src/components/chat-socket.tsx (subscription wiring).