kata_cx

Headless Kata CX Dashboard chat client for Flutter — iOS, Android and Web.

Everything the CX chat widget does, with no UI attached: fetch tenant settings, send a message (streaming or not), run the CSAT survey, replay past conversations. For a ready-made interface, add kata_cx_ui.

Ported from com.kata.sdk.cx in the Android SDK. Behavioural parity is the requirement, not a goal — and it is tested, not asserted.

Status

Phase 1 feature-complete. The client, store, transport, survey and notifications are done and exercised against live staging on a physical device. saveContact is deferred, and the floating-launcher settings are parsed but not implemented — they are a web-widget concern.

Every public member carries doc comments, so the API reference is the detail. A task-first integration guide — theming, CSAT, notifications and the things that bite — is available from your Kata contact.

Install

flutter pub add kata_cx

Android needs the INTERNET permission

Not optional, and the Flutter template does not give it to you: flutter create declares INTERNET in the debug and profile manifests only, so hot reload works, and leaves main without it. A release build then reaches no network at all, and every call here fails with

KataCxHttpException: connection errored

— with entirely valid credentials, which makes it look like a token problem. In android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

Debug builds work without it, so this only appears on the first release build.

Use

await KataCx.setup(
  token: 'acme.proj123.st',   // <companyId>.<projectId>.<st|nst>
  widgetToken: '...',
  visitorId: '628123456789',  // user id or phone; drives the session id
);

final settings = await KataCx.client.settings();

await for (final event in KataCx.client.sendStreaming('Hello')) {
  switch (event) {
    case KataCxChunk(:final accumulated):
      // Re-render the bubble; chunks are incremental.
    case KataCxDone(:final response):
      if (response.isEndOfSession) { /* offer CSAT */ }
  }
}

sendStreaming works against a non-streaming (.nst) project too — the single reply arrives as one chunk and then completes, so callers need not branch on the token.

No state-management dependency: the client exposes plain Futures and Streams, so it drops into Riverpod, bloc, setState or anything else.

Visitor identity

visitorId is optional. Pass the signed-in user's id or phone and the session id becomes {visitorId}_{timestamp}, re-minted on each new conversation, exactly as the web widget does. Omit it and the SDK mints a device-scoped anon_<32 hex> id instead, so an unidentified visitor still keeps one continuous conversation on that device.

The value reaches the backend as user_id, and prefixed as session_id, on every chat and CSAT record. If a raw phone number is more PII than your analytics should hold, pass a hash or an opaque customer id — the SDK does not care which.

Notifications

Off unless configured, and pluggable so this package carries no notification dependency:

await KataCx.configureNotifications(
  config: const KataCxNotificationConfig(androidSmallIcon: '@mipmap/ic_launcher'),
  notifier: KataCxLocalNotifier(),   // from kata_cx_ui
);

Kata CX is request/response with no server push, so a notification only ever fires for a reply to a message the app itself sent — typically one landing after the visitor navigated away mid-conversation. It is not a substitute for push messaging, and nothing arrives while the app is closed. Implement KataCxNotifier yourself if you already have a notification stack.

Platform notes

  • Web uses the Fetch API for streaming, because the browser XHR adapter buffers the whole response body. CORS on the CX endpoints is open, X-Widget-Token included.
  • Storage is flutter_secure_storage, falling back to shared_preferences when the platform keystore is unusable. On web this is not real encryption, and the store holds the transcript — take that into account before persisting sensitive conversations there.
  • flutter_secure_storage is pinned to ^10.3.2. 11.0.0 declares compileSdk = 37, for which no installable platform package exists yet.

Testing against this package

package:kata_cx/testing.dart exports the doubles the SDK's own suite uses:

final middleware = FakeMiddleware()
  ..settingsBody = '{"botName":"Acme","csatEnabled":true}'
  ..chatReplies = [{'bot_response': 'Halo', 'intent': 'end_intention'}];

await KataCx.setupWithCustomServer(
  token: 'acme.proj.nst',
  widgetToken: 'cw_test',
  baseUrl: 'https://example.test',
  visitorId: 'tester',
  transport: middleware,
  storeBackend: InMemoryStoreBackend(),
);

One trap worth knowing up front: never await KataCx.reset() in a tearDown that follows a testWidgets body. The store's write queue belongs to that test's FakeAsync zone, whose clock stops being pumped when the body returns, so the await never resolves and the suite hangs until its ten-minute timeout. Reset inside the body, or unawaited it — state is cleared synchronously either way.

Parity with the Android SDK

test/parity/ diffs every request this client makes against a recording of the bytes chat-sdk-android's KataCXApi put on a real socket. The bodies are identical — key order, n_chat, the empty-string-not-omitted CSAT fields and an ISO-8601 timestamp with exactly three fractional digits included.

One divergence is declared rather than fixed: this SDK sends X-Widget-Token on the chat endpoints as well, where the Java SDK sends it only on settings and CSAT. Sending it everywhere keeps the SDK correct against whatever the middleware requires, without needing a release to follow a server-side change.

Treat the widget token as tenant routing rather than as an authentication mechanism you control: what it grants is Kata's to define, and it is scoped to one published project.

Libraries

kata_cx
Headless Kata CX Dashboard chat client.
testing
Test doubles for hosts integrating Kata CX.