Architecture
How the native container, the typed bridge, and native session ownership work.
The SDK is a native container around the Loops web runtime loaded in
mode=sdk. The web owns the conversation UI; native owns the session, routing,
and analytics. Understanding this helps you reason about callbacks and security.
The container
LoopsAIChatViewController
└─ WKWebView → chat.loopsai.com/<agentId>?mode=sdk&platform=ios…
↕ loopsAIBridge (typed message bridge)The bridge
Native and web talk over a typed message envelope on the native channel:
{ "protocolVersion": 1, "type": "nativeAction", "name": "productQuoteChanged",
"requestId": "req_123", "payload": { /* … */ } }- Web → native actions drive the host callbacks (ready, message, responding, product quote, conversation active, analytics passthrough, open URL, close).
- Native → web actions are the methods you call (context updates, search, try-on, size). See Flow modes.
- Unknown action names are ignored.
protocolVersionlets a shipped binary evolve without breaking older app versions.
Native session ownership
iOS WebKit can wipe partitioned third-party storage on app kill, which would lose the anonymous session. The SDK fixes this natively:
- It bootstraps the session against the runtime and persists the anon session in
UserDefaults. - It re-injects the session (
_lsuid/_lscid) on every WebView load, and appliespersistSessionupdates the runtime emits. - Bootstrap retries transient failures with exponential backoff.
The result: the session survives app relaunch with no extra work on your side.
Security
- Origin allowlist — only
chat.loopsai.comand your customenvironmenthost can drive the bridge; messages from other frames are ignored, and only the main frame is trusted. - Foreign links open in the system browser, never inside the WebView.
Authenticated (non-anonymous) sessions via a token provider are on the roadmap; v1 is anonymous-session based. Today the bridge accepts a token-refresh request but it is a no-op until the auth provider ships.
Host callbacks reference
| Callback | Fires when |
|---|---|
loopsAIChatDidBecomeReady | The runtime is live; safe to push context. |
loopsAIChat(_:isResponding:) | The assistant starts/stops responding. |
loopsAIChat(_:didChangeProductQuote:) | The active product quote changes (nil = cleared). |
loopsAIChat(_:didEmitAnalyticsEvent:) | A canonical analytics event crossed the bridge. |
loopsAIChatDidBecomeConversationActive | A conversation becomes active. |
loopsAIChat(_:didReceiveMessageEvent:) | A message / new-chat / overlay event. |
loopsAIChatDidRequestClose | The runtime asked to close (default: dismiss/pop). |
loopsAIChat(_:didRequestOpenURL:) | A foreign link should open externally. |
loopsAIChat(_:didFailWith:) | A load / bridge / session error occurred. |