kata_cx 1.0.1
kata_cx: ^1.0.1 copied to clipboard
Headless Kata CX Dashboard chat client for Flutter — iOS, Android and Web. Send messages, stream replies, run the CSAT survey, with no UI attached.
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-Tokenincluded. - Storage is
flutter_secure_storage, falling back toshared_preferenceswhen 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_storageis pinned to^10.3.2. 11.0.0 declarescompileSdk = 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.