Classes
KindlyChatClient
class
public final class KindlyChatClient: LoggableiOS SDK for connecting and interacting with the Kindly Chat API.
Member | Declaration | Description |
|---|---|---|
shared | public static var shared: KindlyChatClient! | The shared singleton instance of the Kindly Chat SDK. Available after calling start(). |
config | public static var config: KindlySDKConfig | The SDK configuration for permissions, auth callbacks, and form settings. |
connectionState | public static var connectionState: ChatConnectionState | The current connection state of the SDK. |
inputFieldHidden | public static var inputFieldHidden: Bool | Whether the input field is currently hidden |
delegate | public static var delegate: KindlyChatClientDelegate! | A static delegate that gets or sets the delegate of the shared instance of KindlyChatClient. |
init(config:behaviorType:) | public init(config: CoreSDKConfig, behaviorType: BehaviorTypeEnum) | Initialize Kindly Chat SDK. In most cases, you will only want to have one instance of KindlyChatClient. Our "Getting started" guide covers this topic and gives you instructions on how to configure the SDK. Parameters - config: API keys and other configuration of type KindlySDKConfig. |
events | static var events: AnyPublisher<KindlyEvent, Never> | Unified event publisher for all Kindly events (matches web API pattern) Subscribe to this to receive all events: load, message, buttonClick, ui, state |
state | static var state: AnyPublisher<KindlyEventState, Never> | Unified state publisher that emits state change events Subscribe to this to get real-time updates of: isChatDisplayed, isConnected, isStarted, isSocketConnected |
notificationDelegate | static var notificationDelegate: NotificationDelegate | The shared notification delegate for handling Kindly push notifications. Assign this to UNUserNotificationCenter.current().delegate to let the SDK process incoming notifications. |
verboseLogging | static var verboseLogging: Bool | The current state of the SDK. |
setCustomTheme(_:) | static func setCustomTheme(_ theme: Theme) | Sets a custom theme for the chat. Parameters - theme: The theme to be applied to the chat. This theme will override any theme retrieved from Kindly's settings. |
setCustomTheme(background:botMessageBackground:botMessageText:buttonBackground:buttonOutline:buttonSelectedBackground:buttonText:defaultShadow:inputBackground:inputCursor:inputText:navBarBackground:navBarText:userMessageBackground:userMessageText:) | static func setCustomTheme( background: UIColor? = nil, botMessageBackground: UIColor? = nil, botMessageText: UIColor? = nil, buttonBackground: UIColor? = nil, buttonOutline: UIColor? = nil, buttonSelectedBackground: UIColor? = nil, buttonText: UIColor? = nil, defaultShadow: UIColor? = nil, inputBackground: UIColor? = nil, inputCursor: UIColor? = nil, inputText: UIColor? = nil, navBarBackground: UIColor? = nil, navBarText: UIColor? = nil, userMessageBackground: UIColor? = nil, userMessageText: UIColor? = nil ) | Sets a custom theme for the chat using the CustomTheme struct. Parameters - background: Background color for the chat interface. - botMessageBackground: Background color for bot message bubbles. - botMessageText: Text color for bot messages. - buttonBackground: Background color for buttons. - buttonOutline: Outline color for buttons. - buttonSelectedBackground: Background color for selected buttons. - buttonText: Text color for buttons. - defaultShadow: Default shadow color. - inputBackground: Background color for the input field. - inputCursor: Color for the input cursor. - inputText: Text color for the input field. - navBarBackground: Background color for the navigation bar. - navBarText: Text color for the navigation bar. - userMessageBackground: Background color for user message bubbles. - userMessageText: Text color for user messages. |
clearCustomTheme() | static func clearCustomTheme() | Clears any custom theme that has been set and reverts to the API theme or default theme. |
openChatOnNotificationTap | static var openChatOnNotificationTap: Bool | Whether tapping a Kindly notification opens the chat. Defaults to true. When the tap arrives before start() (the notification launched the app) the SDK remembers it and opens the chat once start() has been called and the app is active. A tap older than a minute is dropped. |
notificationMode | static var notificationMode: KindlyNotificationMode | How Kindly push notifications reach this app. Defaults to KindlyNotificationMode.automatic. The SDK tells the backend when it registers for push, and the backend sends a visible, decrypted-on-device alert only to an app that can decrypt it. With .automatic the SDK detects that by itself: it looks for a Notification Service Extension in your app that is built on the Kindly SDK. Set .alert or .silent to override. A change applies from the next token registration, which happens every time the chat connects. |
enableCrashReporting | static var enableCrashReporting: Bool | Whether the SDK should send crash reports via Sentry. Defaults to true. |
start(botKey:languageCode:market:authTokenCallback:) | class func start(botKey: String, languageCode: String = "en", market: String, authTokenCallback: AuthTokenCallback? = nil) | Start the Kindly Chat SDK. The SDK will automatically use .native behavior for optimal performance and compliance. - botKey: The bot key for the bot you want to connect to. - languageCode: The language code for the chat. Defaults to "en". - market: Required market slug for the "Markets" feature (different UI/greeting/responses per location). You must pass the market configured for the bot. The SDK loads that market's settings (/settings/{botKey}/{market}.json) and tells /connect which market was picked. If the market's settings can't be loaded the chat won't load — there is no fallback to another market and no per-bot default settings. Find your market slugs in the Kindly platform (Settings → General → Details & markets). - authTokenCallback: A callback function that will be called when the authentication token is available. |
start(botKey:languageCode:market:authCredentialsCallback:) | class func start( botKey: String, languageCode: String = "en", market: String, authCredentialsCallback: @escaping AuthCredentialsCallback ) | Starts the SDK with full authentication credentials rather than a bare token. Use this overload when your identity provider issues more than a single JWT — a companion access token, an issuer the backend has to verify against, or an opaque token whose expiry only you know. Everything else behaves exactly like the token overload. - botKey: The bot key for the bot you want to connect to. - languageCode: The language code for the chat. Defaults to "en". - market: Required market slug — see the token overload. - authCredentialsCallback: Called with the current chatId and a Promise you fulfil with a KindlyAuthCredentials. |
displayChat(languageCode:triggerDialogueId:) | class func displayChat(languageCode: String? = nil, triggerDialogueId: String? = nil) | This function is used to display the chat interface. It first checks if there is a key window available, and if there is a root view controller. If both conditions are met, it creates a new instance of KindlyChatNavigationController. Parameters - languageCode: Optional language code to use for this chat session. If provided, it will override the language set during SDK start. - triggerDialogueId: Optional dialogue ID to trigger after connection. |
displayChat(languageCode:triggerDialogueId:) | func displayChat(languageCode: String? = nil, triggerDialogueId: String? = nil) | This function is used to display the chat interface. It first checks if there is a key window available, and if there is a root view controller. If both conditions are met, it creates a new instance of KindlyChatNavigationController. Parameters - languageCode: Optional language code to use for this chat session. If provided, it will override the language set during SDK start. - triggerDialogueId: Optional dialogue ID to trigger after connection. |
setLanguage(_:) | class func setLanguage(_ languageCode: String) -> Promise<Void> | Update the language for the current SDK session. This will be used for all subsequent chat connections. Parameters - languageCode: The language code to use (e.g., "en", "lt", "sv") Returns - Promise that resolves when language change is complete |
setNewContext(_:) | class func setNewContext(_ context: [String: String]) | Set the current context that will be set in the next message or click request Parameters - context: context to be set |
setNewContext(_:) | func setNewContext(_ context: [String: String]) | Set the current context that will be set in the next message or click request Parameters - context: context to be set |
clearNewContext() | class func clearNewContext() | Clear the currently set context |
clearNewContext() | func clearNewContext() | Clear the currently set context |
sendMessage(_:newContext:) | @discardableResult class func sendMessage(_ text: String, newContext: [String: String]? = nil) -> Promise<ExternalChatMessage> | Send a message on behalf of the user, programmatically. Lets the host app drive the conversation as if the user typed and tapped send. The SDK renders the bubble locally with a temporary id and reconciles it with the server-assigned id when the POST response returns — do not also display the message yourself, and do not add it to any external chat log you maintain. Behaviour: - Trims whitespace; an empty/whitespace-only string rejects with KindlySDKError.invalidData. - Rejects with KindlySDKError.chatNotConnected if called before the chat is connected. Use KindlySDK.state to observe isConnected before calling. - Per-call newContext overrides any context previously staged via setNewContext(_:). Parameters - text: The message text to send. Whitespace is trimmed. - newContext: Optional per-call context attached to this message only. Returns - A Promise<ExternalChatMessage> that resolves with the reconciled message (carrying the server-assigned id and timestamp), or rejects with the underlying error. |
setAPNSDeviceToken(_:) | class func setAPNSDeviceToken(_ deviceToken: Data) | Sets the device token. Parameters - deviceToken: The device token to be set. It is of type Data. |
setAPNSDeviceToken(_:) | class func setAPNSDeviceToken(_ deviceToken: String) | Sets the device token. Parameters - deviceToken: The device token to be set. It is of type String. |
saveAuthToken(_:) | @discardableResult class func saveAuthToken(_ token: String) -> Bool | Saves the authentication token in the Keychain. Parameters - token: The authentication token to be saved. |
saveAuthCredentials(_:) | @discardableResult class func saveAuthCredentials(_ credentials: KindlyAuthCredentials) -> Bool | Saves full authentication credentials in the Keychain. The direct equivalent of saveAuthToken(_:) for integrations whose identity provider issues more than a single JWT. The SDK uses these until they expire or the backend rejects them, then falls back to the callback given at start(...) if there is one. Parameters - credentials: The credentials to store. Returns - false if start() has not been called yet, or the Keychain refused the write. |
authenticate() | @discardableResult class func authenticate() -> Promise<Void> | Re-authenticates the live chat, asking the host callback for fresh credentials. For the case the SDK cannot detect on its own: the user signed in, or switched account, while the chat was already open. The SDK refreshes on expiry by itself, so this is only needed when the identity changed rather than the token. The chat, its history and its socket are all kept — this re-binds the identity in place and does not restart anything. Returns - A promise that fulfils once the backend has accepted the new credentials, and rejects if the callback failed or the backend refused them. |
deauthenticate() | @discardableResult class func deauthenticate() -> Promise<Void> | Signs the user out of the chat without ending it. Unbinds the identity on the backend and forgets the stored credentials. The chat carries on anonymously with its history intact — use endChat() instead when the conversation itself should end. Safe to call when the chat is already anonymous; it fulfils immediately. The local credentials are cleared even when the backend call fails, so a sign-out is never left half done, but the promise still rejects so the host knows the backend did not hear about it. Returns - A promise that fulfils once the identity has been unbound. |
saveAuthToken(_:) | @discardableResult func saveAuthToken(_ token: String) -> Bool | Saves the authentication token in the Keychain. Parameters - token: The authentication token to be saved. |
saveAuthCredentials(_:) | @discardableResult func saveAuthCredentials(_ credentials: KindlyAuthCredentials) -> Bool | Saves full authentication credentials in the Keychain. Replaces whatever was stored before, including the previous access token, issuer and expiries — a partial overwrite would leave the SDK sending one credential's access token alongside another's id token. |
closeChat() | class func closeChat() | This function is used to close the chat by dismissing the kindlyChatNavigationController. |
closeChat() | func closeChat() | This function is used to close the chat by dismissing the kindlyChatNavigationController. Always dispatches to the main thread — the public KindlySDK.closeChat() is callable from any thread, and the internal endChat() invokes this from the disconnect() onComplete callback which runs on the .promises background queue (Promise.then's default). UIKit dismiss(animated:) must be called on main; calling from background triggers Main Thread Checker warnings (and rare race-conditions in older iOS). |
endChat() | class func endChat() -> Promise<Void> | Ends the active chat session. Messages, token and current connection will be cleared. This will call the "user left" API in Kindly Chat. Note Please note that the Chat UI will be dismissed after using this function. |
kill() | class func kill() -> Promise<Void> | Completely kills the SDK and clears all state. After calling this method, start() must be called again before using any other SDK functions. This is more destructive than endChat() which only disconnects the chat session. |
endChat() | func endChat() -> Promise<Void> | Ends the active chat session. Messages, token and current connection will be cleared. This will call the "user left" API in Kindly Chat. Note Please note that the Chat UI will be dismissed after using this function. |
notificationReceived(_:) | class func notificationReceived(_ userInfo: [AnyHashable: Any]) | Handles a received notification from the didReceiveRemoteNotification path (silent pushes). The SDK decrypts it, asks KindlyChatClientDelegate.shouldHandleNotification, and posts a local banner unless the user is on the chat screen. For a visible push delivered while the app is in the foreground use notificationWillPresent(_:completionHandler:) instead, which can also decide what iOS presents. Parameters - userInfo: The user information dictionary that contains data related to the notification. |
notificationReceived(_:) | class func notificationReceived(_ notification: UNNotification) | Handles the received notification. Parameters - notification: The notification that was received. |
notificationWillPresent(_:completionHandler:) | class func notificationWillPresent(_ notification: UNNotification, completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) | Decides what iOS should present for a notification delivered while the app is in the foreground. Call it from your own UNUserNotificationCenterDelegate.willPresent and pass the completion handler through. The SDK returns banner and sound for notifications that are not Kindly's, and for Kindly notifications applies its display rule: nothing while the user is on the chat screen, the decrypted content when the Notification Service Extension already rewrote it, and its own decrypted banner in place of the placeholder when there is no extension. Parameters - notification: The notification handed to willPresent. - completionHandler: The completion handler handed to willPresent. |
notificationResponseReceived(_:) | class func notificationResponseReceived(_ response: UNNotificationResponse) | Handles the received notification response (the user tapped the notification). Opens the chat when openChatOnNotificationTap is on and the notification is Kindly's. Parameters - response: The notification response that was received. |
isKindlyNotification(_:) | class func isKindlyNotification(_ userInfo: [AnyHashable: Any]) -> Bool | Checks if the notification is from Kindly. Works before start(), so it can route a tap that launched the app. Parameters - userInfo: The user information dictionary from the notification. Returns - A boolean indicating whether the notification is from Kindly. |
triggerDialogue(id:) | class func triggerDialogue(id: String) | Triggers a specific dialogue by its ID. The SDK must be connected before calling this method. Parameters - id: The dialogue ID to trigger. |
handleURL(_:) | @discardableResult class func handleURL(_ url: URL) -> Bool | Handles a Kindly deep link URL. Supported format: kindly://chat/dialogue/{id} Parameters - url: The URL to handle. Returns - true if the URL was recognized and handled, false otherwise. |
_forceConnectionState(_:) | static func _forceConnectionState(_ state: ChatConnectionState) | Force a connection state for testing purposes. This method is only available in DEBUG builds. Warning This is for internal testing only and should never be used in production. |
KindlyNotificationServiceExtension
class
open class KindlyNotificationServiceExtension: UNNotificationServiceExtensionBase class for the host app's Notification Service Extension.
Subclass it in the extension target and nothing else is needed:
import Kindly
final class NotificationService: KindlyNotificationServiceExtension {}iOS runs the extension for every push that carries mutable-content: 1, in every app state including when the app was killed. The extension decrypts the Kindly payload with the key the SDK stored in the shared keychain group during token registration, rewrites the title and body, and marks the content as decrypted so the SDK inside the app never shows it a second time. Pushes that are not Kindly's, and Kindly pushes that cannot be decrypted (no key yet, device not unlocked since boot), are delivered unchanged, which for a Kindly push means the generic placeholder the backend put in aps.alert.
Override customize(_:payload:) to add a category, thread identifier, attachments or a different sound. Hosts that already have their own extension for another provider keep it and call KindlyPushDecryption.decrypt(userInfo:) plus KindlyPushDecryption.apply(_:to:) themselves.
Both the app target and the extension target need the same keychain-access-groups entitlement so the extension can read the key; see the push guide.
Member | Declaration | Description |
|---|---|---|
init() | override public init() | |
didReceive(_:withContentHandler:) | override open func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) | |
serviceExtensionTimeWillExpire() | override open func serviceExtensionTimeWillExpire() | |
customize(_:payload:) | open func customize(_ content: UNMutableNotificationContent, payload: KindlyPushDecryption.DecryptedPayload?) | Called after the SDK rewrote the content and before it is handed back to iOS. payload is nil when decryption failed and the placeholder is still in place. The default does nothing. |
NotificationDelegate
class
public class NotificationDelegate: NSObject, UNUserNotificationCenterDelegateHandles push notification presentation and user interaction for the Kindly SDK. Set as UNUserNotificationCenter.current().delegate to let the SDK process Kindly notifications.
Notifications that are not Kindly's are presented with banner and sound, as before. For Kindly notifications the SDK decrypts, asks KindlyChatClientDelegate.shouldHandleNotification, shows nothing while the user is on the chat screen, presents content the Notification Service Extension already decrypted, and replaces a placeholder with its own decrypted banner when there is no extension.
Member | Declaration | Description |
|---|---|---|
userNotificationCenter(_:willPresent:withCompletionHandler:) | public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) | |
userNotificationCenter(_:didReceive:withCompletionHandler:) | public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) | |
Promise
class
public class Promise<T>A lightweight promise implementation for asynchronous operations in the Kindly SDK.
Used by SDK methods like endChat(), setLanguage(), kill(), sendMessage(), and most API.* calls which all return Promise<T>. Chain results with .then { } and handle errors with .catch { }. Combine multiple promises with whenAll(_:) (defined in PromiseAwait.swift).
Typical usage
API.greet()
.then { result in /* handle success */ }
.catch { error in /* handle failure */ }Threading
Callbacks default to DispatchQueue.promises (background, concurrent). If you touch UI or @Published, opt into main:
API.connect().then(on: .main) { /* safe for UIKit */ }Internal design quirk: outer vs inner promise
When created via the convenience init Promise<T> { resolve, reject in … }, this class holds both self AND a separate inner Promise<T> (referenced by self.promise). The work-block's resolve/reject callbacks operate on self. The external .fulfill(_:) / .reject(_:) methods operate on self.promise. For Promises created via the plain init(), self.promise == self (lazy) so both paths converge on the same state — but for work-block-initialized Promises, the two paths target different objects. Do not mix the two patterns. Either resolve via the work block's closure, or use .fulfill() externally — never both.
Member | Declaration | Description |
|---|---|---|
onFulfilled | public typealias onFulfilled = (T) throws -> Void | Closure type for success callbacks. May throw; thrown errors are converted to _reject(_:) automatically by the dispatch in then. |
onRejected | public typealias onRejected = (Error) -> Void | Closure type for failure callbacks. Cannot throw. |
resolve(_:) | public static func resolve(_ value: T) -> Promise<T> | Returns a Promise already in the .fulfilled state with the given value. Useful for short-circuit returns in code paths that must return a Promise<T> but already have the value synchronously. |
reject(_:) | public static func reject(_ error: Error) -> Promise<T> | Returns a Promise already in the .rejected state with the given error. |
fulfill(_:) | func fulfill(_ value: T) | Resolves this Promise with the given value. Operates on self.promise (the inner reference) — see "Internal design quirk" in the class doc. For Promises made via the plain init(), self.promise == self. For Promises made via the work-block init Promise(on:_:), self.promise is a separate object — calling .fulfill() on those will resolve the inner, not the outer that was returned to the caller. Don't mix patterns. |
reject(_:) | func reject(_ error: Error) | Rejects this Promise with the given error. Same caveat as .fulfill(_:) above. |
then(on:_:) | @discardableResult func then(on queue: DispatchQueue = .promises, _ onFulfilled: @escaping onFulfilled) -> Promise<T> | Registers a success callback. The closure is dispatched to queue (default .promises, a background concurrent queue) when (or once) the Promise is fulfilled. Thread-safe: lock ensures the isClosed check + callbacks.append are atomic w.r.t. _resolve/_reject. Callback execution itself happens via queue.async, so user code never runs while holding lock. If the Promise is already cancelled (or gets cancelled later), onFulfilled will not fire — the inner state == .fulfilled check gates it. Parameters - queue: Queue on which to invoke onFulfilled. Default is .promises (background). For UI work, pass .main. - onFulfilled: Called with the resolved value. May throw — thrown errors trigger _reject(_:) on this Promise. Returns - self for chaining. |
catch(on:_:) | @discardableResult func `catch`(on queue: DispatchQueue = .promises, _ onRejected: @escaping onRejected) -> Promise<T> | Registers a failure callback. Dispatched to queue (default .promises) when the Promise is rejected. Same thread-safety guarantees as then(on:_:). Cancelled Promises do NOT fire .catch (cancellation is distinct from rejection). Parameters - queue: Queue on which to invoke onRejected. Default .promises (background). - onRejected: Called with the rejection error. Returns - self for chaining. |
always(on:_:) | @discardableResult func always(on queue: DispatchQueue = .promises, _ block: @escaping () -> Void) -> Promise<T> | Registers a callback that fires regardless of fulfillment or rejection — like a finally block. Dispatched to queue (default .promises) once the Promise resolves either way. Note Does NOT fire on cancellation — .always runs only when the Promise actually resolves (fulfilled or rejected). Cancelled Promises drain their callbacks list without firing them. If you need a true "fires-no-matter-what" hook including cancellation, you'll need to track cancellation separately. Parameters - queue: Queue on which to invoke block. Default .promises (background). - block: Called with no arguments. Cannot inspect value/error here. Returns - self for chaining. |
cancel() | func cancel() | Cancel this Promise. Pending callbacks (.then, .catch, .always already attached) will not fire. Callbacks attached AFTER cancellation also will not fire (their inner state check gates them). No-op if the Promise has already resolved (fulfilled/rejected) or already been cancelled. Cancellation only takes effect on a still-pending Promise. Does NOT abort underlying work. This Promise impl has no concept of "abort the network request that's filling this Promise". If your network call later calls .fulfill() / .reject() on a cancelled Promise, those calls silently no-op (existing state == .pending guard) — the result just gets dropped. The HTTP/socket request continues to run on the wire. await() caveat: a Promise cancelled while await() is suspended will cause await() to block forever (semaphore is never signalled). Don't mix cancel() with await() on the same Promise. |
isCancelled | var isCancelled: Bool | true if this Promise has been explicitly cancelled via .cancel(). False for pending, fulfilled, and rejected Promises. |
pipe(to:) | func pipe(to target: Promise<T>) | Forwards this Promise's resolution to target. When self fulfills, target is fulfilled with the same value; when self rejects, target is rejected with the same error. Used internally by connectSDK() and similar to expose an internal Promise's result through a wrapper. |
ThemeManager
class
public final class ThemeManagerA central class to manage Theme support.
Member | Declaration | Description |
|---|---|---|
shared | public static let shared = ThemeManager() | |
currentTheme | public var currentTheme: Theme | |
setUserTheme(_:) | public func setUserTheme(_ theme: Theme) | Sets a custom user-defined theme. Parameters - theme: The custom theme to be used. |
clearUserTheme() | public func clearUserTheme() | Clears the user-defined theme and reverts to API theme or default theme. |
hasUserTheme() | public func hasUserTheme() -> Bool | Returns true if a user-defined theme has been set via setCustomTheme(). |
TypingEventManager
class
public final class TypingEventManagerManager for typing events
Member | Declaration | Description |
|---|---|---|
shared | public static let shared = TypingEventManager() | |
typingEventSubject | public let typingEventSubject = PassthroughSubject<TypingEvent, Never>() | Publisher for typing events that UI can listen to |
emitTypingEvent(_:) | public func emitTypingEvent(_ event: TypingEvent) | Emit a typing event |
emitBotStartedTyping(duration:) | public func emitBotStartedTyping(duration: TimeInterval?) | Convenience method to emit bot started typing |
emitBotStoppedTyping() | public func emitBotStoppedTyping() | Convenience method to emit bot stopped typing |
emitAgentStartedTyping(duration:) | public func emitAgentStartedTyping(duration: TimeInterval?) | Convenience method to emit agent started typing |
emitAgentStoppedTyping() | public func emitAgentStoppedTyping() | Convenience method to emit agent stopped typing |