A community-driven native Swift SDK for integrating Cossistant support chat into SwiftUI apps.
Cossistant is an open-source support chat platform by Anthony Riera. The official web widget and server are maintained in the cossistantcom/cossistant repository. This SDK provides a native iOS/macOS client built on top of the same API.
- Swift 6.2+
- iOS 17+ / macOS 14+
- Dependencies: SFSafeSymbols, ULID.swift
Add the package via Swift Package Manager:
dependencies: [
.package(url: "https://github.com/seemayr/cossistant-ios.git", from: "0.1.0")
]Or in Xcode: File > Add Package Dependencies and enter the repository URL.
import Cossistant
// 1. Create the client
let client = CossistantClient(
configuration: Configuration(
apiKey: "pk_live_YOUR_KEY",
origin: "https://your-whitelisted-domain.com"
)
)
// 2. Show the support view (push into an existing NavigationStack)
SupportView(client: client)If the parent view does not already provide a NavigationStack, use SupportNavigationView instead — it wraps SupportView with its own navigation container:
SupportNavigationView(client: client, onDismiss: { dismiss() })Note: Both
SupportViewandSupportNavigationViewcallbootstrap()automatically — no manual setup needed. Theclient.bootstrap()method in the API section below is for programmatic usage without the built-in views.
Skip the conversation list and go straight to a new conversation with context:
SupportView(
client: client,
autoCreate: SupportContext(
source: "game_loading",
autoCreateConversation: true,
identity: SupportIdentity(externalId: user.id, email: user.email),
contactMetadata: VisitorMetadata(["plan": .string("pro")]),
conversationContext: VisitorMetadata([
"gameId": .string(game.id),
"groupId": .string(group.id),
]),
initialMessage: "I'm having trouble loading a game."
)
)Identity and contact metadata are attached via prepareSupportContact before the first message is sent. Conversation context is sent as metadata on the conversation create request. If preparation fails, support still opens — a dismissible banner surfaces the issue with a retry option.
| Parameter | Required | Description |
|---|---|---|
apiKey |
Yes | Your public API key (pk_live_... or pk_test_...) |
origin |
Yes | Must match a whitelisted domain in your Cossistant dashboard. Test keys accept http://localhost:3000. |
apiBaseURL |
No | REST API base URL (defaults to https://api.cossistant.com/v1) |
webSocketBaseURL |
No | WebSocket base URL (defaults to wss://api.cossistant.com/ws) |
supportEmail |
No | When set, shows a "Direct Contact" button in error views. Also settable on the client after init via client.supportEmail. |
Customize the visual appearance of the support UI with CossistantDesign:
SupportNavigationView(client: client)
.cossistantDesign(CossistantDesign(accentColor: .purple, fontDesign: .rounded))| Parameter | Default | Description |
|---|---|---|
accentColor |
App's accent color | Color for CTA buttons, visitor message bubbles, and interactive elements |
fontDesign |
.default |
Font design applied to all text (.rounded, .serif, .monospaced) |
Both parameters are optional — pass only what you want to change.
Override default UI strings or add contextual hints with CossistantContent. Set it once during app setup — no view modifier needed:
CossistantContent.current = CossistantContent(
emptyChatHumanNote: "We're a solo dev — replies may take a day or two!",
participationWaitingHint: "Our team was notified. We're a small crew — thanks for your patience!"
)| Property | Default | Description |
|---|---|---|
emptyChatDescription |
Localized SDK string | Overrides the chat empty-state description |
emptyChatHumanNote |
Localized SDK string | Overrides the human-review note in chat empty state and below messages |
emptyConversationsDescription |
Localized SDK string | Overrides the conversation list empty-state description |
participationWaitingHint |
Localized SDK string | Hint shown below the "team notified" event when a human agent is requested |
supportPreparationWarningTitle |
"Details unavailable" |
Shared title for the degraded-state banner |
supportPreparationIdentificationMessage |
Localized SDK string | Message when visitor identification fails |
supportPreparationDetailsMessage |
Localized SDK string | Message when metadata or context attachment fails |
supportPreparationDismissLabel |
"Dismiss warning" |
Accessibility label for the banner dismiss button |
All properties are optional — nil uses the SDK's built-in localized default.
Inside your own views you can read the current design tokens from the environment:
@Environment(\.cossistantDesign) private var design
var body: some View {
Text("Styled text")
.foregroundStyle(design.accentColor)
.fontDesign(design.fontDesign)
}- Real-time messaging via WebSocket with automatic reconnection
- AI agent support with typing indicators and processing state
- Conversation list with pagination
- File and image upload attachments with fullscreen image viewer
- Long-press copy on message bubbles
- Visitor identification and metadata with graceful degradation
- Conversation ratings
- Activity tracking (heartbeat, focus)
- Localized UI (English, German, Spanish, French, Italian)
- Full strict concurrency support (Swift 6)
The main entry point. When using SupportView or SupportNavigationView, bootstrap is called automatically. These methods are for programmatic usage without the built-in views.
// Bootstrap — required before calling other methods (views handle this automatically)
try await client.bootstrap()
// Pre-configure identity (applied automatically during bootstrap)
client.setIdentity(
externalId: "user_123",
email: "user@example.com",
name: "Jane Doe"
)
// Or identify after bootstrap
try await client.identify(
externalId: "user_123",
email: "user@example.com",
name: "Jane Doe",
metadata: VisitorMetadata(["plan": .string("pro")])
)
// Clear identity on logout (full reset: visitor ID, storage, REST state)
client.clearIdentity()
// Update visitor metadata (merge, not replace — requires identified visitor)
client.updateMetadata(VisitorMetadata([
"lastScreen": .string("settings"),
"appVersion": .string("2.1.0"),
]))
// Get notified when the visitor sends a message
client.onMessageSent = { text in
print("Visitor sent: \(text)")
}
// Get notified when the visitor rates a resolved conversation
client.onConversationRated = { rating in
print("Visitor rated: \(rating)/5")
}
// Activity tracking
try await client.sendActivity(sessionId: "session_abc")
// Disconnect when done
await client.disconnect()All stores are @Observable and @MainActor-isolated for direct use in SwiftUI views.
| Store | Key Properties | Key Methods |
|---|---|---|
client.conversations |
conversations, sorted, hasMore, hasUnread |
load(), loadMore(), create(...) |
client.timeline |
items, visibleItems, pendingMessages |
sendMessage(...), markSeen(...), submitRating(...) |
client.connection |
isConnected, typingIndicators, aiProcessing |
isAgentTyping(...), aiStatusMessage(...) |
client.agents |
— | agent(forUserId:), agent(forAIAgentId:), sender(for:) |
| Language | Code | |
|---|---|---|
| 🇬🇧 | English | en |
| 🇩🇪 | German | de |
| 🇪🇸 | Spanish | es |
| 🇫🇷 | French | fr |
| 🇮🇹 | Italian | it |