Events
The SDK reports what happens in the chat as a stream of events, and its own state as a second stream
Listen to events
import Combine
import KindlyWebView
var subscriptions = Set<AnyCancellable>()
KindlyWebViewSDK.events
.sink { event in
if case let .ui(action) = event.detail, action.type == .closeChatBubble {
// the user minimised the chat
}
}
.store(in: &subscriptions)Events arrive on the main thread. Listening lasts as long as you keep the AnyCancellable
Events are not replayed: a listener sees only the events emitted after it started listening. Listening can start before start, so a listener added at launch sees the first load event, which comes when the chat page first loads
Event types
rawType | iOS, Flutter and React Native | Android | Emitted when |
|---|---|---|---|
kindly:load | load | LOAD | The widget loaded the bot's settings |
kindly:message | message | MESSAGE | A message was added to the conversation |
kindly:buttonclick | buttonClick | BUTTON_CLICK | The user clicked a message button |
kindly:ui | ui | UI | A UI action happened, see UI actionsīģŋ |
kindly:globalicon | globalIcon | GLOBAL_ICON | A global icon was clicked |
kindly:state | state | STATE | The SDK state changed, see Stateīģŋ |
The chat hides the widget's global icons, so globalIcon events practically never arrive. Flutter and React Native report a type they do not know as unknown and keep its name in rawType
Message events
The chat emits a message event only while it is open, and not for the messages already there when it first renders: restored history, or the reply to a dialogue triggered as the chat opens, which on a new chat page often lands before that first render. Messages that arrive while the chat is hidden produce no event either. The next message event's chatLog carries all of them
UI actions
Widget value | iOS and Flutter | Android | Happens when |
|---|---|---|---|
open-chatbubble | openChatBubble | OPEN_CHAT_BUBBLE | The chat opened |
close-chatbubble | closeChatBubble | CLOSE_CHAT_BUBBLE | The chat was minimised or closed |
end-chat | endChat | END_CHAT | The conversation ended |
restart-chat | restartChat | RESTART_CHAT | The conversation restarted |
download-chat | downloadChat | DOWNLOAD_CHAT | The user downloaded the conversation |
delete-chat | deleteChat | DELETE_CHAT | The user deleted the conversation |
change-language | changeLanguage | CHANGE_LANGUAGE | The user changed the chat language |
show-feedback | showFeedback | SHOW_FEEDBACK | The chat showed a feedback form |
submit-feedback | submitFeedback | SUBMIT_FEEDBACK | The user submitted feedback |
dismiss-feedback | dismissFeedback | DISMISS_FEEDBACK | The user dismissed the feedback form |
welcome-page-start-chat | welcomePageStartChat | WELCOME_PAGE_START_CHAT | The user started the chat from the welcome page |
endChat and kill finish with an end-chat and then a close-chatbubble event that the SDK emits itself, with the source programmatic. The SDK also emits close-chatbubble when the chat closes without the widget reporting it. An event the SDK emits can have an empty chatId
The action is action.type, a KindlyUIActionType, in the .ui(action:) detail. action.source is user, or programmatic when a call to the SDK caused it
What an event carries
KindlyEvent has type, detail, timestamp (epoch milliseconds, also as a Date in date), rawType and detailJSON, the detail as JSON text. detail is a KindlyEventDetail with one case per type: .load(settings:), .message(newMessage:chatLog:), .buttonClick(button:buttonType:chatLog:lastMessage:), .ui(action:), .globalIcon(action:) and .state(state:)
The SDK's enums can gain cases in a later version, so give a switch over one of them a default or @unknown default case
The SDK caps each detail at 4,000 characters of message text, the last 50 messages of the chat log, and 20 buttons, 20 images and 10 attachments per message. URLs in events have their query removed, and attachment download links are never included. Every value comes from the chat page, so treat it as untrusted input, and message events carry chat content, so keep them out of your logs
State
KindlyWebViewSDK.state
.sink { state in
print(state.isChatDisplayed, state.connectionState)
}
.store(in: &subscriptions)KindlyWebViewSDK.currentState reads it without subscribing
A new listener gets the current state first, then every change. The same changes also arrive as state events
Field | Meaning |
|---|---|
isStarted | start was called and kill has not run since |
isChatDisplayed | The chat screen is visible |
isSocketConnected, isConnected | Both mean the chat page is loaded, also while the chat is hidden |
connectionState | Where the chat page is, one of the values below |
connectionState | Meaning |
|---|---|
none | No chat page yet, before the first display or after kill |
loading | The chat page is loading |
connected | The chat page is loaded. It stays loaded while the chat is hidden |
failed | The chat page failed to load, or it loaded and the chat did not open, even after one more try |
unsupported | This device's web view cannot run the chat |
disconnected | The chat page was released: after endChat, or while the chat was hidden, when memory ran low or the system ended the web view's process. The next display loads a new one |
On iOS and Android connectionState is a string. Flutter's KindlyWebViewConnectionState and React Native's string add unknown for a value this version does not know, with the raw value in Flutter's rawConnectionState and React Native's raw.connectionState