iOS SDK reference
Every public type, function, property, listener and error of GraineInApp 0.10.3 for iOS and its optional WebRTC package, with the Objective-C interface: signatures, parameters, what each returns, the errors it raises, and what to expect.
Every public declaration of the iOS SDK, GraineInApp 0.10.3, and of its optional WebRTC package for calls, GraineInAppRTC, read from their source and grouped by what you are doing: installing it, saying who the customer is, the chat, the voice bar, several agents, screen context and actions, calls in both directions, booking, the scheduled-calls list, design and going live. Every entry starts with what it is for, then its signature, its parameters, what it returns, the errors it can raise and an example; each type sits with the use case that returns or takes it, field by field, with what to expect. A value newer than the SDK — a status, a source, an error code — never crashes it: it arrives as unknown, carrying its word. The Objective-C interface is the appendix. The same API across every platform, topic by topic, is the SDK reference; installing and setting up is iOS.
Requirements, install, permissions and initialisation
| Package | GraineInApp 0.10.3 — a Swift package with no dependencies |
|---|---|
| Requires | iOS 15+ (Mac Catalyst 15+ also builds); Swift tools 5.9 (Xcode 15 or later). It links Apple's AVFoundation, CallKit, Combine, CoreGraphics, CoreText, PushKit, SwiftUI, UIKit and WebKit. |
| Calls need | A real iPhone (CallKit and VoIP pushes do not run in the simulator), an Apple Developer account and an APNs auth key for hosted ringing |
| WebRTC calls (optional) | GraineInAppRTC 0.10.3 — a separate package, always the same version as GraineInApp: Add WebRTC calls |
Install — Swift Package Manager (primary)
Use it to add the SDK with Xcode or a Package.swift — the primary way to install it.
Xcode → File → Add Package Dependencies… → https://github.com/GraineAI/inapp-ios, version 0.10.3 or later, and add the GraineInApp library to your app target. In a Package.swift:
Package.swift
Install — CocoaPods
Use it when your app already manages its dependencies with CocoaPods.
CocoaPods — Podfile
The pod builds exactly what the Swift package builds (the same sources, iOS 15, Swift 5.9) and carries the privacy manifest in its own resource bundle, GraineInApp_Privacy.
Add WebRTC calls (optional)
Add it so the native calls — the customer calling your agent, and your agent ringing the customer — carry their audio over WebRTC and hold up on a weak or changing network.
Package.swift
In Xcode: File → Add Package Dependencies… → https://github.com/GraineAI/inapp-ios-rtc, the same version as GraineInApp, and add the GraineInAppRTC library to the same target as GraineInApp. With CocoaPods, beside pod 'GraineInApp':
CocoaPods — Podfile
Nothing else changes: no code, no Info.plist entry, no capability and no permission beyond the calls' own (Permissions and capabilities). The calls find the module by themselves. A build that strips code nothing references can make sure of it at launch:
- What it is. A separate package, so
GraineInAppkeeps no dependencies and an app that does not add it never downloads it. It carries a prebuilt WebRTC library (Google's WebRTC, BSD 3-Clause, its symbols renamed so it never clashes with another copy in the app). Its licences are in the package'sTHIRD-PARTY-NOTICES.md: include them in your app's open-source notices. If another package in your app pins the same WebRTC build at a different version, the two cannot be resolved together: leave the module out (the calls stay on the WebSocket) or align the versions. - Size. About 12 MB more installed on an arm64 iPhone, and about 6 MB more to download.
- Privacy. It ships its own privacy manifest (with CocoaPods, in the
GraineInAppRTC_Privacybundle): the system boot time (reason35F9.1), to time the call's steps; no tracking and no tracking domains. - Which calls. Both directions of the native calls (
GraineCalls): a call the customer places withstartCall, and a call Graine rang — a booked call or "Ring now" — that the customer answers. The voice bar's in-app call (GraineVoice) is not affected;GraineConfig.transportchooses its audio. - Choosing.
GraineCallsConfig.callTransport:.auto(the default) uses WebRTC when the module is in the app and the dashboard allows it, else the WebSocket;.websocketalways the WebSocket;.webrtcWebRTC wherever it can carry the call. See GraineCallTransport. - The dashboard's switch. The agent's In-app call audio setting (agent → Calls & scheduling): WebRTC (recommended) or Standard. Standard moves every install's calls back to the WebSocket, whatever the app chose, from each app's next start — no app release. The app reads it as
GraineHostedCallsSettings.audioTransport. - What the customer notices. The same ring, the same call screen, the same "Connecting…". On a weak or changing network the agent's voice holds up instead of stalling and catching up (a jitter buffer, loss concealment and the platform's echo canceller). The call is prepared at the tap — or while a ring is showing — so it connects as fast as before. Over WebRTC the agent's voice arrives at telephone bandwidth today: steadier through a weak network, narrower than the WebSocket's on a perfect one.
- When WebRTC cannot carry a call. A network that blocks it, a server that turns it down, or no answer within the time allowed (about 12 seconds in all), before any audio flows: the call continues on the WebSocket inside the same call — no second ring, no new call screen, no error; "Connecting…" shows a moment longer — and
GraineCalls.transportFallbackReasonsays why. A call the agent cannot take at all (no credit left, the agent switched off) ends with that reason, exactly as without the module. Once audio flows a call never moves: a network drop is retried once, then the call ends as dropped.
Permissions and capabilities
Read it to add what the agent and calls need to the app target before you submit the app.
| Add (Xcode → the app target) | For | Why |
|---|---|---|
NSMicrophoneUsageDescription in Info.plist | Voice and calls | Required: iOS stops an app that asks for the microphone without it. Suggested text: Lets you talk to the assistant in the app and on calls. |
| Signing & Capabilities → Push Notifications | Calls that ring | VoIP pushes reach the app (the aps-environment entitlement) |
Background Modes → Audio, AirPlay, and Picture in Picture (audio) | Calls | The call's audio keeps going with the screen off |
Background Modes → Voice over IP (voip) | Calls that ring | iOS wakes a closed app for a VoIP push |
XML — Info.plist
Nothing else: no notification permission (VoIP pushes and CallKit need none), no Bluetooth usage description, no remote-notification background mode — and the WebRTC package adds nothing to this list. The SDK ships a privacy manifest declaring the required-reason APIs it calls — system boot time (reason 35F9.1, for timing) and UserDefaults (reason CA92.1) — and no tracking and no tracking domains; Xcode adds it to your app's privacy report. What your app collects through the agent, the customer's voice included, goes in your App Privacy details.
What the SDK configures in CallKit
Read it to know how a call looks and behaves on the system's call screen, and which of your settings change it.
| CallKit setting | Value |
|---|---|
| Call name (caller on the system screen, Recents) | For a ring Graine sends: the agent's Name on the incoming call (else the agent's name). For a call the customer places, and a ring from your own server without a name: GraineCallsConfig.displayName; with several agents, each agent's call name first; empty, your app's display name. |
| Icon template (the app button on the system call screen) | GraineCallsConfig.iconTemplateImageData (a 40 × 40 pt transparent PNG); otherwise the brand's launcher glyph from the design, drawn as a template image |
| Ringtone | GraineCallsConfig.ringtoneSound (a sound file in your bundle, 30 seconds or less); otherwise the Garage's Ringtone — Graine or Soft written as a sound file for CallKit, or the phone's own ringtone for Phone's own |
| Calls in the Phone app's Recents | GraineCallsConfig.includesCallsInRecents, off by default |
| Video, hold, grouping, keypad | Off: voice only, one call at a time (one call group, one call per group) |
| Ring timeout | A ring Graine sends rings for the agent's Ring for; any other for GraineCallsConfig.ringTimeout (30 seconds by default) |
| Audio session | Play-and-record in voice-chat mode, Bluetooth hands-free allowed, the earpiece by default; activated by CallKit, and the app's own session restored afterwards |
- Every VoIP push is reported to CallKit at once. iOS requires it and stops delivering VoIP pushes to an app that does not. The SDK reports every one — even one it cannot use, or one that arrives during a live call (that one is ended at once and reported
missed; the live call is untouched). Send VoIP pushes only for calls. An app with its ownPKPushRegistryhands the payload toreportIncomingCall(payload:completion:)instead. - On the lock screen the ring is the system's incoming-call screen (a banner while the phone is in use), with your call name, icon and ringtone. Answered there, the call connects; the system call screen stays on the lock screen for the call, and your app shows
GraineCallViewwhen it is opened. Do Not Disturb, a Focus or the block list can stop a ring; it is then reportedmissed. - The microphone: for a call the customer places, the SDK asks before placing it. For a ring, the customer answers first; the SDK reports
answered, iOS asks while the call connects, and a refusal — or no answer within 60 seconds — ends itfailed. Ask during onboarding so a call answered on the lock screen already has it. - Apple does not allow CallKit in apps on the App Store in China: leave calls off there.
Initialise
Use it as the starting point: configure the agent at launch, and mount the bar and the call screen once.
iOS · Swift — AcmeApp.swift
The bar mounts the voice engine in the key window the first time it appears (autoAttach), so a call carries on as the customer moves through your app. UIKit apps host the SwiftUI views in a UIHostingController, or use GraineChatViewController; without the bar, mount the engine with GraineInApp.shared.voice.attach().
Install and the first answer
Configure the agent once, at launch, and know when its settings from the dashboard have arrived. The agent starts loading its settings the moment configure runs; every request waits for them.
GraineInApp.configure
Call it once at launch to set up an agent; everything else goes through the instance it returns.
Configures an agent. Call it once, at launch (App.init or application(_:didFinishLaunchingWithOptions:)). Without a name it is the default agent — the one shared returns and every view draws unless handed another; an app with several agents configures each under its own name and publishable key. The name is trimmed, and an empty name is the default. Calling it again with a name that is already configured gives back that same agent, untouched. A new agent takes on who is signed in, the memory choice and the current screen, and starts loading its settings from Graine.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
config | GraineConfig | required | Everything the SDK needs up front — at least baseUrl and publishableKey. |
name | String | "default" | The name to configure this agent under, to find it again with named(_:); left out or empty, the default agent. |
Returns — GraineInApp: the agent's instance. Keep it, or reach it later as GraineInApp.shared (the default agent) or GraineInApp.named(_:).
Errors — none.
GraineConfig (constructor)
Use it to build the configuration you pass to configure.
Builds a configuration.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
baseUrl | String | required | "https://www.graine.ai" unless your workspace is self-hosted. |
publishableKey | String | required | The agent's publishable key, pk_live_…, from Agent → Embed & Widgets → Install. |
variables | [String: Any] | [:] | Prompt variables by name, for this call or conversation; masked on the device before they leave it. |
transport | GraineTransport? | nil | How the voice bar's in-app call travels: the standard path or high quality (WebRTC); left out, the dashboard decides. Native calls are not affected: they take GraineCallsConfig.callTransport. |
appVersion | String? | nil | Your app's version; it lands on the call record. |
endOnBackground | Bool | true | End a live conversation when the app goes to the background. |
timing | Timing | Timing() | Load timing for the engine page that carries the in-app call (see GraineConfig.Timing); most apps keep the defaults. |
debug | Bool | false | Log the SDK's traffic with Graine — keys only, never values. |
friction | GraineFrictionConfig? | nil | The app's own friction (stuck-customer) rules, applied over the dashboard's; nil uses the dashboard's alone. |
fonts | [String: String] | [:] | Fonts your app ships itself, by family: a family named here is used instead of downloading it — ["Inter": "InterVariable"] maps a family to the name your app registered it under. |
captureTaps | Bool | true | Tell the agent what the customer taps while a screen is set. |
chatSize | GraineChatSize? | nil | How big the chat window opens, over the agent's Window size. |
Returns — a new GraineConfig.
Errors — none.
GraineConfig.Timing (constructor)
Use it only for a very slow network, to give the engine page that carries the in-app call longer to load.
Builds the load timing for the engine page that carries the in-app call, in milliseconds — for a very slow network. Most apps keep the defaults.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
stallMs | Int | 15_000 | Silence — nothing downloaded — before a load is given up on and retried. |
bootMs | Int | 15_000 | Downloaded but still not ready after this long: the page is broken, not slow, and is reloaded. |
hardCapMs | Int | 120_000 | The ceiling on one load attempt, however steadily it is arriving. |
retryDelaysMs | [Int] | [1_000, 2_000, 4_000, 8_000, 15_000, 15_000] | The waits between reloads; its length is how many reloads are tried before GraineVoice.connectivity becomes failed. |
endWatchdogMs | Int | 4_000 | How long GraineVoice.end() waits for the page to confirm before ending the call locally. |
slowConnectMs | Int | 4_000 | A connect the customer has waited on this long sets GraineVoice.slowConnect ("Still connecting…"). |
Returns — a new GraineConfig.Timing.
Errors — none.
GraineInApp.session
Read it to know whether the agent's settings from the dashboard have arrived, and what they say.
The agent's settings, loaded once per launch, starting the moment configure runs; nil until they arrive. Every request to Graine made before then waits for them, so you never have to. GraineListener.onSessionReady tells you when they arrive — the moment to read bookingAvailable, schedulingRules, theme and checkActions(). If they cannot be loaded, onError says why and the next request tries again. Readiness of the voice engine is separate: GraineVoice.ready and GraineVoice.connectivity.
Parameters — none.
Returns — GraineSessionConfig? — its fields: GraineSessionConfig. Example value: nil before the settings load.
Errors — none.
GraineInApp
Use it as the SDK's entry point: one instance per configured agent, reached as GraineInApp.shared or GraineInApp.named(_:).
The SDK's entry point: one instance per configured agent, made by GraineInApp.configure(_:name:) at launch and reached as GraineInApp.shared (the default agent) or GraineInApp.named(_:). Its methods may be called from any thread; voice, calls and chat are main-thread objects, and listeners are called on the main thread.
What to expect — The SDK's entry point, configured once at launch; everything else goes through it, or through the agent you name. A request to Graine made before the agent's settings have loaded waits for them.
| Property | Type | Meaning |
|---|---|---|
static var all: [String: GraineInApp] { get } | [String: GraineInApp] | Every configured agent, keyed by name. |
static var current: GraineInApp? { get } | GraineInApp? | The default agent — or, when every agent was given a name, the first one configured. Nil before any agent is configured. |
static var shared: GraineInApp { get } | GraineInApp | The same agent as current. Stops the app with a message if no agent has been configured: call configure first. |
typealias ActionHandler = ([String: Any]) throws -> GraineActionResult | — | The handler type for registerAction(_:handler:): it receives the agent's arguments and returns the answer, and may throw. |
typealias DeferredActionHandler = ([String: Any], @escaping (GraineActionResult) -> Void) -> Void | — | The handler type for registerAction(_:deferred:): it receives the arguments and a done callback to answer with later. |
let config: GraineConfig | GraineConfig | The configuration this agent was made with. |
let name: String | String | The name this agent was configured under; "default" for the default agent. |
var session: GraineSessionConfig? { get } | GraineSessionConfig? | The agent's settings from the dashboard, once loaded (once per launch); nil before. onSessionReady reports it. |
var design: GraineDesign? { get } | GraineDesign? | The brand's design from the agent's settings: light and dark tokens and which to use. Nil before the agent's settings load, and from a server that sends none; the SDK's views then draw from the dashboard's appearance settings. |
var theme: GraineThemeTokens? { get } | GraineThemeTokens? | The design tokens to draw right now: the brand's fixed scheme, else the device's current light or dark, picked again whenever the device switches. Nil without a design. onTheme reports each change. |
private(set) lazy var voice: GraineVoice | GraineVoice | The in-app voice call that GraineAgentBar uses. Attach it to your window before starting a call. Main thread only. |
var calls: GraineCalls { get } | GraineCalls | Native calls on CallKit and VoIP pushes: the customer calling your agent, or your agent ringing the customer. One object for the whole app — every agent's calls is the same object, and startCall(reason:variables:agent:) picks the agent. Main thread only. |
private(set) lazy var chat: GraineChat | GraineChat | The typed conversation with this agent. Both GraineChatView and the bar already work through it on their own. Main thread. |
var memoryChoice: Bool { get } | Bool | The current customer's memory choice: true until they turn it off. When false, nothing is recalled or stored. |
var installId: String { get } | String | This install's id: a UUID made on first launch and kept in the Keychain (this device only), surviving reinstalls. The same for every agent. Graine uses it to count a customer's devices, and it is never shown to the agent. If the Keychain cannot keep it, that launch uses a new one. |
var schedulingRules: GraineSchedulingRules { get } | GraineSchedulingRules | The rules for booking calls, from the agent's settings — or, before they load, from the last ones kept on the device; the defaults (no booking) when there is neither. scheduleCall checks them first. |
var bookingAvailable: Bool { get } | Bool | Whether this agent can be booked from the app now — Graine rings this iPhone, the agent lets customers book, and the customer is identified. What the SDK's own "Book a call" follows, and what your own "Call me" button should; see GraineInApp.bookingAvailable. |
Its functions: configure, named, addListener, removeListener, identify, setMemory, forgetMemory, memories, setVariables, currentVariables, setScreen, updateField, removeField, clearScreen, track, tap, noteInteraction, configureFriction, answerComponent, reopenComponent, registerAction, checkActions, scheduleCall, scheduledCalls, cancelScheduledCall, presentBooking — each with its parameters, response, errors and an example.
GraineConfig
Read it to see every option the SDK takes at launch and what each one changes.
Everything the SDK needs up front, passed to GraineInApp.configure(_:name:) and kept as GraineInApp.config. A value type; only baseUrl and publishableKey are required.
What to expect — Made once, at launch, before anything else. Optional fields have the defaults shown; nothing in it is sent to Graine except what the descriptions say.
| Field | Type | Default | Meaning |
|---|---|---|---|
var baseUrl: String | String | required | Graine's address: https://www.graine.ai unless self-hosted. |
var publishableKey: String | String | required | From Agent → Embed & Widgets; selects the agent. It is not a secret and cannot place a call on its own: add app:// to that agent's Allowed domains, or every request is refused. |
var variables: [String: Any] | [String: Any] | [:] | Prompt variables known at build time. identify and setVariables win over these. |
var transport: GraineTransport? | GraineTransport? | nil | Forces how the in-app call's audio travels. nil lets the dashboard's Call audio setting decide. |
var appVersion: String? | String? | nil | Stored on the call record, so one build can be told from another. |
var endOnBackground: Bool | Bool | true | Ends a live in-app call when the app goes to the background (after two seconds, if it is still there). The chat also hangs up in the background and comes back with the app. |
var timing: Timing | GraineConfig.Timing | Timing() | Load timing for the engine page, for very slow networks. |
var debug: Bool | Bool | false | Logs every message between the SDK and its engine page, both ways, and the page's own console, under the subsystem ai.graine.inapp. Variables are logged as keys only and screen context as the screen's name only. The engine page can also be inspected in Safari (iOS 16.4 and later). Turn it on in debug builds. |
var friction: GraineFrictionConfig? | GraineFrictionConfig? | nil | The app's friction rules, applied over the dashboard's — for screens only the app knows about. |
var fonts: [String: String] | [String: String] | [:] | Fonts the app ships itself. The SDK downloads the design's typeface from Graine; a family named here, or one the app bundles under the family's own name, is used instead and never downloaded. If your app registered a family under a different name, map the family to that name: ["Inter": "InterVariable"]. |
var captureTaps: Bool | Bool | true | Tells the agent about taps: a tap on a control with an accessibility identifier — or a button's accessibility label — reaches it as tap:<name>, any other tap as tap:screen with where it landed. No more than two go out per second, only while a screen is set, and a field's value is never included. The recogniser only observes; every touch still reaches the app. |
var chatSize: GraineChatSize? | GraineChatSize? | nil | How big the chat window opens, over the dashboard's Window size: .compact above the bar, .large a sheet over most of a phone (a panel on an iPad), .full the whole screen. Nil follows the dashboard; a size passed to chat.open(size:) wins for that open. |
Its functions: init — each with its parameters, response, errors and an example.
GraineConfig.Timing
Read it to tune how patiently the engine page loads on a poor network.
Load timing for the engine page that carries the in-app call, in milliseconds, set as GraineConfig.timing. Most apps keep the defaults.
What to expect — Made once, at launch, before anything else. Optional fields have the defaults shown; nothing in it is sent to Graine except what the descriptions say.
| Field | Type | Default | Meaning |
|---|---|---|---|
var stallMs: Int | Int | 15_000 | How long a load may go with nothing downloaded before it is abandoned. |
var bootMs: Int | Int | 15_000 | Downloaded but still not ready after this long: the page is broken, not slow. |
var hardCapMs: Int | Int | 120_000 | Ceiling on one load attempt, however steadily it is arriving. |
var retryDelaysMs: [Int] | [Int] | [1_000, 2_000, 4_000, 8_000, 15_000, 15_000] | The waits between reloads; its length is the number of reloads before connectivity becomes failed. |
var endWatchdogMs: Int | Int | 4_000 | How long GraineVoice.end() waits for the page to confirm before ending the call locally. |
var slowConnectMs: Int | Int | 4_000 | A connect the customer has waited on this long sets GraineVoice.slowConnect ("Still connecting…"). |
Its functions: init — each with its parameters, response, errors and an example.
GraineSessionConfig (constructor)
Use it to build agent settings by hand for SwiftUI previews and tests, without the network.
Builds settings by hand — for previews and tests; the SDK makes the real ones.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
agentId | String | required | The agent's id, as the dashboard shows it. |
webcallAgentId | String | required | The id in-app calls are placed and filed under; usually the same as agentId. |
appearance | [String: Any] | [:] | The agent's appearance settings. |
variables | [String: Any] | [:] | Prompt variables by name, for this call or conversation; masked on the device before they leave it. |
pageVersion | String? | nil | The engine page's version, used to cache it. |
declaredActions | Set<String> | [] | The in-app actions declared on the agent, for testing checkActions(). |
friction | [String: Any]? | nil | The dashboard's friction rules, as JSON. |
design | GraineDesign? | nil | The agent's design — nil draws from appearance. |
wsHost | String? | nil | Where the agent's conversations are hosted; nil uses Graine's own. |
identity | GraineIdentitySettings? | nil | The agent's Identity setting: which trait names the customer, and how many devices they may use. nil means user_id and two devices. |
memoryEnabled | Bool? | nil | Whether the agent remembers customers between conversations; nil when the settings do not say. |
calls | GraineHostedCallsSettings? | nil | Whether Graine rings this app, the booking rules and the calls' audio setting; nil means no ringing, no booking and no say on the calls' audio. |
Returns — a new GraineSessionConfig.
Errors — none.
GraineSessionConfig
Read it to see what the dashboard says about the agent, once its settings have loaded.
The agent's settings from the dashboard, fetched once per launch: GraineInApp.session and GraineListener.onSessionReady(_:). Missing fields are empty, never a crash.
What to expect — The agent's settings from the dashboard, loaded once per launch; null / nil until then. A setting an older server does not send means its default: the user_id trait, 2 devices per user, booking off. Missing fields are empty, never a crash.
| Field | Type | Default | Meaning |
|---|---|---|---|
let agentId: String | String | required | The agent's id. |
let webcallAgentId: String | String | required | The id in-app calls are placed and filed under; the same as agentId when the settings name none. |
let appearance: [String: Any] | [String: Any] | [:] | The settings from Embed & Widgets → Appearance, unchanged from how they are stored. |
let variables: [String: Any] | [String: Any] | [:] | The agent's default prompt variables. |
let pageVersion: String? | String? | nil | The engine page's version, used to cache it. |
let wsHost: String? | String? | nil | Where the conversation server is, when the settings name it; nil uses Graine's default. |
let declaredActions: Set<String> | Set<String> | [] | The in-app actions declared on this agent in the dashboard. |
let friction: [String: Any]? | [String: Any]? | nil | The dashboard's friction rules for this agent, applied under GraineConfig.friction. |
let design: GraineDesign? | GraineDesign? | nil | The brand's design, resolved into light and dark tokens. Nil from an older server: every surface then draws from appearance. |
let identity: GraineIdentitySettings? | GraineIdentitySettings? | nil | The agent's Identity setting; nil from an older server, which means user_id. |
let memoryEnabled: Bool? | Bool? | nil | Whether the agent remembers customers between conversations; nil from an older server. |
let calls: GraineHostedCallsSettings? | GraineHostedCallsSettings? | nil | Whether Graine rings this app, the booking settings and the In-app call audio setting; nil from an older server. |
var schedulingRules: GraineSchedulingRules { get } | GraineSchedulingRules | — | The rules for booking calls from calls; no booking, and the defaults, when there are none. |
var preferredTransport: GraineTransport { get } | GraineTransport | — | The dashboard's Call audio setting. |
var accent: String? { get } | String? | — | The dashboard's accent colour, as stored (with or without its "#"). |
Its functions: init — each with its parameters, response, errors and an example.
Identity, memory and conversations that continue
Say who the customer is, let them choose whether they are remembered, show and forget what the agent remembers, and continue — or restart — the conversation they had.
GraineInApp.identify
Call it whenever the signed-in customer changes, so memory, bookings and the calls Graine rings follow the right person.
Who is signed in, for every configured agent. The trait the agent's Identity setting names (user_id unless the dashboard says email, phone or a custom trait) identifies the customer for memory and for the calls Graine rings: its value travels only with the requests that start a conversation, never as a prompt variable. Every other trait becomes a prompt variable, masked on the device; nested objects and arrays are dropped. A placeholder value ("guest", "null", a masked "[EMAIL]") is no identity, and those conversations are anonymous. memory: false means this customer's conversations are neither remembered nor recalled; nil keeps their last choice (true until they make one). The identity is fixed per conversation: a change applies to the next one. identify(nil) signs out: the device stops being that customer's, Graine is told, and later conversations are anonymous. When a different customer signs in, or the customer signs out, the chat transcript on screen is cleared.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
traits | [String: Any?]? | required | Who the customer is. The trait your agent's Who is the user? field names (user_id unless you chose email, phone or a custom field) identifies them; every other trait becomes a prompt variable, masked on the device. nil / null signs out. |
memory | Bool? | nil | false: this customer's conversations are neither remembered nor recalled (kept on the device for them). Left out: their last choice stands — on until they make one. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.setMemory
Call it when the customer turns being remembered on or off in your app's settings.
Whether the current customer's conversations are remembered, for every agent — the same as calling identify again with memory: enabled. Kept on the device for that customer; applies to the next conversation.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | Bool | required | true to remember this customer's conversations; false to stop. Kept on the device for them. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.memoryChoice
Read it to show the current state of a "Remember my conversations" switch.
The current customer's memory choice: true until they turn it off with setMemory(false) or identify(_:memory: false). Kept on the device for each customer, so the next customer who signs in on the same phone has their own. When false, nothing is recalled or stored for them.
Parameters — none.
Returns — Bool. Example value: true.
Errors — none.
GraineInApp.installId
Read it to tell your support team which install a customer is using.
This install's id: a UUID made on first launch and kept in the Keychain (this device only), so it survives reinstalls. The same for every agent. Graine counts a customer's devices by it (Devices per user); the agent never sees it. If the Keychain cannot keep it, that launch uses a new one.
Parameters — none.
Returns — String. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none.
GraineInApp.memories
Call it to show the customer what the agent remembers about them.
What the agent remembers about the signed-in customer, newest first, at most 100, already redacted — to show people what is kept about them. Waits for the agent's settings first, and throws identity_required before sending anything when nobody is identified ("Nobody is identified: call identify() with the customer's identifier first.").
Parameters — none.
Returns — [GraineMemory] — its fields: GraineMemory. Example value: [GraineMemory(id: "m_41", text: "Prefers a call after 6 pm", createdAt: 2026-10-02 09:14:00 +0000, updatedAt: 2026-10-12 17:40:00 +0000)].
Errors — GraineServiceError: identity_required, device_limit, rate_limited, network, unavailable, invalid_key, origin_refused
GraineInApp.forgetMemory
Call it when the customer asks the agent to forget them.
Deletes everything this agent's memory holds about the signed-in customer. Waits for the agent's settings first. Throws identity_required before sending anything when nobody is identified, and device_limit from a device that is not one of the customer's. Returns how many memories were deleted (0 when Graine does not say). The kept conversation goes too, and the chat on screen clears — only when it succeeds; a refusal leaves both as they were.
Parameters — none.
Returns — Int. Example value: 3.
Errors — GraineServiceError: identity_required, device_limit, rate_limited, network, unavailable, invalid_key, origin_refused
GraineChat.newConversation
Call it when the customer wants a fresh start instead of continuing the earlier conversation.
Starts a fresh thread: the chat hangs up, Graine lets go of the conversation it kept for this agent and customer, the chat on screen clears and — when the customer was in the chat — a fresh chat opens with the welcome and the starters. Memories are untouched. With nobody identified only the screen clears. On a refusal the chat is as it was, connected again. While GraineChatView shows an earlier conversation, its header offers this.
Parameters — none.
Returns — nothing; it returns when done.
Errors — GraineServiceError: device_limit, rate_limited, network, unavailable, invalid_key, origin_refused — the chat is then as it was, connected again.
GraineMemory (constructor)
Use it to build sample memories for previews and tests of a "what we remember" screen.
Builds a memory by hand — for previews and tests of a screen that lists what is remembered; memories() makes the real ones.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The memory's id, as Graine keeps it. |
text | String | required | What is remembered, in a sentence — already redacted. |
createdAt | Date? | nil | When it was first remembered. |
updatedAt | Date? | nil | When it last changed. |
Returns — a new GraineMemory.
Errors — none.
GraineMemory
Read it to show one thing the agent remembers about the customer.
One thing the agent remembers about the signed-in customer, already redacted — returned by GraineInApp.memories(), to show people what is kept about them beside forgetMemory(). A value type.
What to expect — From memories(): newest first, at most 100, already redacted. Only for the identified customer, from one of their devices. The dates are null when Graine did not say; they are moments in UTC — show them in the device's time zone.
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | required | The memory's id, as Graine keeps it — to tell memories apart in a list. |
let text: String | String | required | What is remembered, in a sentence — show it to the customer as it is. |
let createdAt: Date? | Date? | nil | When it was first remembered; nil when Graine did not say. |
let updatedAt: Date? | Date? | nil | When it last changed; nil when Graine did not say. |
Its functions: init — each with its parameters, response, errors and an example.
GraineIdentitySettings (constructor)
Use it to build identity settings for previews and tests.
Builds a setting; a field that is not a valid trait name becomes user_id, and devicesPerUser is held to 1–5.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
field | String | "user_id" | The trait passed to identify that names the customer: user_id, email, phone or a custom trait such as member_id. |
devicesPerUser | Int | 2 | How many devices one customer's memory may be used from, 1 to 5. |
Returns — a new GraineIdentitySettings.
Errors — none.
GraineIdentitySettings
Read it to learn which trait identifies the customer, and how many devices they may use.
The agent's Identity setting: which trait passed to identify names the customer, and how many devices they may use. In GraineSessionConfig.identity.
What to expect — The agent's settings from the dashboard, loaded once per launch; null / nil until then. A setting an older server does not send means its default: the user_id trait, 2 devices per user, booking off. Missing fields are empty, never a crash.
| Field | Type | Default | Meaning |
|---|---|---|---|
let field: String | String | "user_id" | user_id, email, phone, or a custom trait such as member_id. |
let devicesPerUser: Int | Int | 2 | How many devices one customer's memory may be used from, 1 to 5. |
static let standard = GraineIdentitySettings() | GraineIdentitySettings | — | What applies when the agent's settings say nothing: user_id, two devices. |
Its functions: init — each with its parameters, response, errors and an example.
Chat
The typed conversation: open it, send, follow its state, and draw it — with the SDK's own chat screen or your own.
GraineChat.open
Call it when the customer opens your chat screen, so the conversation connects.
Puts the customer in the chat, connecting and keeping the connection up. A mounted GraineAgentBar moves to the chat (where the agent offers one) and draws it at size until close(); nil follows GraineConfig.chatSize, then the dashboard's. Ends any in-app call and closes other agents' chats. A dropped connection is made again 1, 2, 4, 8, 15 and 15 seconds apart, then the status is offline until the customer sends something or you call retry().
| Parameter | Type | Default | Meaning |
|---|---|---|---|
size | GraineChatSize? | nil | How big the chat opens this time: compact, large or full. Left out: the app's chatSize, then the agent's Window size. |
Returns — nothing; it returns when done.
Errors — none.
GraineChat.close
Call it when the customer leaves the chat, so it hangs up.
The customer has gone: the chat disconnects and remains disconnected. What was said stays on screen.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineChat.send
Call it to send what the customer typed.
Sends a message. It is shown at once and sent now, or as soon as the chat is connected (opening and connecting it if needed). Empty text is ignored. Up to 20 messages wait while the chat is down; older ones are marked failed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | String | required | What the customer typed. Blank text is ignored. |
Returns — nothing; it returns when done.
Errors — none.
GraineChat.retry
Call it from a "Try again" control when messages could not be sent.
Connects now, and sends what could not be sent.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineChat.clear
Call it to empty the chat on screen — a new topic, a tidy screen — while the conversation goes on.
Empties the chat on screen: its turns and the typing bubble go. Only the screen. The conversation goes on — connected stays connected — a message still waiting for the connection is still sent, and nothing Graine keeps is touched: the conversation that continues comes back with the next history, and the agent's memories stay. newConversation() starts over; forgetMemory() forgets. Safe from any thread (it hops to the main thread).
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineChatView (constructor)
Use it to show the whole chat, drawn in your agent's design, in a SwiftUI app.
The chat as a SwiftUI screen, drawn in the agent's design. It connects when it appears and hangs up when it goes; it draws nothing before an agent is configured.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
accent | Color? | nil | Overrides the design's accent colour. |
onClose | (() -> Void)? | nil | The header's close control: called when the customer taps it — dismiss the view here. Without it no close control is drawn, and the navigation you put the view in closes it. |
Returns — a new GraineChatView.
Errors — none.
GraineChatViewController (constructor)
Use it to show the chat in a UIKit app.
The chat screen for UIKit, drawn in the agent's design. Presented, it is a page sheet with a close control; pushed, the navigation bar's back button closes it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
Returns — a new GraineChatViewController.
Errors — none.
GraineChatRows.build
Call it to lay out a chat of your own exactly as the SDK's chat window draws it.
The rows a chat window draws for turns, in order: a bubble for each chat message (.turn); one row for a run of an earlier call's voice turns on one day (.voice — that a call happened and when, never what was said); and a day line (.day — Today, Yesterday or a date) before the first row when it is not from today, and wherever the device's day changes between rows. A conversation all from today is exactly its bubbles. GraineChatView and the bar's chat window draw these same rows. Rebuild whenever turns changes; each row's id stays the same as the conversation grows.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | [GraineChatTurn] | required | The conversation, oldest first — GraineChat.turns. |
now | Date | Date() | The moment that counts as today — set it only in previews and tests. |
calendar | Calendar | .current | How days are counted; its time zone decides where one day ends. |
Returns — [GraineChatRow] — its cases: GraineChatRow. Example value: [.day(…yesterday…), .voice(…), .day(…today…), .turn(…), .turn(…)] for an earlier call yesterday and two messages today.
Errors — none.
GraineChatRows.dayLabel
Call it for the words of a day line, in the device's language.
"Today", "Yesterday", else the date in the device's words ("Tue 14 Oct"), with the year when it is not this year's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
line | GraineChatDayLine | required | A day line from GraineChatRows.build. |
now | Date | Date() | The moment that counts as now, for the year — set it only in previews and tests. |
calendar | Calendar | .current | The calendar and time zone the date is written in. |
locale | Locale | .current | The language the date is written in. |
Returns — String. Example value: "Tue 14 Oct".
Errors — none.
GraineChatRows.callLabel
Call it for the words of an earlier call's row.
"Voice call · 6:05 PM": the time of the call's first turn, in the device's short time.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
line | GraineChatCallLine | required | A call's row from GraineChatRows.build. |
calendar | Calendar | .current | The time zone the time is written in. |
locale | Locale | .current | The language the time is written in. |
Returns — String. Example value: "Voice call · 6:05 PM".
Errors — none.
GraineChatRows.callSpoken
Call it for what VoiceOver should say for an earlier call's row.
"Voice call at 6:05 PM" — the row's accessibility label.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
line | GraineChatCallLine | required | A call's row from GraineChatRows.build. |
calendar | Calendar | .current | The time zone the time is said in. |
locale | Locale | .current | The language the time is said in. |
Returns — String. Example value: "Voice call at 6:05 PM".
Errors — none.
GraineChatRows.clock
Call it to write a message's time the way the chat window does.
A time in the device's short words — "6:05 PM", or "18:05" where the device keeps a 24-hour clock. For a message's time where the design shows timestamps (GraineChatTokens.showTimestamps).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
date | Date | required | The moment to write, such as GraineChatTurn.at. |
calendar | Calendar | .current | The time zone the time is written in. |
locale | Locale | .current | The language the time is written in. |
Returns — String. Example value: "6:05 PM".
Errors — none.
GraineChatRows.offersStarters
Call it to know whether the brand's starters belong above your composer.
True while the conversation is new: the customer has not said anything, and no earlier conversation is on screen (no restored turn). The SDK's chat window shows the brand's starters (GraineChatTokens.suggestions) only then.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | [GraineChatTurn] | required | The conversation on screen — GraineChat.turns. |
Returns — Bool. Example value: true in a new chat.
Errors — none.
GraineChatRows.offersNewConversation
Call it to know whether your own "New conversation" control belongs on screen.
True while an earlier conversation is on screen — a restored turn, when the customer came back and the conversation continues. GraineChatView's header offers New conversation only then (and only where the design keeps it, GraineChatTokens.newConversation).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | [GraineChatTurn] | required | The conversation on screen — GraineChat.turns. |
Returns — Bool. Example value: false in a new chat.
Errors — none.
GraineChat
Use it to drive and draw the typed conversation: GraineInApp.shared.chat.
The typed conversation with an agent, on its own connection: GraineInApp.shared.chat, an ObservableObject to read on the main thread (its methods may be called from any thread and hop there). One conversation runs at a time across every agent: opening the chat ends any in-app call, starting a call closes it, a native call holds it down until it is over, and with endOnBackground it hangs up in the background and comes back with the app.
What to expect — The agent's typed conversation. It connects while the customer is in the chat (open) and hangs up when they leave (close); a message typed while it is not connected is shown at once and sent when it is. Its state changes as the conversation moves: draw from it. Opening the chat ends a call the bar holds.
| Property | Type | Meaning |
|---|---|---|
@Published private(set) var turns: [GraineChatTurn] = [] | [GraineChatTurn] | What has been said, oldest first: the customer's messages and the agent's replies, one bubble each (the last 200). Kept while the app runs. |
@Published private(set) var status: GraineChatStatus = .idle | GraineChatStatus | Where the connection is — draw a connecting or offline state from it. |
@Published private(set) var typing = false | Bool | The agent is working on a reply: from the customer's message until its first words, or 30 seconds. |
@Published private(set) var upcomingCalls: [GraineScheduledCall] = [] | [GraineScheduledCall] | This customer's calls still to come, soonest first, at most three — the chat window's "Upcoming calls" card. Fetched when the chat opens and after the agent finishes a reply (at most every 10 seconds), only where Graine rings this app. |
var online: Bool { get } | Bool | Connected: a message goes now. |
var awaitingFirstMessage: Bool { get } | Bool | True when the customer has not said anything in this conversation and it does not continue an earlier one (no turn was restored); that is when the brand's starters are offered. |
private(set) var isOpen = false | Bool | The customer is in the chat: open() was called and close() was not. |
@Published private(set) var openedSize: GraineChatSize? | GraineChatSize? | The size the current open asked for (open(size:)), until close(); nil follows GraineConfig.chatSize, then the dashboard's. Moving to the call and back keeps it. |
Its functions: open, close, send, retry, clear, newConversation, refreshUpcomingCalls, cancelUpcomingCall — each with its parameters, response, errors and an example. To draw the conversation in a chat of your own, GraineChatRows lays turns out the way the SDK's chat window does.
GraineChatSize
Use it to choose how big the chat window opens.
public enum GraineChatSize: String, CaseIterable, Sendable — how big the chat window opens. The dashboard's Window size chooses; GraineConfig.chatSize overrides it for the app, and chat.open(size:) for one open. Raw values are the design's words.
What to expect — Which size wins: the size passed to the open call, then the app's chatSize, then the agent's Window size in the Garage, else compact. An open's size lasts until the chat closes. On a phone, large is a sheet over about 92% of the screen and full the whole screen inside the safe areas; on a tablet or computer both open a panel 420 wide and up to 760 tall. A word from a newer design that the SDK does not know is passed over.
| Case | Meaning |
|---|---|
case compact | The usual window above the bar, unchanged from earlier releases. |
case large | On a phone, a sheet covering most of the screen; on an iPad, a panel in the corner. |
case full | The whole screen. |
GraineChatTurn
Read it to draw one message of the chat.
One message in GraineChat.turns, made by the SDK. Each agent reply is its own turn.
What to expect — One per message, oldest first. The agent's turn updates in place (same id) while live, then stops. A customer's message typed while the chat is not connected is sending (React Native: pending) and goes as soon as it is; failed means every reconnect was spent — the next message or a retry sends it. Restored turns arrive once when the chat opens, for an identified customer on an agent with memory on, on one of their devices: at most 50, oldest first, delivered, never live, with their original times; the welcome is not sent then. Consecutive voice turns show as one "Voice call · time" row (GraineChatRows lays a conversation out that way for a chat of your own). The list empties on sign-out, when another customer signs in, after a successful forgetMemory(), on newConversation() and on clear() (the screen only: the conversation goes on).
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | — | Stays the same for as long as the transcript lasts: t1, t2, … |
let role: GraineRole | GraineRole | — | Who said it: .agent or .customer — which side to draw the bubble on. |
internal(set) var text: String | String | — | What was said; an agent's words are appended as they stream in. |
internal(set) var live: Bool | Bool | — | The agent's words are still arriving: draw them as provisional. |
internal(set) var delivery: Delivery | GraineChatTurn.Delivery | — | Where a customer's message is. An agent's turn is always sent. |
let at: Date | Date | — | When it was said — for an agent's turn, its first words. |
let proactive: Bool | Bool | — | The agent spoke unprompted — this turn did not come after one of the customer's. |
let restored: Bool | Bool | false | Carried over from an earlier conversation and brought back when the chat opened (the customer returned): it counts as delivered, carries the time it was said and Graine's id, and was not said just now. |
let channel: GraineChatChannel | GraineChatChannel | .chat | Where it was said. In a chat window, consecutive .voice turns appear as a single "Voice call" row, and their words are never shown. |
enum Delivery: String, Sendable | nested type | — | See GraineChatTurn.Delivery. |
GraineChatTurn.Delivery
Read it to show whether a customer's message is sending, sent or failed.
Where a customer's chat message is. Raw values are the wire words.
What to expect — Only on the customer's messages (the agent's are sent, or null): sending → sent; failed once every reconnect was spent.
| Case | Meaning |
|---|---|
case sending | Typed while the chat was disconnected; it waits for the connection and goes out once there is one. |
case sent | On its way to the agent. |
case failed | The chat could not reconnect to send it. GraineChat.retry() tries again. |
GraineChatChannel
Read it to tell a typed message from one said on a call.
public enum GraineChatChannel: String, Sendable — where a message was said: typed in the chat, or spoken on a call (only an earlier conversation's turn is).
What to expect — chat for a typed message; voice for a message said on a call — only a restored turn can be voice.
| Case | Meaning |
|---|---|
case chat | Typed in the chat. |
case voice | Said on a call with the agent. |
GraineChatStatus
Read it to show whether the chat is connected, and offer a retry when it is not.
Where the chat's connection is: GraineChat.status.
What to expect — Idle until the customer is in the chat; connecting, then online. A dropped connection is retried six times — 1, 2, 4, 8, 15 and 15 seconds apart — showing reconnecting with the attempt; then offline until the customer sends something or the app retries. Opening the chat ends a call the bar holds; a native call holds the chat until it is over.
| Field | Type | Default | Meaning |
|---|---|---|---|
var isConnecting: Bool { get } | Bool | — | Connecting or reconnecting: draw "Connecting…". |
| Case | Meaning |
|---|---|
case idle | The customer is not in the chat — it was never opened, they left, or the app is in the background. |
case connecting | Opening. |
case online | Connected. |
case reconnecting(attempt: Int) | The connection was lost and is being re-established; this is try number attempt out of six. |
case offline | Reconnecting failed, or the other side ended the conversation. A message, or retry(), connects again. |
GraineRole
Read it to tell the agent's lines from the customer's.
Who said a line, in captions and chat turns. Raw values are the wire words.
What to expect — Who said it: the agent, or the customer (user).
| Case | Meaning |
|---|---|
case agent | The agent. |
case customer | The customer. |
GraineChatView
Use it as the SwiftUI chat screen.
The chat as a SwiftUI screen, drawn by the SDK in the brand's design: header, one bubble per reply, a typing bubble, the components the agent draws (answered in place) and the composer. It connects when it appears, hangs up when it goes, and draws nothing before an agent is configured.
What to expect — Push it or present it like any SwiftUI view; it fills the space it is given. The header offers New conversation while an earlier conversation is on screen, and the Upcoming calls card (with Book a call where bookingAvailable is true) when the design keeps it.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineChatViewController
Use it as the UIKit chat screen.
The chat screen for a UIKit app, to present or push: present(GraineChatViewController(), animated: true). Presented, its close control dismisses it; pushed, the navigation bar's back button does and no close control is drawn.
What to expect — The same screen as GraineChatView, hosted for UIKit; it presents as a page sheet unless you change modalPresentationStyle.
Its functions: init, viewDidLoad — each with its parameters, response, errors and an example.
GraineChatRows
Use it to draw a chat of your own in the same rows as the SDK's chat window.
public enum GraineChatRows — static functions only: the rows a chat window draws for a conversation, and the words for them. What GraineChatView and the bar's chat window draw, public for an app that draws its own chat; every Graine SDK lays a conversation out in these same rows.
What to expect — Worked out on the device from the turns you pass; nothing is sent. "Today", "Yesterday" and "Voice call" are the SDK's words; dates and times are written in the device's language and time zone.
Its functions: build, dayLabel, callLabel, callSpoken, clock, offersStarters, offersNewConversation — each with its parameters, response, errors and an example.
GraineChatRow
Read it to draw one row of a chat window.
public enum GraineChatRow: Equatable, Identifiable — one row, as GraineChatRows.build(_:now:calendar:) returns them, oldest first.
What to expect — A day line before the first row when it is not from today, and wherever the day changes; one row per earlier call on a day, however many turns it had; one row per chat message. A conversation all from today has no day line.
| Field | Type | Default | Meaning |
|---|---|---|---|
var id: String { get } | String | — | Stable as the conversation grows — a day line by its date (day-2026-10-14), a call by its first turn (voice-t3), a message by its turn's id — for List and ForEach. |
| Case | Meaning |
|---|---|
case day(GraineChatDayLine) | A day line: draw GraineChatRows.dayLabel(_:). |
case voice(GraineChatCallLine) | An earlier call, as one row: draw GraineChatRows.callLabel(_:) — never what was said on it. |
case turn(GraineChatTurn) | A message: one bubble. |
GraineChatDayLine
Read it to draw a day line.
public struct GraineChatDayLine: Equatable, Sendable — the day a run of rows belongs to. Made by GraineChatRows.build; an app reads it, never makes one.
What to expect — Before the first row when it is not from today, and wherever the day changes between rows; a conversation all from today has none.
| Field | Type | Default | Meaning |
|---|---|---|---|
let day: GraineChatDay | GraineChatDay | — | Which day: .today, .yesterday or .date. |
let date: String | String | — | The device's date, YYYY-MM-DD. |
let at: Date | Date | — | When the day's first message was said. |
var key: String { get } | String | — | "today", "yesterday", or the date — the same words on every platform. |
GraineChatDay
Read it to know which day a day line names.
public enum GraineChatDay: Equatable, Sendable — in GraineChatDayLine.day.
What to expect — Counted on the device's calendar and time zone when the rows were built: rebuilt after midnight, what was .today is .yesterday.
| Case | Meaning |
|---|---|
case today | The device's today. |
case yesterday | The day before today. |
case date | An earlier day: GraineChatDayLine.date says which. |
GraineChatCallLine
Read it to draw an earlier call as one row.
public struct GraineChatCallLine: Equatable, Sendable — an earlier call: when it began, and which turns it stands for — never their words. Made by GraineChatRows.build.
What to expect — Voice turns come only from an earlier conversation the chat restored; a run of them on one day is one call.
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | — | The row's id: voice- and the call's first turn's id. |
let at: Date | Date | — | When the call's first turn was said. |
let first: Int | Int | — | Where the call's first turn is in turns. |
let last: Int | Int | — | Where its last turn is in turns. |
The voice bar
The agent talking inside your app: the bar that draws the call, the launcher, the voice engine behind them, the loader, the captions, how the audio travels and how good the connection is.
GraineAgentBar (constructor)
Use it to put the agent in your app: mount the bar once, at your root.
The assistant bar, drawn in the agent's design: it fills the area it is given, draws only the bar, lets other touches through and rises above the keyboard. Nothing draws before an agent is configured.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
name | String? | nil | The name shown beside the status, and what VoiceOver calls the agent; left out, the dashboard's heading. |
accent | Color? | nil | Overrides the design's and the dashboard's accent colour. |
bottomInset | CGFloat | 16 | Points above the bottom of the area it fills — lift it clear of your tab bar. |
draggable | Bool | true | Whether the customer can drag it up and down; a tap still does what a tap did. |
initialOffset | CGFloat | 0 | A position you saved from onPlacementChange, in points above the anchor. |
onPlacementChange | ((CGFloat) -> Void)? | nil | Called with the new offset when the customer lets go somewhere new — save it here; the SDK stores nothing. |
autoAttach | Bool | true | Mount the voice engine in the key window when the bar appears, if nothing has yet. |
surface | GraineSurface? | nil | Which conversation opens, .voice or .chat, among those the dashboard offers; it can never add one the dashboard withheld. nil is the dashboard's choice. |
onClose | (() -> Void)? | nil | The customer hung up, or closed the bar with no call to end — called 400 ms later, after the goodbye has left the speaker. If your app shows the bar only on demand, remove it here. |
Returns — a new GraineAgentBar (GraineAgentBar<EmptyView>; accessory makes one with content above it).
Errors — none.
GraineAgentBar.accessory
Use it to ride a view of your own, such as a text field, above the bar.
Something that rides above the bar and moves with it, above the keyboard included — a text field, say.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
content | () -> Content | required | A view builder for what rides above the bar; it is drawn in your own style and keeps its own state. |
Returns — GraineAgentBar<Content>: the same bar, with the content above it. Mount that instead of the plain bar.
Errors — none.
GraineLauncher (constructor)
Use it for a floating button that starts the in-app call, when you do not mount the bar.
The floating launcher in the agent's design: a tap places the in-app call, and during the call it is the end button. Nothing draws before an agent is configured.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
label | String? | nil | A pill launcher's words, and what VoiceOver says; left out, the dashboard's heading. |
accent | Color? | nil | Overrides the design's and the dashboard's accent colour. |
autoAttach | Bool | true | Mount the voice engine in the key window when the button appears, so a tap is only a call away. |
action | (() -> Void)? | nil | What a tap does instead of placing the in-app call — open your own screen, say. With it, the button only draws and is never the end button. |
Returns — a new GraineLauncher.
Errors — none.
GraineVoice.attach
Call it to mount the voice engine in a window you choose.
Mounts the engine in that view — pass your key window, not a view a screen owns — and loads the page idle: nothing registered, nothing dialled, no microphone, so a later start() connects quickly. Attaching to a different view moves the engine there and ends a call that is under way.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
to view | UIView | required | Where the engine lives: your key window, not a view a screen owns — a call ends when the view it is in goes. |
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.attach
Call it to mount the voice engine in the key window when you do not use the bar.
Mounts the engine in the app's key window. Call it once a window exists — from a view's onAppear, say; with no window yet it reports through onError and does nothing.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.detach
Call it to remove the voice engine, hanging up any call first.
Removes the engine, hanging up first: a call never outlives its UI.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.start
Call it from your own control to start the in-app voice call.
Places the call. Asks for the microphone first if the app does not hold it; refused, no call is placed and onMicDenied is called. A start before the page is ready is held and placed when it is. Ends any other conversation — another agent's call, every chat. Refused through onError when the engine is not attached or a native call (calls) is live.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.end
Call it from your own control to hang up the in-app call.
Hangs up. If the page does not confirm within Timing.endWatchdogMs (4 s), the call is ended locally — state, onEnded, and a reload that drops the audio. If a start was only on hold, it is just cancelled.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.setMuted
Call it from your own mute control during the in-app call.
Stops (or resumes) sending the customer's voice without ending the call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Bool | required | true to stop sending the customer's voice; false to send it again. |
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.retry
Call it from a "Try again" control after the engine page failed to load.
Reloads the engine page and starts its reloads over (GraineConfig.Timing.retryDelaysMs) — for a "Try again" button once connectivity is failed.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.setCaptionsOn
Call it from your own captions (CC) button to show or hide the line being said.
The captions switch, captionsOn. While it is off, GraineAgentBar does not show the line being said; on — the default — it does. The call goes on either way, and what is said is still kept in captions (clearCaptions() forgets it). The switch is this agent's and lasts while the app runs: it is on again at the next launch, so set it at launch to keep the customer's choice. A UI of your own decides from captionsOn. Safe from any thread (it hops to the main thread).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Bool | required | true to show the line being said; false to hide it. |
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.clearCaptions
Call it to forget what has been said on the in-app call without ending it.
Empties captions — what has been said this call — and the call goes on; the next line said starts the list again. A call starting clears them too. The captions switch is untouched. Safe from any thread (it hops to the main thread).
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.viewOpened
Call it when a voice UI of your own comes on screen, so a slow connect is reported while the customer is watching.
A UI of your own that shows the voice came on screen — GraineAgentBar says so by itself. While one is open, an engine page still loading is the customer's wait: after GraineConfig.Timing.slowConnectMs (4 seconds) slowConnect turns true, for your "Still connecting…". Call it in pairs with viewClosed() — from onAppear and onDisappear, or viewDidAppear(_:) and viewDidDisappear(_:). Safe from any thread (it hops to the main thread).
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineVoice.viewClosed
Call it when the voice UI you reported with viewOpened() leaves the screen.
The UI from viewOpened() left the screen. Once no UI that shows the voice is open — yours or the bar — and no call is being placed, a page still loading is no longer anyone's wait, and slowConnect goes back to false. Safe from any thread (it hops to the main thread).
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineLoader (constructor)
Use it to show waiting the way the agent's design does.
The dashboard's loader, with its words for the moment you name. Pair moment: "thinking" with GraineVoice.phase == .thinking.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
moment | String? | nil | Which waiting moment's words to show — "connecting", "reconnecting", "thinking" or "component"; left out, no words, only the mark. |
label | String? | nil | Words to show instead of the moment's words from the dashboard. |
size | CGFloat? | nil | The mark's size in points, over the dashboard's (14, 20 or 28 for small, medium and large). |
stacked | Bool | false | Draw the words under the mark instead of beside it. |
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
Returns — a new GraineLoader.
Errors — none.
GraineLoaderText (constructor)
Use it to build loader words for previews and tests.
Builds a set of words for the waiting moments — for previews and tests; the SDK reads the real ones from the dashboard (GraineLoaderLook.text).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
connecting | String? | nil | Shown while the page and the call are being set up. |
reconnecting | String? | nil | Shown while the page is reloaded after a failure, or the call is re-established. |
thinking | String? | nil | Shown between the customer finishing and the agent starting to answer. |
component | String? | nil | Shown while a component's content is loading. |
Returns — a new GraineLoaderText.
Errors — none.
GraineLoaderText.forMoment
Call it to get the words for one waiting moment.
The words for "connecting", "reconnecting", "thinking" or "component"; nil for anything else.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
moment | String | required | The waiting moment: "connecting", "reconnecting", "thinking" or "component". |
Returns — String?: that moment's words, or nil for any other word or when none were set. Example value: "Thinking…".
Errors — none.
GraineCaption (constructor)
Use it to build caption lines for previews and tests of your own call UI.
Builds a caption line by hand — for previews and tests; the SDK makes the real ones.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | GraineRole | required | Who said it: the agent or the customer. |
text | String | required | The words of the line. |
live | Bool | required | The words are still arriving. |
Returns — a new GraineCaption.
Errors — none.
GraineTransport.from
Call it to turn a transport's wire word into a GraineTransport.
.webrtc for "rtc"; anything else is .websocket, the transport that always works.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | String? | required | A transport's wire word: "rtc" or "ws"; anything else, or nil, is the WebSocket. |
Returns — GraineTransport. Example value: .websocket.
Errors — none.
GraineConnectionQuality.classify
Call it to grade one connection measurement the way the SDK does.
The word for one measurement (loss as a fraction, 0.08 = 8%); the worst value wins, and unknown when all three are missing.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
rttMs | Double? | required | The round-trip time, in ms. |
jitterMs | Double? | required | The jitter, in ms. |
loss | Double? | required | The packet loss, from 0 to 1. |
Returns — GraineConnectionQuality. Example value: .excellent.
Errors — none.
GraineConnectionQuality.classify
Call it to grade a measurement given as a map.
The same, from a sample map {rtt, jitter, loss}; a missing, negative or non-numeric value is absent, not zero. Used by the SDK; apps rarely need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sample | [String: Any]? | required | One measurement as a map: rtt and jitter in milliseconds, loss as a fraction. |
Returns — GraineConnectionQuality. Example value: .excellent.
Errors — none.
GraineAgentBar
Use it as the assistant bar: the agent's voice, chat and components in one view.
What the customer sees during a conversation, drawn by the SDK in the brand's design: avatar, name and a status line that names the network's state ("Weak connection", "No connection", "Reconnecting…"), microphone, hang-up, the line being said (while GraineVoice.captionsOn), the components the agent draws, and the chat — with a switch when the dashboard offers both. A SwiftUI view: mount it once at your root as an overlay, not inside a screen; it draws only the bar, lets other touches through, can be dragged up and down, and rises above the keyboard.
What to expect — Place it in your UI; it draws in the agent's design and follows the conversation by itself.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init, accessory — each with its parameters, response, errors and an example.
GraineSurface
Use it to choose whether the bar opens the call or the chat.
Which conversation: a call or a chat, never both at once. Passed to GraineAgentBar(surface:).
What to expect — Only a choice among what the dashboard offers (What visitors get): asking for a conversation the agent does not offer changes nothing, and nil follows the dashboard's choice of which opens first.
| Case | Meaning |
|---|---|
case voice | The call. |
case chat | The chat. |
GraineLauncher
Use it as a ready-made launcher button.
The floating launcher button in the brand's design (56 points high; a pill is as wide as its label): a tap places the in-app call, mounting the engine first if needed, and during the call it is the end button. A SwiftUI view that draws only the button — place it yourself, for example .overlay(alignment: .bottomTrailing) { GraineLauncher().padding(20) }.
What to expect — Draws nothing until an agent is configured, then follows the agent's design (shape, glyph or logo, colours) as soon as the settings load; during a call it turns into the end button.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineVoice
Use it to drive and draw the in-app voice call yourself: GraineInApp.shared.voice.
The in-app voice call: a page Graine hosts, in a web view the SDK owns (one invisible point big), so audio fixes reach your app without a release — reached as GraineInApp.shared.voice and driven for you by GraineAgentBar. An ObservableObject; every method belongs on the main thread, and one called from elsewhere hops there.
What to expect — The assistant bar's voice call. The bar starts and ends it by itself; these are for an app that draws its own controls. One conversation at a time: starting it closes an open chat.
| Property | Type | Meaning |
|---|---|---|
@Published private(set) var connected = false | Bool | A call is up — draw your hang-up control from it. |
@Published private(set) var connecting = false | Bool | A call is being placed or re-established. |
@Published private(set) var muted = false | Bool | The customer's voice is not being sent. |
@Published private(set) var ready = false | Bool | The engine page is loaded and ready: a start now connects within moments. |
@Published private(set) var quality: GraineConnectionQuality = .unknown | GraineConnectionQuality | The connection in one word — show a weak-connection hint from it. |
@Published private(set) var online: Bool? = nil | Bool? | The engine page's view of the network; nil until it has said. |
@Published private(set) var reconnecting = false | Bool | The call was cut off by the network and is being brought back. Not an end. |
@Published private(set) var connectivity: GraineConnectivity = .loading | GraineConnectivity | Whether the engine page is there: loading, ready, retrying or failed. |
@Published private(set) var captions: [GraineCaption] = [] | [GraineCaption] | What has been said this call, oldest first. A live line is replaced as its words arrive, not appended; at most 12 are kept; cleared when a call starts, and by clearCaptions(). |
@Published private(set) var captionsOn = true | Bool | The captions switch (CC): GraineAgentBar shows the line being said only while it is true. On by default; setCaptionsOn(_:) changes it. What is said is kept in captions either way. |
@Published private(set) var connectStage: GraineConnectStage? = nil | GraineConnectStage? | Where a call being placed is: registering, dialling, retrying or switching. Nil outside a connect. |
@Published private(set) var slowConnect = false | Bool | The customer has waited on this connect for Timing.slowConnectMs (4 s by default) — a bar open with the page not there yet, or a call they asked for not yet answered. Say so ("Still connecting…"). It goes back to false as soon as the connect goes through or they stop waiting. |
@Published private(set) var callFailed = false | Bool | The last start could not be connected on either transport. Not a setting to fix — the network may be better in a moment, so start() simply tries again. Any start, answered call, end() or detach() resets it. |
var isAttached: Bool { get } | Bool | Whether the engine is mounted. |
var phase: GrainePhase { get } | GrainePhase | What the call is doing in one word — idle, connecting, reconnecting, thinking, speaking or listening — for an app that draws its own bar. Draw GraineLoader(moment: "thinking") for thinking. |
Its functions: attach, detach, start, end, setMuted, setCaptionsOn, clearCaptions, viewOpened, viewClosed, answerWidget, reportEvent, retry — each with its parameters, response, errors and an example.
GrainePhase
Read it to draw what the in-app call is doing, in one word.
What the in-app call is doing, in one word, for an app that draws its own bar: GraineVoice.phase. Raw values are the wire words.
What to expect — Worked out from the call as it moves, and published with GraineVoice's other state: idle outside a call; thinking is the moment to show a loader.
| Case | Meaning |
|---|---|
case idle | No call; ready to start. |
case connecting | The page loading, or a call being placed. |
case reconnecting | Either the page failed and is loading again, or the call is being brought back. |
case thinking | The customer finished a sentence and the agent has not answered yet — draw GraineLoader(moment: "thinking"). |
case speaking | The agent is talking. |
case listening | The agent is waiting for the customer. |
GraineConnectStage
Read it to tell the customer where a call being placed is.
Where a call being placed is: GraineVoice.connectStage. Raw values are the wire words.
What to expect — How the connection is doing, reported as it changes. Draw it so the customer can tell when the network is the problem.
| Case | Meaning |
|---|---|
case registering | A line is being set up (a cold start). |
case dialling | The call is on its way to the agent. |
case retrying | The first attempt went unanswered and a fresh connection is being made — on mobile data, usually one the phone dropped while idle. Say "Reconnecting…". |
case switching | The call is moving to the other transport. |
GraineConnectProgress
Read it only if you handle the engine page's progress reports yourself.
One progress report from the engine page: a stage, or failed. The SDK turns it into GraineVoice.connectStage and callFailed.
What to expect — How the connection is doing, reported as it changes. Draw it so the customer can tell when the network is the problem.
| Case | Meaning |
|---|---|
case stage(GraineConnectStage) | The connect reached this stage. |
case failed | Nothing could carry the call on either transport; start() tries again. |
GraineConnectivity
Read it to know whether the engine page is there, and offer a retry when it failed.
Whether the engine page that carries the in-app call is there: GraineVoice.connectivity and onConnectivity. Raw values are the wire words.
What to expect — How the connection is doing, reported as it changes. Draw it so the customer can tell when the network is the problem.
| Case | Meaning |
|---|---|
case loading | First load in progress. |
case ready | The page is ready. |
case retrying | A load failed or never became ready; a reload is scheduled. |
case failed | Every reload used up — say so, and offer GraineVoice.retry(). |
GraineLoader
Use it as the agent's loader.
How waiting looks, drawn from the dashboard's Appearance → loader: dots, pulse, ring, bars or the brand's logo breathing, at the configured size, speed and colour, with the dashboard's words for the moment. A SwiftUI view; nothing animates while it is off screen.
What to expect — Draws nothing until an agent is configured; until the agent's settings load it draws the default dots in the default accent, then the dashboard's loader.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineLoaderText
Read it to get the dashboard's words for each waiting moment.
The words for each waiting moment, as carried in GraineLoaderLook.text. A value type.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
var connecting: String? | String? | nil | While the page and the call are being set up. |
var reconnecting: String? | String? | nil | While the page is being reloaded after a failure, or the call re-established. |
var thinking: String? | String? | nil | Between the customer finishing and the agent starting. |
var component: String? | String? | nil | While a component's content is loading. |
Its functions: init, forMoment — each with its parameters, response, errors and an example.
GraineLoaderLook
Read it only to draw a loader of your own that matches the dashboard's.
The loader in effect, as values a renderer can use. Used by the SDK's own views; apps do not need it.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let kind: String | String | "dots" | One of "dots", "pulse", "ring", "bars" or "mark"; the last is the brand's logo, breathing. |
let color: String? | String? | nil | #RRGGBB, or nil for the accent. |
let px: Double | Double | 20 | The mark's size in points: 14, 20 or 28 for small, medium and large. |
let periodMs: Double | Double | 1_100 | One animation cycle: 1,600, 1,100 or 700 ms for calm, normal and quick. |
let text: GraineLoaderText | GraineLoaderText | "Connecting…", "Reconnecting…", "Thinking…", "Loading…" | Every moment's words filled in; the dashboard's own words replace these (at most 40 characters each). |
let skeleton: Bool | Bool | false | When true, the outline of the component that is about to appear is drawn under the loader as well. |
GraineCaption
Read it to draw a line being said.
One caption line: in GraineVoice.captions, GraineCalls.caption and GraineCalls.transcript. A value type.
What to expect — The line being said right now, by either side; live while it is still being recognised or spoken. Replaced as the conversation moves on: draw the latest. The captions switch (GraineVoice.captionsOn) hides the bar's line, never the list: captions keeps what is said either way.
| Field | Type | Default | Meaning |
|---|---|---|---|
let role: GraineRole | GraineRole | — | Who said it: the agent or the customer. |
let text: String | String | — | The words of the line so far. |
let live: Bool | Bool | — | Still being recognised or spoken — draw it as provisional. |
Its functions: init — each with its parameters, response, errors and an example.
GraineTransport
Read it to know how the in-app call's audio travels, or set GraineConfig.transport to force one.
How the in-app call's audio travels; raw values are the wire words. Set GraineConfig.transport only to force one, and hear what was used through onTransport. It applies to the voice bar's in-app call (GraineVoice) only: native calls (GraineCalls) carry their audio in your app's own process and choose their road with GraineCallsConfig.callTransport (GraineCallTransport).
What to expect — The SDK sends and reads these for you; they are public so a custom UI can reuse them.
| Field | Type | Default | Meaning |
|---|---|---|---|
var wire: String { get } | String | — | The wire word: ws or rtc. |
| Case | Meaning |
|---|---|
case websocket = "ws" | Audio on the conversation's WebSocket. It works on any network, though the audio suffers wherever the network loses data. |
case webrtc = "rtc" | WebRTC, with a jitter buffer and the device's echo canceller. Falls back to .websocket. |
Its functions: from — each with its parameters, response, errors and an example.
GraineConnectionQuality
Read it to show the customer when the network is the problem.
The connection in one word, with the same thresholds on every platform: GraineVoice.quality and onQuality. Raw values are the wire words.
What to expect — How the connection is doing, reported as it changes. Draw it so the customer can tell when the network is the problem.
| Case | Meaning |
|---|---|
case excellent | Under 150 ms round trip, 30 ms jitter and 2% loss. |
case good | At or past one of those, but under 400 ms, 80 ms and 8%. |
case poor | 400 ms round trip, 80 ms jitter or 8% loss, or worse — any one is enough. The bar says "Weak connection". |
case lost | The network is gone. |
case unknown | Nothing measured yet, or outside a call. |
Its functions: classify — each with its parameters, response, errors and an example.
Several agents in one app
More than one agent in the same app — a support agent in Help, a sales agent on Plans — each with its own publishable key, design, chat, voice bar, actions and requests. Who the customer is, the screen and the phone's calls belong to the app and are shared. Configure each with GraineInApp.configure(_:name:), and hand one to a view with its graine: parameter. One conversation runs at a time across all of them.
GraineInApp.named
Call it to reach an agent you configured under a name.
The agent configured under name (trimmed; an empty name is the default agent), or nil when no agent has that name.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The name the agent was configured under with configure(_:name:). |
Returns — GraineInApp?: the agent, or nil. Example value: nil before configure(_:name: "support").
Errors — none.
GraineInApp.all
Read it to go through every agent your app configured — to sign them all in or out, say.
Every configured agent, keyed by the name it was configured under; the default agent is under "default". Empty before any agent is configured.
Parameters — none.
Returns — [String: GraineInApp]. Example value: ["default": <GraineInApp>, "support": <GraineInApp>].
Errors — none.
GraineInApp.current
Read it when your code may run before any agent is configured — it never stops the app.
The default agent — or, when every agent was given a name, the first one configured. Nil before any agent is configured. The SDK's views draw this agent unless you hand them another.
Parameters — none.
Returns — GraineInApp?. Example value: nil before configure.
Errors — none.
GraineInApp.shared
Use it to reach the default agent from anywhere once it is configured.
The same agent as current. If no agent has been configured, it stops the app with a message saying to call configure first — so read it only after configure has run (current is the safe form).
Parameters — none.
Returns — GraineInApp.
Errors — none thrown; it stops the app when nothing is configured.
GraineInApp.name
Read it to tell which agent an instance is — in a listener shared by several agents, say.
The name this agent was configured under (trimmed); "default" for the default agent. The same name GraineCalls.agent, GraineMissedCall.agent and the bridges' agent fields carry.
Parameters — none.
Returns — String. Example value: "support".
Errors — none.
Screen context, actions and components
Tell the agent what the customer sees and does, let it act in your app, notice when the customer is stuck, keep identifiers on the device, and show the components the agent draws.
GraineInApp.setScreen
Call it on every meaningful screen change, so the agent knows what the customer sees.
What the customer is looking at; call it on every meaningful change. Shared by every agent. An unchanged screen is not sent again, and a burst of changes goes as one update every 400 ms — the first at once. nil means no screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
next | GraineScreen? | required | What the customer can see now; nil clears it. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.updateField
Call it as the customer edits one field, instead of describing the whole screen again.
Reports one changed field, merged by name into the current screen (added when new), so a form need not describe itself again on every keystroke. Does nothing without a current screen, or for a field with an empty name.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
field | GraineField | required | The field: its name, label, value (masked on the device) and status. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.removeField
Call it when a field leaves the screen.
Removes a field from the current screen. Nothing happens if it was never reported.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The field's id (GraineField.name). |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.clearScreen
Call it in a screen's teardown, so the agent stops describing a screen the customer left.
Forgets the screen only if it is still the one with that id — safe in a screen's teardown, which can run after the next screen has reported itself. nil clears whatever screen is set.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screenId | String? | required | The id (GraineScreen.screen) of the screen being left; nil clears whatever screen is set. |
Returns — nothing; it returns when done.
Errors — none.
GraineScreen (constructor)
Use it to describe what the customer is looking at.
Builds a screen description to hand to setScreen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screen | String | required | A stable id for the screen, such as "checkout_address"; keep it constant — it is also used for analytics. |
title | String? | nil | What the agent calls the screen, such as "Delivery address". |
journey | Journey? | nil | Where the screen sits in a flow, and where the flow goes next. |
fields | [GraineField] | [] | The inputs on the screen, with their status — what lets the agent name the field that needs attention. |
error | String? | nil | A screen-level error banner, distinct from a field's own message. |
data | [String: Any] | [:] | Anything else the agent should know, as JSON values; masked on the device. |
availableActions | [String]? | nil | Which registered actions work on this screen; nil allows every one, an empty list none. |
Returns — a new GraineScreen.
Errors — none.
GraineScreen
Read it to see every part of a screen description.
What the customer is looking at, passed to GraineInApp.setScreen(_:). Report real values: masking runs on the device before anything leaves it, and a screen that pre-redacts only blinds the agent.
What to expect — What you tell the agent the customer can see. Values are masked on the device before they leave it; an unchanged screen is not sent again; the first change goes at once and later ones at most every 400 ms.
| Field | Type | Default | Meaning |
|---|---|---|---|
var screen: String | String | required | A stable id for the screen; keep it constant — it is also used for analytics. |
var title: String? | String? | nil | What the agent calls the screen. |
var journey: Journey? | GraineScreen.Journey? | nil | Where the screen sits in a flow, and where the flow goes next. |
var fields: [GraineField] | [GraineField] | [] | The inputs on the screen. Their status is what lets the agent name what needs attention after a pause. |
var error: String? | String? | nil | A screen-level error banner, distinct from a field's own message. |
var data: [String: Any] | [String: Any] | [:] | Anything else the agent should know, as JSON values. |
var availableActions: [String]? | [String]? | nil | Which registered actions work here. With nil, every action this build registered is allowed; with an empty list, none is. It can only narrow what the agent declares. |
static func == (a: GraineScreen, b: GraineScreen) -> Bool | Bool | — | Equality, comparing data by its JSON content; an unchanged screen is not sent again. |
struct Journey: Equatable | nested type | — | See GraineScreen.Journey. |
Its functions: init — each with its parameters, response, errors and an example.
GraineScreen.Journey (constructor)
Use it to say where a screen sits in a flow.
Builds where a screen sits in a flow.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The flow's name, such as "Checkout". |
step | Int? | nil | This screen's step in the flow, counting from 1. |
of | Int? | nil | How many steps the flow has. |
next | String? | nil | The next step's name, so the agent can say what comes after this one. |
Returns — a new GraineScreen.Journey.
Errors — none.
GraineScreen.Journey
Read it to see how a flow is described.
Where a screen sits in a flow, and — next — where the flow goes after it. A value type.
What to expect — What you tell the agent the customer can see. Values are masked on the device before they leave it; an unchanged screen is not sent again; the first change goes at once and later ones at most every 400 ms.
| Field | Type | Default | Meaning |
|---|---|---|---|
var name: String | String | required | The flow's name, such as "Checkout". |
var step: Int? | Int? | nil | This screen's step in the flow. |
var of: Int? | Int? | nil | How many steps the flow has. |
var next: String? | String? | nil | The next step's name. |
Its functions: init — each with its parameters, response, errors and an example.
GraineField (constructor)
Use it to describe one input on a screen.
Builds one input's description, for GraineScreen.fields or updateField.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The field's id; updateField merges by it. |
label | String? | nil | What the field is called on screen. |
value | String? | nil | The real value — masked on the device on the way out; never pre-redact it. |
status | Status? | nil | empty, filled, invalid, pending or locked: what lets the agent name the field that needs attention. |
error | String? | nil | The validation message the customer really sees; the agent can offer to fix what it describes. |
isOptional | Bool | false | The field may stay empty, so the agent does not treat it as still to do. |
Returns — a new GraineField.
Errors — none.
GraineField
Read it to see every part of a field description.
One input on a screen, as the agent should understand it, inside GraineScreen.fields or reported alone with GraineInApp.updateField(_:). A value type.
What to expect — One input on the screen. Its status — empty, filled, invalid, pending or locked — is what lets the agent name the field that is wrong.
| Field | Type | Default | Meaning |
|---|---|---|---|
var name: String | String | required | The field's id; fields merge by it. |
var label: String? | String? | nil | What the field is called on screen. |
var value: String? | String? | nil | The real value — masked on the way out; never pre-redact it. |
var status: Status? | GraineField.Status? | nil | The field's state. |
var error: String? | String? | nil | The validation message the customer really sees; the agent can offer to fix what it describes. |
var isOptional: Bool | Bool | false | The field may stay empty, so the agent does not treat it as still to do. Sent as optional. |
enum Status: String | nested type | — | See GraineField.Status. |
Its functions: init — each with its parameters, response, errors and an example.
GraineField.Status
Use it to say what state a field is in, so the agent can name the field that needs attention.
A field's state; raw values are the wire words. It is what lets the agent name the field that needs attention instead of asking a vague question.
What to expect — One input on the screen. Its status — empty, filled, invalid, pending or locked — is what lets the agent name the field that is wrong.
| Case | Meaning |
|---|---|
case empty | Nothing entered. |
case filled | Has a value. |
case invalid | Rejected — pair it with error. |
case pending | In flight. |
case locked | Cannot be changed here; not the customer's to fix. |
GraineInApp.setVariables
Call it to hand the agent facts from your app's data while it runs.
Sets prompt variables from the app's own data while the app is running. Merged, not replaced: nil under a key removes it, nil for the whole map clears them. Values become strings (nested objects and arrays are dropped) and are masked. They reach the next call; an engine page already loaded is updated too.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
values | [String: Any?]? | required | Prompt variables by name, merged into those already set: nil under a key removes it, nil for the whole map clears them all. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.currentVariables
Call it to check exactly which variables the agent will receive.
Every variable the agent will receive, merged in precedence order: the dashboard's defaults, then GraineConfig.variables, then setVariables, then the traits from identify (without the identifying trait).
Parameters — none.
Returns — [String: String]: every variable by name, values as strings, already masked. Example value: ["plan": "gold", "name": "Asha"].
Errors — none.
GraineInApp.track
Call it to tell the agent about a product event without interrupting.
A product event that informs the agent without interrupting. It goes with the next screen update; the last 10 events are kept. Every agent hears it. An empty name is ignored.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The product event's name, for example "plan_viewed". |
data | [String: Any] | [:] | Details of the event, by name; masked on the device. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.tap
Call it to name a tap on a control the automatic capture cannot name.
Records a tap on a named control as tap:<name> (the name trimmed, inner spaces collapsed, cut to 60 characters; an empty name is ignored). Taps are context, not news: they go with the next screen update, at the end of its 400 ms window. Every agent hears it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The control's name, as the agent sees it: tap:<name>. |
data | [String: Any] | [:] | Details of the tap, by name; masked on the device. |
Returns — nothing; it returns when done.
Errors — none.
View.graineTap
Use it on a SwiftUI control so its taps reach the agent by name.
A tap on the view reaches the agent's context as tap:<name> ("you tapped Pay twice"), through GraineInApp.tap(_:data:), without taking the tap from the control. graine defaults to the default agent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The control's name, as the agent sees it: tap:<name>. |
graine | GraineInApp? | nil | The agent to tell — another agent's (GraineInApp.named("support")) or the default when left out; every agent hears taps either way. |
Returns — some View: your view, its taps reported by name.
Errors — none.
View (SwiftUI modifiers)
Use them to name a control for the agent, and to present the booking sheet, from SwiftUI.
Two SwiftUI modifiers the SDK adds to every View (public extension View).
What to expect — Each returns your view with the behaviour attached; neither changes how your view looks.
| Member | What it does |
|---|---|
func graineTap(_ name: String, graine: GraineInApp? = nil) -> some View | Names a control for the agent: a tap on it reaches the agent as tap: and the name, since SwiftUI views carry no accessibility identifier for the automatic tap capture to read. See View.graineTap. |
func graineBookingSheet(isPresented: Binding<Bool>, graine: GraineInApp? = nil, reason: String? = nil, onBooked: ((GraineScheduledCall) -> Void)? = nil) -> some View | Presents the SDK's booking sheet while the binding is true. See View.graineBookingSheet. |
GraineVoice.reportEvent
Call it during a call when something happened that the agent may want to speak up about.
Something the agent may want to speak up about. Only during a call — otherwise it is dropped, so it never opens the next conversation. The current screen is sent first. data may carry quiet, cooldown_seconds and max_per_session, which pace the agent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | What happened, in a word — the agent may speak up about it. |
detail | String? | nil | A sentence about it, or nothing. |
data | [String: Any] | [:] | Details, by name; quiet, cooldown_seconds and max_per_session pace how often the agent speaks up about it. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.noteInteraction
Call it when the customer does something that is not a field edit, so they are not taken for stuck.
The customer did something that is not a field edit — scrolled a list, opened a picker, sent a message — so the idle clock that detects a stuck customer restarts. Field edits do not count.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.registerAction
Call it to let the agent do something in your app, such as changing a setting.
Something the agent can do in your app; declare it on the agent in the dashboard too — a handler with no declaration is never offered, and a declaration with no handler is refused. The handler runs on the main thread and must answer within six seconds — five and a half in the chat and on a native call — or the agent is told it was not done (error: "The app did not answer within 6 seconds.", or "<name>" did not finish in time. in the chat and on a native call); a handler that throws answers error with the error's description. An action the agent asks for with no handler is answered refused: This app build does not implement "<name>". Registering the same name again replaces the handler. The returned function unregisters it, if it is still this registration.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name, exactly as it is declared on the agent in the dashboard (Embed & Widgets → Actions). |
handler | ActionHandler | required | The function that runs it, on the main thread: it receives the agent's arguments and returns .ok, .refused or .error — or throws. |
Returns — () -> Void: call it to unregister the action (it does nothing once the name was registered again).
Errors — none.
GraineInApp.registerAction
Call it for an action whose work finishes later, such as an upload.
As above, but for work that completes later, such as an upload or a network call. done must be called once and only once, on any thread; any call after the first is ignored. After six seconds the agent is told the app did not answer, so slower work should answer at once and report its outcome later with setScreen or track.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name, exactly as it is declared on the agent in the dashboard (Embed & Widgets → Actions). |
deferred handler | DeferredActionHandler | required | The function that runs it, on the main thread: it receives the agent's arguments and a done callback — call done once, from any thread, with .ok, .refused or .error. |
Returns — () -> Void: call it to unregister the action (it does nothing once the name was registered again).
Errors — none.
GraineInApp.checkActions
Call it once at startup to log which actions will and will not work.
Which registered actions the agent can call, and which it cannot, compared with the actions declared on the agent. Log it once at startup.
Parameters — none.
Returns — GraineActionCheck — its fields: GraineActionCheck. Example value: GraineActionCheck(usable: ["set_language"], undeclared: [], unhandled: ["open_order"]).
Errors — none.
GraineActionCheck
Read it to find actions that will not work before a customer does.
Which registered actions the agent can call and which it cannot, returned by GraineInApp.checkActions(). Log it once at startup.
What to expect — Worked out on the device from what you registered and what the agent's settings declare; nothing is sent. Before the settings have loaded, every registered action is undeclared. The SDK also logs a warning for every unhandled action when the settings load.
| Field | Type | Default | Meaning |
|---|---|---|---|
let usable: [String] | [String] | — | Actions both registered here and declared on the agent — these work. |
let undeclared: [String] | [String] | — | Actions registered here that the agent does not declare — the agent never offers them. |
let unhandled: [String] | [String] | — | Actions the agent declares that are not registered here — they are refused mid-sentence. Sorted. |
GraineActionResult (constructor)
Use it to answer an action with a status you choose at run time.
Builds an action's answer from a status; ok, refused and error are shorter.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
status | Status | required | .ok, .refused or .error. |
message | String | "" | A sentence for the agent, not the customer — it decides how to phrase it. |
data | [String: Any] | [:] | A structured result the agent can reason about, as JSON values. |
Returns — a new GraineActionResult.
Errors — none.
GraineActionResult.ok
Return it from an action handler when the action happened.
It happened; the agent will tell the customer it is done.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | "" | A sentence for the agent about what happened. |
data | [String: Any] | [:] | A structured result the agent can reason about, as JSON values. |
Returns — GraineActionResult.
Errors — none.
GraineActionResult.refused
Return it from an action handler when the action will not happen, and say why.
Says the action will not happen and gives the reason ("this is locked after submission"); this is often the most helpful reply.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | Why it will not happen, for the agent to explain. |
Returns — GraineActionResult.
Errors — none.
GraineActionResult.error
Return it from an action handler when the action was tried and failed.
It was tried and failed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | What went wrong, for the agent to explain. |
Returns — GraineActionResult.
Errors — none.
GraineActionResult
Read it to see what an action's answer carries.
What an action handler answers. The agent speaks about exactly this, so say what actually happened.
What to expect — What your app answers an agent's action with: ok (with an optional sentence and data), refused (it will not happen, and why) or error (it was tried and failed).
| Field | Type | Default | Meaning |
|---|---|---|---|
let status: Status | GraineActionResult.Status | required | ok, refused or error. |
let message: String | String | "" | For the agent, not the customer — it decides how to phrase it. |
let data: [String: Any] | [String: Any] | [:] | The result in structured form, for the agent to reason with. |
enum Status: String | nested type | — | See GraineActionResult.Status. |
Its functions: init, ok, refused, error — each with its parameters, response, errors and an example.
GraineActionResult.Status
Use it to say how an action turned out.
The outcome of an action. Raw values are the wire words.
What to expect — Your handler picks one; the SDK answers refused for an action with no handler and error for a handler that threw or ran out of time.
| Case | Meaning |
|---|---|
case ok | Done. |
case refused | Will not be done — also the SDK's answer for an action this build has no handler for. |
case error | Tried and failed — also the SDK's answer for a handler that threw or took longer than six seconds. |
GraineInApp.configureFriction
Call it to change the stuck-customer rules for screens only your app knows.
Replaces the app's layer of friction rules, applied over the dashboard's; nil removes it. Safe at any time.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
override | GraineFrictionConfig? | required | The app's friction rules, applied over the dashboard's; nil removes the app's layer. |
Returns — nothing; it returns when done.
Errors — none.
GraineFrictionConfig (constructor)
Use it to build friction rules from JSON in the dashboard's shape.
Rules from JSON in that shape.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
json | [String: Any] | required | The rules in the dashboard's shape: enabled, idle (afterMs, or false), cooldownMs, message, nudge (cooldownSeconds, maxPerSession, openLauncher), requireOutstandingField and screens. |
Returns — a new GraineFrictionConfig.
Errors — none.
GraineFrictionConfig (constructor)
Use it to build friction rules from values.
Rules from values; nil leaves the layer below in force. idleAfterMs: on one screen this long fires stalled_on_step (45 s by default; 0 or less is off). cooldownMs: the least gap between two signals on the same screen (60 s). cooldownSeconds and maxPerSession: the pacing of unprompted interventions (20 s and 3). openLauncher: for a host that draws its own launcher, open it for this. message: what the agent is told instead of the built-in sentence, with {title}, {screen}, {seconds}, {field} and {error}. requireOutstandingField: fire only while a field is outstanding — invalid, or empty and not optional — timed from when it became so (true by default). screens: per-screen overrides by screen id.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | Bool? | nil | Friction signals on (true) or off (false) for the whole app; nil leaves the dashboard's choice. |
idleAfterMs | Int? | nil | Milliseconds on one screen, with a field outstanding, before the customer counts as stalled (45,000 by default); 0 or less switches it off. |
cooldownMs | Int? | nil | The least gap between two signals on the same screen, in milliseconds (60,000 by default). |
cooldownSeconds | Int? | nil | How long the agent waits between unprompted interventions, in seconds (20 by default). |
maxPerSession | Int? | nil | The most unprompted interventions in one conversation (3 by default). |
openLauncher | Bool? | nil | For an app that draws its own launcher: open it when a signal fires. |
message | String? | nil | What the agent is told instead of the built-in sentence, with {title}, {screen}, {seconds}, {field} and {error} filled in. |
requireOutstandingField | Bool? | nil | Fire only while a field is outstanding — invalid, or empty and not optional (true by default). |
screens | [String: Screen] | [:] | Rules for single screens, keyed by screen id (GraineScreen.screen). |
Returns — a new GraineFrictionConfig.
Errors — none.
GraineFrictionConfig
Read it to see the friction rules your app can set.
One layer of friction (stuck-customer) rules — the app's own, applied over the dashboard's — set as GraineConfig.friction or with GraineInApp.configureFriction(_:). Most apps set nothing; use it for screens only the app knows about.
What to expect — Friction is the SDK noticing a customer who is stuck — stalled on a step, a field error repeated, looping between screens, rage taps, an incomplete submit, an error on the screen. The dashboard's rules apply unless the app overrides them; a signal lets the agent offer help.
| Field | Type | Default | Meaning |
|---|---|---|---|
let json: [String: Any] | [String: Any] | — | The rules as JSON, in the same shape as the dashboard's: enabled, idle ({afterMs} or false), cooldownMs, message, nudge (cooldownSeconds, maxPerSession, openLauncher), requireOutstandingField and screens. Keys it does not recognise are left out, and an invalid number is replaced by its fallback. |
struct Screen | nested type | — | See GraineFrictionConfig.Screen. |
Its functions: init — each with its parameters, response, errors and an example.
GraineFrictionConfig.Screen (constructor)
Use it to set the friction rules for one screen.
Builds the friction rules for one screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
ignore | Bool? | nil | True: no friction signals on this screen at all. |
idleAfterMs | Int? | nil | Milliseconds on this screen before it counts as stalled; 0 or less switches the idle rule off here. |
message | String? | nil | What the agent is told for this screen, with the same placeholders. |
Returns — a new GraineFrictionConfig.Screen.
Errors — none.
GraineFrictionConfig.Screen
Read it to see the rules one screen can override.
Friction rules for one screen, keyed by screen id in GraineFrictionConfig(screens:). A value type.
What to expect — Friction is the SDK noticing a customer who is stuck — stalled on a step, a field error repeated, looping between screens, rage taps, an incomplete submit, an error on the screen. The dashboard's rules apply unless the app overrides them; a signal lets the agent offer help.
| Field | Type | Default | Meaning |
|---|---|---|---|
var ignore: Bool? | Bool? | nil | True: no friction signals on this screen. |
var idleAfterMs: Int? | Int? | nil | The idle time for this screen; 0 or less switches the idle rule off here. |
var message: String? | String? | nil | What the agent is told for this screen, with the same placeholders. |
Its functions: init — each with its parameters, response, errors and an example.
GraineFrictionSignal
Read it to react in your own UI when the customer looks stuck.
The customer looks stuck, as reported to GraineListener.onFriction(_:). Made by the SDK; fired at most once per screen visit, only during a live conversation.
What to expect — Friction is the SDK noticing a customer who is stuck — stalled on a step, a field error repeated, looping between screens, rage taps, an incomplete submit, an error on the screen. The dashboard's rules apply unless the app overrides them; a signal lets the agent offer help.
| Field | Type | Default | Meaning |
|---|---|---|---|
let name: String | String | — | The signal: "stalled_on_step" — the customer stayed on one screen with a field outstanding. |
let screen: String | String | — | The screen's id. |
let detail: String | String | — | Plain English, given to the agent verbatim — for example the field that is outstanding and the message it shows. |
let data: [String: Any] | [String: Any] | — | seconds, field and error. |
let cooldownSeconds: Double | Double | — | The pacing the agent applies to unprompted interventions. |
let maxPerSession: Double | Double | — | The most unprompted interventions per conversation. |
let openLauncher: Bool | Bool | — | If your app draws its own launcher and this is true, open the launcher for this signal. |
GraineMasking.mask
Call it to mask a string the same way the SDK masks what it sends.
Replaces identifiers in one string with [PAN], [EMAIL], [CARD], [PHONE] and [AADHAAR], then applies the app's own rules.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input | String | required | Any text your app is about to report. |
Returns — String: the same text with every match replaced. Example value: "Mail me at [EMAIL]".
Errors — none.
GraineMasking.addRule
Call it once at startup to mask a value shape only your app knows.
A rule for a value shape only your app knows — an order number, a membership id. Registered for the whole app, once at startup, so it covers every screen; every match is replaced, not just the first. token is a template: $1 refers to a capture group.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
pattern | NSRegularExpression | required | What to find: a compiled regular expression. |
token | String | "[REDACTED]" | What each match becomes — a template, so $1 keeps the first capture group. |
Returns — nothing; it returns when done.
Errors — none.
GraineMasking.addRule
Call it to add a masking rule from a pattern string.
The same, from a pattern string. Returns false, and adds nothing, when the pattern does not compile.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
pattern | String | required | What to find, as a regular expression's text. |
token | String | "[REDACTED]" | What each match becomes — a template, so $1 keeps the first capture group. |
Returns — Bool. Example value: true.
Errors — none.
GraineMasking.clearCustomRules
Call it in tests to remove the masking rules your app added.
Removes the app's own rules. For tests only.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineMasking.deep
Call it to mask every string inside a JSON value.
Goes through a JSON value and masks each string it finds, however deeply nested. Keys are not masked; numbers and other non-string values are returned untouched.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Any? | required | Any JSON value — a map, a list, a string or a number. |
Returns — Any: the same shape, every string inside masked. Example value: ["note": "Mail me at [EMAIL]", "items": 3].
Errors — none.
GraineMasking
Read it to know what never leaves the device unmasked, and to add rules of your own.
What never leaves the device unmasked: PAN, email, card, Indian mobile and Aadhaar numbers are replaced, in that order, in what the app reports — screens, events, traits and the variables set while the app runs — before it is sent. Static members, safe from any thread; masking is a set of rules, not a guarantee.
What to expect — Applied on the device, before anything leaves it: card and account numbers, government ID numbers, phone numbers and email addresses are replaced; rules you add apply as well.
Its functions: mask, addRule, clearCustomRules, deep — each with its parameters, response, errors and an example.
GraineWidgetView (constructor)
Use it to draw the agent's components yourself, above your own UI, instead of in the bar.
The agent's components, drawn by the SDK in the dashboard's preset and answered for you. Mount it once, above your bar; while it is mounted the bar's own component slot steps aside.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to draw — another agent's (GraineInApp.named("support")) or the default when left out. |
accent | Color? | nil | Overrides the component's and the dashboard's accent colour. |
Returns — a new GraineWidgetView.
Errors — none.
GraineWidgetView
Use it as the component view.
The component the agent put on screen — product lists, plan comparisons, info cards, choices and quick actions, calendar slots, or the agent's fallback sentence for any other kind — drawn by the SDK in the dashboard's preset and answered for you. A SwiftUI view: mount it once, above your bar; it shows inline only when there is something to show (never as a full-screen cover, which would take the voice engine out of the window), and while it is mounted the bar's own component slot steps aside.
What to expect — Place it in your UI; it draws in the agent's design and follows the conversation by itself.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineVoice.answerWidget
Call it when your own UI drew a component and the customer answered it.
Passes the agent the customer's answer to a component it drew, on the in-app call. summary is the sentence the agent hears ("Let's compare the two plans."); without it the tap lands and the conversation does not move. GraineWidgetView does this itself. To answer on whichever conversation is live — the chat or the call — use GraineInApp.answerComponent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widgetId | String | required | The component's id, from the map onWidget delivered (GraineWidget.id). |
data | [String: Any] | required | What the customer chose, as JSON values — the agent can reason about it. |
summary | String | required | The sentence the agent hears as the customer's answer; without it the tap lands and the conversation does not move. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.answerComponent
Call it when a component you drew yourself was answered — in the chat or on a call.
Passes the agent the customer's answer to a component it drew, on the conversation that is live: the chat — the structured answer, then summary as the customer's own message, as the website's chat sends them — else the in-app call. summary is the sentence the agent hears ("The Creta, please."); without it the answer lands and the conversation does not move. GraineWidgetView and the bar answer for themselves: use this when your app draws components from onWidget. Safe from any thread (it hops to the main thread).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widgetId | String | required | The component's id, from the map onWidget delivered (GraineWidget.id). |
data | [String: Any] | required | What the customer chose, as JSON values — the agent can reason about it. |
summary | String | required | The sentence that is the customer's answer — their message in the chat; without it the answer lands and the conversation does not move. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.reopenComponent
Call it from a "show that again" control to bring back the last component the customer closed.
Puts back the last component the customer closed without answering, exactly as it was — for a "show that again" control. Returns false when there is none.
Parameters — none.
Returns — Bool. Example value: true.
Errors — none.
GraineWidget (constructor)
Use it to read a component map when you draw components yourself.
Reads a component map (widget_id or id, kind, data, fallback_text, style, on_select). Nil when it has no id. Pass the agent's baseUrl to resolve images.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
frame | [String: Any] | required | The component map onWidget delivered. |
baseUrl | String? | nil | Your GraineConfig.baseUrl, to resolve image paths that start with /; nil leaves them as they are. |
Returns — a new GraineWidget, or nil when the map has no id.
Errors — none.
GraineWidget
Read it to draw a component yourself.
A component the agent asked to show, read from the map GraineListener.onWidget(_:) delivers — for an app that draws components itself (GraineWidgetView draws them for you). A value type.
What to expect — A component the agent drew — a product list, choices, time slots, an information card — with the data it was drawn from. The SDK's views draw it in your design.
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | — | The component's id, which an answer names (answerWidget(widgetId:…)). |
let kind: String | String | — | Whatever kind the agent declares, for example "product_list", "plan_comparison" or "choices". |
let data: [String: Any] | [String: Any] | — | Its content, with root-relative image paths resolved against baseUrl. |
let fallbackText: String | String | — | What the agent would have said instead — draw it for a kind you do not render. |
let raw: [String: Any] | [String: Any] | — | The map exactly as it arrived. |
var preset: GraineWidgetPreset { get } | GraineWidgetPreset | — | The preset the dashboard chose, or .cards. |
Its functions: init — each with its parameters, response, errors and an example.
GraineWidgetSurface
Read it only to place components on screen the way the SDK does.
Where a component goes on screen — one rule shared with every platform, so the same component is not a sheet in one place and a card in another. Used by the SDK's component view; apps do not need it.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case screen | The component is the screen — pictures, several items to compare (product lists). |
case sheet | A choice to finish, then back to the app (plan comparisons, choices, quick actions, calendar slots). |
case card | A fact, or a kind this build cannot draw. |
GraineWidgetPreset
Read it to match the component look chosen in the dashboard.
How a component looks: the preset chosen in the dashboard's Web components, as GraineWidget.preset. Raw values are the wire words.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case gallery | Large pictures. |
case cards | Cards with medium pictures; the default. |
case list | Rows with small pictures. |
case spotlight | One item at a time, with a large picture. |
case compact | Small pictures and outlined buttons. |
Calls the user places
Calls like WhatsApp, on CallKit: the customer calls your agent from your app, on the system's call screen, with the audio in your app's own process — so the call carries on from the lock screen and with the screen off. One call at a time. With the optional GraineInAppRTC package in the app (Add WebRTC calls), the calls in both directions carry their audio over WebRTC: GraineCallsConfig.callTransport chooses, and GraineCalls.transport and transportFallbackReason say what carried each call.
GraineCalls.configure
Call it once at launch to set how calls ring and what they show.
How calls ring and what they show. Call it a single time at launch, ahead of enableVoIPPushes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
config | GraineCallsConfig | required | How calls ring and what they show: the call name, the ringtone, the icon, Recents and the ring timeout. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsConfig (constructor)
Use it to build that configuration.
Builds a calls configuration.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
displayName | String | "" | The name a call shows when nothing else names it. |
ringtoneSound | String? | nil | A sound file in your app's bundle (30 seconds or less) to ring with; left out, the brand's ring from the design, else the system's ringtone. |
iconTemplateImageData | Data? | nil | PNG data for the template image on the system call screen's app button: 40 × 40 points, transparent. Left out, the brand's launcher glyph from the design. |
includesCallsInRecents | Bool | false | Whether Graine's calls appear in the Phone app's Recents. |
ringTimeout | TimeInterval | 30 | Seconds a ring rings before it is missed — a ring from your own server; a ring Graine sends lasts the agent's Ring for from the dashboard instead. |
callTransport | GraineCallTransport | .auto | The road the calls' audio takes, in both directions: .auto (WebRTC when the GraineInAppRTC package is in the app and the dashboard allows it), .websocket or .webrtc. See GraineCallTransport. |
Returns — a new GraineCallsConfig.
Errors — none.
GraineCallsConfig
Read it to see every call setting your app controls.
How calls ring and what they show, passed to GraineCalls.configure(_:) once at launch. A value type.
What to expect — Made once, at launch, before anything else. Optional fields have the defaults shown; nothing in it is sent to Graine except what the descriptions say.
| Field | Type | Default | Meaning |
|---|---|---|---|
var displayName: String | String | "" | The name shown while a call rings and on the call screen — your agent's or your brand's. Empty: the agent's call name from the dashboard, else your app's name. With one agent configured a non-empty value wins; with several, each agent's dashboard call name comes first, so a ring says who is calling. A push's caller_name wins for its own call. |
var ringtoneSound: String? | String? | nil | The ringtone: a sound file bundled with your app. nil: the brand's ring when the design chose one, else the system ringtone. |
var iconTemplateImageData: Data? | Data? | nil | The template image, as PNG data (40×40 points, transparent), for the app button on the system call screen. nil: the brand's launcher glyph, from the design. |
var includesCallsInRecents: Bool | Bool | false | List the calls in the Phone app's Recents. |
var ringTimeout: TimeInterval | TimeInterval | 30 | Seconds an agent's call rings before it is missed (at least 1). A ring Graine sent lasts the agent's ring timeout from the dashboard instead. |
var callTransport: GraineCallTransport | GraineCallTransport | .auto | The road the calls' audio takes — a call the customer places and a ring they answer alike: .auto, .websocket or .webrtc (GraineCallTransport). The dashboard's In-app call audio set to Standard wins over it. Read at the start of each call, so a new configure applies from the next call. |
Its functions: init — each with its parameters, response, errors and an example.
GraineCalls.startCall
Call it when the customer taps your "Call" button to talk to the agent.
The customer calls the agent. The agent hears who_called = "customer" and call_reason (reason), with variables (masked on the device) over the SDK's own; a call carries at most 12 variables, names up to 40 characters, values up to 200, about 1 KB in all — the call's own first (who_called, call_reason, then variables), then the app's while they fit. agent names which configured agent to call; nil is the default. Returns the call's id — "call_" and a lowercase UUID — or nil when a call is already live or no agent has that name (each said in the log). Asks for the microphone first if the app does not hold it; refused, the call ends failed and onMicDenied is called. Ends any in-app (voice) call. Safe from any thread: it runs on the main thread and returns the id from there.
How the call connects (0.8.0). The same for a call the customer places and a ring they answer:
- It starts at once on the agent's configuration the SDK already has — this launch's, or the one kept on the device from an earlier launch — and refreshes it behind the call. Only when it has none it can use does the call wait for the network first.
- One 20-second deadline covers everything before the conversation opens: the agent's configuration, the ticket and the connection. Past it the call fails with "The call could not connect within 20 seconds." — it never sits on "Connecting…".
- A call is connected once it can be heard.
statestaysconnectinguntil the call's audio runs. If the conversation opens before CallKit has activated the call's audio, the SDK waits 3 seconds, then starts the audio itself, once; still no audio 3 seconds later, the call fails with "The call's audio could not start." instead of staying open and silent. - The prompt variables ride the call's ticket and are not added to the connection's address; only the direct connection, used when no ticket can be had, still carries them.
Where a failure shows. GraineCallsListener.onCallOutcome hears .failed(message) — outcome.error is the message, after .answered for a ring the customer answered; state is .ended(.failed) for a second (end reason failed), then .idle; GraineCallView says "Call failed"; the call ends on the system's call screen too. Other messages the SDK words there: "The microphone permission was not granted.", "The agent's configuration could not be loaded.", "The conversation did not open within 15 seconds.", "The line closed before the call connected." and "The call could not be placed: …" (CallKit's reason). With GraineConfig.debug on, the log also says when CallKit activates and deactivates the call's audio.
Which road the audio takes (0.9.0). With the GraineInAppRTC package in the app and GraineCallsConfig.callTransport allowing it (.auto does), the call tries WebRTC first — prepared at the tap, so it connects as fast as before. If WebRTC cannot set the call up before any audio flows — a network that blocks it, a server that turns it down, no answer within about 12 seconds — the call continues on the WebSocket inside the same call: no second ring, no new call screen, no error, "Connecting…" a moment longer. transport says which road carried it and transportFallbackReason why it is not WebRTC. A call the agent cannot take at all (no credit left, the agent switched off) ends failed with that reason, exactly as without the package. Once audio flows a call never moves: a lost network path is restarted once, then the call ends as dropped (.ended(.failed)).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | String? | nil | Why the customer is calling — the agent hears it as call_reason. |
variables | [String: Any] | [:] | Prompt variables by name, for this call or conversation; masked on the device before they leave it. |
agent | String? | nil | The agent's name in your app — the one it was configured under; the default agent when it is left out or empty. |
Returns — String?: the new call's id, which every state change and outcome carries; nil when a call is live or no agent has that name. Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — none thrown: a call that cannot connect ends failed, as above.
GraineCalls.end
Call it from your own hang-up control — or to decline a ring.
Hangs up — or declines, while it rings. Does nothing with no live call. The outcome follows: .declined for a ring, .ended(.local) for a call that was connecting or on.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.setMuted
Call it from your own mute control during a call.
Stops (or resumes) sending the customer's voice without ending the call; the system's call screen shows it too.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Bool | required | true to stop sending the customer's voice; false to send it again. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.setSpeaker
Call it from your own speaker control during a call.
The loudspeaker (true) or the earpiece or headset (false). A call starts on the earpiece, or a connected headset.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Bool | required | true for the loudspeaker; false for the earpiece or a headset. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallView (constructor)
Use it to show the call while your app is open.
The calls to draw; GraineInApp.shared.calls when nil, so an agent must be configured. The screen draws every moment of a call: a call Graine rang at a booked time ringing ("Scheduled call") and any other ring ("Incoming call"), each with Decline and Answer; connecting ("Connecting…"); on the call — the timer (with "· Muted" while muted), or "Muted", "Speaking" or "Listening" when the design has no timer, the line being said, and Mute, Speaker and End in the design's order; ended ("Call ended", "Call failed" or "Declined", the controls dimmed); and missed ("Missed call") with Close and Call back, for 30 seconds. The avatar is the brand's logo, its launcher glyph or the call name's initial; a ring pulses out while it rings and the avatar breathes while the agent speaks, not under Reduce Motion. A push that woke a closed app draws from the agent's settings kept on the device.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
calls | GraineCalls? | nil | The calls object to draw — GraineInApp.shared.calls when left out (an agent must be configured). |
Returns — a new GraineCallView.
Errors — none.
GraineCallView
Use it as the in-app call screen.
A ready call screen for while your app is in the foreground, drawn in the design of the agent the call is with; the system's own call screen covers the lock screen and the background. A SwiftUI view — present it over your UI, for example .fullScreenCover(isPresented: .constant(calls.showsCallScreen)) { GraineCallView() }.
What to expect — Place it in your UI; it draws in the agent's design and follows the conversation by itself.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineCalls.addListener
Call it to hear call states and outcomes, for your own UI or your server.
Hear about calls. Held weakly; called on the main thread.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | GraineCallsListener | required | Your listener; held weakly, so keep your own reference to it. Adding it twice has no effect. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.removeListener
Call it when that listener should stop hearing about calls.
Stops reporting to that listener.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | GraineCallsListener | required | The listener to stop telling. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsListener.onCallState
Implement it to follow the call as it moves, for your own call UI.
The call moved: idle, ringing, connecting, active or ended. Called only on a change. A call stays connecting until its audio runs, and moves to ended(.failed) when it cannot connect — see GraineCalls.startCall.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | GraineCallState | required | Where the call is now: idle, ringing, connecting, active or ended with its reason. |
callId | String? | required | The call's ID: your server's call_id, Graine's for a ring it sent, or the one startCall returned. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineCallsListener.onCallOutcome
Implement it to tell your server what became of each call.
How a call turned out — answered, declined, missed, ended (local, remote, failed) or failed. Tell your server, with the call id; do not depend on its order relative to onCallState. A call that could not connect is .failed with the reason in a sentence — such as "The call could not connect within 20 seconds." or "The call's audio could not start."
| Parameter | Type | Default | Meaning |
|---|---|---|---|
outcome | GraineCallOutcome | required | What became of the call: outcome.wire is one word for your server, outcome.endReason who ended a call that connected, outcome.error why one never did. |
callId | String | required | The call's ID: your server's call_id, Graine's for a ring it sent, or the one startCall returned. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineCalls.transport
Read it to know which road carried a call's audio — WebRTC or the WebSocket.
The road the current call's audio runs on: .webrtc or .websocket — never .auto. Nil until it is decided: a call trying WebRTC has none until its audio path connects (.webrtc) or it falls back (.websocket, with transportFallbackReason); a call on the WebSocket from the start has .websocket as soon as it begins connecting. So read it once the call is active. The same for both directions — a call placed with startCall and a ring the customer answered — and nil again once the calls are idle. Published on the main thread.
Parameters — none.
Returns — GraineCallTransport? — its cases: GraineCallTransport. Example value: .webrtc.
Errors — none.
GraineCalls.transportFallbackReason
Read it to know why a call that tried WebRTC runs on the WebSocket.
Set only for a call that tried WebRTC and continues on the WebSocket — WebRTC could not set it up before any audio flowed; the customer noticed nothing but "Connecting…" a moment longer. Nil for a call on WebRTC, for a call that never tried it (no GraineInAppRTC package, callTransport .websocket, the dashboard's Standard), and before a transport is decided. Read it once the call is active; nil again once the calls are idle. Its values are API — compare them as they are:
| Value | Means |
|---|---|
mint_failed | The call's WebRTC session could not be set up (refused, rate-limited, or unreachable). |
register_failed | Graine's media service did not accept the connection, or never answered it. |
no_response | The call got no reply at all. |
no_answer | Replies came, the answer did not. |
ice_timeout | Answered, but the audio path never connected — a network that blocks WebRTC. |
error | Anything else before the audio flowed. |
Parameters — none.
Returns — String?. Example value: "ice_timeout".
Errors — none.
GraineCallTransport.from
Call it to turn a word — from your own settings or a remote flag — into a GraineCallTransport.
"auto", "websocket" (or "ws") and "webrtc" (or "rtc"), trimmed and in any case; nil for anything else.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | String? | required | A word: auto, websocket, ws, webrtc or rtc; anything else, or nil, is nil. |
Returns — GraineCallTransport?. Example value: .webrtc.
Errors — none.
GraineRTC.install
Call it once at launch only if your build strips code nothing references; otherwise the calls find the WebRTC module by themselves.
In the GraineInAppRTC package (import GraineInAppRTC). Makes the WebRTC module available to the calls now, rather than when the first call looks for it. Adding the package is enough on its own — the calls find the module by themselves — so call it only in a build that strips code nothing references, in application(_:didFinishLaunchingWithOptions:), before the first call. Calling it again changes nothing; safe from any thread.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCallsListener
Implement it to hear about native calls.
What the calls report, for your server, added with GraineCalls.addListener(_:); every method has a default that does nothing. Called on the main thread; held weakly.
What to expect — Implement only the callbacks you need; the others do nothing. Called on the main thread.
| Property | Type | Meaning |
|---|---|---|
extension GraineCallsListener | — | An empty default for every callback, so your listener implements only the ones it needs. |
Its functions: onIncomingCall, onCallState, onCallOutcome — each with its parameters, response, errors and an example.
GraineCalls
Use it to place, answer and draw native calls: GraineInApp.shared.calls.
Native calls like WhatsApp, on CallKit and VoIP pushes — the customer calls your agent, or your agent rings the customer — with the audio in your app's own process, so a call carries on from the lock screen and with the screen off. Reached as GraineInApp.shared.calls (one object for the whole app, one call at a time); an ObservableObject on the main thread whose methods hop there when called elsewhere.
What to expect — The native call engine. Configure it once at launch, before a push can arrive; hand it the push tokens and the pushes; place calls with startCall and listen for outcomes. One call at a time on the device.
| Property | Type | Meaning |
|---|---|---|
@Published private(set) var state: GraineCallState = .idle | GraineCallState | Where the call is. connecting lasts until the call's audio runs; ended lasts one second before idle. |
@Published private(set) var callId: String? | String? | The current (or just-ended) call's id: the push's call_id, or the one startCall made. |
@Published private(set) var direction: GraineCallDirection? | GraineCallDirection? | Who rang whom: .incoming for a ring, .outgoing for a call the customer placed; nil with no call. |
@Published private(set) var callerName = "" | String | The name the call shows — draw it on your own call screen. |
@Published private(set) var muted = false | Bool | The customer's voice is not being sent. |
@Published private(set) var speakerOn = false | Bool | Audio from the loudspeaker rather than the earpiece or a headset. Follows the audio route, so the system's call screen can change it too. |
@Published private(set) var agentSpeaking = false | Bool | The agent's voice is playing — measured from the audio itself, not the agent's text. |
@Published private(set) var caption: GraineCaption? | GraineCaption? | The most recent line — the agent's words while they are spoken, or the customer's while they are heard. |
@Published private(set) var connectedAt: Date? | Date? | When the conversation opened; nil before. |
@Published private(set) var transcript: [GraineCaption] = [] | [GraineCaption] | What has been said on this call, oldest first (up to 12 lines), a live line replaced as its words arrive. Cleared when the call is over. |
@Published private(set) var hosted = false | Bool | This call was rung by Graine, as one booked for this customer (its push carried hosted: "1"). The call screen says "Scheduled call". |
@Published private(set) var agent: String? | String? | Which agent the current call is with, by the name given to GraineInApp.configure(_:name:); nil while idle. |
@Published private(set) var missedCall: GraineMissedCall? | GraineMissedCall? | The ring nobody answered, kept for 30 seconds after the call is over so a call screen can say "Missed call" and offer callBack() — or until the customer calls back, dismissMissedCall() is called, or another call rings or is placed. |
@Published internal(set) var transport: GraineCallTransport? | GraineCallTransport? | The road the current call's audio runs on, .webrtc or .websocket, once decided; nil before. See GraineCalls.transport. |
@Published internal(set) var transportFallbackReason: String? | String? | Why a call that tried WebRTC runs on the WebSocket — mint_failed, register_failed, no_response, no_answer, ice_timeout or error; nil otherwise. See GraineCalls.transportFallbackReason. |
var showsCallScreen: Bool { get } | Bool | Whether a call screen has something to show: a call, its last second, or a missed ring to call back. |
private(set) var config = GraineCallsConfig() | GraineCallsConfig | The configuration in force: the last one passed to configure. |
var callPermissions: GraineCallPermissions { get } | GraineCallPermissions | The call permissions as the phone holds them now. See GraineCalls.callPermissions. |
var callSetupProblems: [GraineCallSetupProblem] { get } | [GraineCallSetupProblem] | What the app's setup still lacks for calls, each with its fix. See GraineCalls.callSetupProblems. |
Its functions: configure, enableVoIPPushes, addListener, removeListener, startCall, reportIncomingCall, voipTokenUpdated, answer, end, setMuted, setSpeaker, callBack, dismissMissedCall, requestCallPermissions, openCallSettings, providerDidReset, provider, pushRegistry — each with its parameters, response, errors and an example.
GraineCallState
Read it to draw where a call is.
Where a native call is: GraineCalls.state and GraineCallsListener.onCallState(_:callId:).
What to expect — idle → ringing (an incoming call) or connecting (a call placed) → active → ended, then idle again once reported. One call at a time: a second ring while a call is live is missed (busy). A call is active only once its audio runs; one that cannot connect within 20 seconds, or whose audio never starts, goes from connecting to ended(.failed).
| Field | Type | Default | Meaning |
|---|---|---|---|
var isLive: Bool { get } | Bool | — | A call is ringing, connecting or active: no other may start. |
| Case | Meaning |
|---|---|
case idle | No call. |
case ringing | An agent's call is ringing on the device. |
case connecting | The call was answered or placed, and the conversation is opening. |
case active | Talking. |
case ended(GraineCallEndReason) | Over, and why. Becomes idle once reported, a second later. |
GraineCallDirection
Read it to tell a call the customer placed from a ring.
Who rang whom: GraineCalls.direction. Raw values are the wire words.
What to expect — Set when a call starts ringing or is placed, kept through its end, and nil again once the calls are idle.
| Case | Meaning |
|---|---|
case incoming | The agent rang the customer (a VoIP push). |
case outgoing | The customer called the agent (startCall). |
GraineCallEndReason
Read it to say why a call ended.
Why a call is over, inside GraineCallState.ended and GraineCallOutcome.ended. Raw values are the wire words.
What to expect — In the state, every end has a reason: failed covers a line that dropped mid-call and a call that never connected. In an outcome, .ended carries only local, remote or failed for a call that connected; a call that never connected is .failed with its reason instead.
| Case | Meaning |
|---|---|
case local | The customer hung up, or the app called end(). |
case remote | The agent hung up. |
case failed | The line was lost, or it never came up at all. |
case declined | The customer declined the ring. |
case missed | Nobody answered within the ring timeout. |
GraineCallOutcome
Read it to report what became of a call.
What became of a call, reported once through GraineCallsListener.onCallOutcome(_:callId:) — send it to your server with the call id. For a call Graine rang, the SDK also reports it to Graine.
What to expect — Reported once per call: answered, then ended with who ended it (local, remote, failed); or declined, missed (also when another call was live), or failed with why — such as "The call could not connect within 20 seconds." or "The call's audio could not start.". For a call Graine rang, the SDK has already told Graine. Send it to your server with the call id; do not rely on its order relative to the call's state changes.
| Field | Type | Default | Meaning |
|---|---|---|---|
var wire: String { get } | String | — | One word for your server: answered, declined, missed, ended or failed — the same words on Android. |
var endReason: GraineCallEndReason? { get } | GraineCallEndReason? | — | For a call that connected (.ended), what ended it: local, remote or failed. Nil otherwise. |
var error: String? { get } | String? | — | Why a call never connected (.failed). Nil otherwise. |
| Case | Meaning |
|---|---|
case answered | The customer answered the agent's call. |
case declined | The customer declined it. |
case missed | No one picked up before the ring timeout, or the call came in while another one was live. |
case ended(GraineCallEndReason) | A connected call has finished — .local, .remote or .failed. |
case failed(String) | It never connected; the string says why, for your logs. |
GraineCallTransport
Use it to choose the road the native calls' audio takes, and to read which one carried a call.
public enum GraineCallTransport: String, CaseIterable, Sendable — what the app asks for (GraineCallsConfig.callTransport), what carried a call (GraineCalls.transport: .webrtc or .websocket, never .auto) and the dashboard's switch (GraineHostedCallsSettings.audioTransport). Raw values are the words: auto, websocket, webrtc. Calls in both directions follow it.
What to expect — WebRTC needs the GraineInAppRTC package in the app: without it every call runs on the WebSocket, whatever is asked (.webrtc also logs a warning). The dashboard can only turn WebRTC off: its In-app call audio set to Standard wins over every choice, from the app's next start. A call WebRTC cannot set up continues on the WebSocket inside the same call, and transportFallbackReason says why.
| Case | Meaning |
|---|---|
case auto | The default: WebRTC when the GraineInAppRTC package is in the app and the dashboard allows it; the WebSocket otherwise. |
case websocket | Always the WebSocket: the call's audio on the conversation's own connection. It works on any network, though the audio suffers wherever the network loses data. |
case webrtc | WebRTC wherever it can carry the call — a jitter buffer, loss concealment and the platform's echo canceller; the WebSocket only when the package is missing, the dashboard says Standard, or WebRTC cannot set up this call. |
Its functions: from — each with its parameters, response, errors and an example.
GraineRTC
Use it only to make sure a stripped build keeps the WebRTC module: GraineRTC.install().
public enum GraineRTC — in the GraineInAppRTC package (import GraineInAppRTC): the WebRTC module for the native calls, in both directions.
What to expect — Adding the package is all the calls need: they find the module by themselves, and GraineCallsConfig.callTransport and the dashboard decide when it carries a call. Nothing to configure, nothing to call in a normal build.
Its functions: install — each with its parameters, response, errors and an example.
GraineRTCVersion
Read it to log which WebRTC module your app ships.
public enum GraineRTCVersion — in the GraineInAppRTC package: the module's version.
What to expect — A constant, always the same as GraineVersion.name — "0.10.3" in this release: the package is built against GraineInApp of exactly its own version. Include both when you ask for support about a call.
| Field | Type | Default | Meaning |
|---|---|---|---|
static let name: String | String | "0.9.0" | The module's version — GraineInApp's, the one it is built against. |
Calls Graine rings
Your agent rings the customer: a VoIP push — from Graine when your workspace has In-app calling set up, or from your own server — rings on the system's call screen, even when the app is closed. Calls the customer booked ring this way at their time. A ring the customer answers takes the same road as a call they place: WebRTC when the GraineInAppRTC package is in the app and GraineCallsConfig.callTransport allows it — prepared while it rings, so answering connects at once — else the WebSocket; GraineCalls.transport and transportFallbackReason say which carried it.
GraineCalls.enableVoIPPushes
Call it at every launch so your agent's calls can ring this iPhone.
Receive your agent's calls as VoIP pushes. Call it at launch, every launch: iOS wakes a terminated app for a push and delivers it once this has run. Every VoIP push is reported to CallKit before its completion runs, as iOS requires. onToken gets the device's VoIP token as hex — at once when already known, again when it changes, nil when iOS invalidates it — for your own server. When the workspace has hosted ringing (Graine rings this app), the SDK registers the token with Graine itself, for every configured agent whose workspace rings iOS — on a new token, a new signed-in customer, a new time zone or app version, and otherwise at most once a day — and reports what became of every ring.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
onToken | ((String?) -> Void)? | nil | Gets the device's VoIP token as hex — at once when already known, again when it changes, nil when iOS invalidates it — for your own server. Leave it out when only Graine rings your app. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.voipTokenUpdated
Call it only if your app runs its own push registry, with each new VoIP token.
For an app with its own PKPushRegistry: the VoIP token (hex, or nil once invalidated), handled as enableVoIPPushes would handle it — registered with Graine where Graine rings this app.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
token | String? | required | The device's VoIP token as hex, or nil once iOS has invalidated it. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.reportIncomingCall
Call it only if your app runs its own push registry, with each VoIP push.
For an app with its own PKPushRegistry: hands over a VoIP push's payload. It is reported to CallKit at once — before completion runs, as iOS requires — even when it is not a usable agent call or a call is already live; then it is ended at once, a live call is never disturbed, and a different call arriving mid-call is reported missed. The payload carries type: "agent_call" and a call_id, and optionally reason, caller_name, variables, hosted and agent_id (the agent the ring is for); the keys may sit under data.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
payload | [AnyHashable: Any] | required | The VoIP push's payload exactly as PushKit delivered it (PKPushPayload.dictionaryPayload). |
completion | (() -> Void)? | nil | PushKit's completion handler: called once, on the main thread, after the push has been reported to CallKit. |
Returns — nothing; it returns when done. A usable ring then rings (state → ringing, onIncomingCall). One it cannot ring is ended at once and reported through onCallOutcome with its call_id: .missed when another call is live, .failed("The push was not an agent call this SDK can ring.") when it is not an agent call; the same call pushed twice rings once.
Errors — none.
GraineCallsListener.onIncomingCall
Implement it to know when an agent's call is ringing, for your own UI or analytics.
An agent's call is ringing on this device. The SDK shows the ring; this is for your own UI or analytics.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
call | GraineIncomingCall | required | The ring as its push described it: its id, reason, caller name, variables, whether Graine rang it (hosted) and the agent it is from. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineCalls.answer
Call it from your own Answer button while a call rings.
Answers the ringing call from your own screen; the system's call screen answers by itself. Does nothing unless a call is ringing. The call is reported answered at once; if the microphone is then refused, or its prompt goes unanswered for a minute, the call ends failed. The conversation then connects as GraineCalls.startCall describes — one 20-second deadline, and connected only once its audio runs — so an answered call that cannot connect is reported answered, then failed with the reason. It takes the same road as a call the customer places: with the GraineInAppRTC package in the app, WebRTC, prepared while the ring showed (a declined or missed ring lets go of what it prepared), and the WebSocket inside the same call when WebRTC cannot carry it — transport and transportFallbackReason say which.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.callBack
Call it from your own Call back button after a missed ring.
Returns the missed call: the customer calls the agent that rang, with the missed call's reason (or returning_missed_call) as call_reason. The new call's id, or nil when there is nothing to return or a call is live.
Parameters — none.
Returns — String?. Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — none.
GraineCalls.dismissMissedCall
Call it when the customer has seen the missed call, so the call screen can go.
Tells the SDK the customer has seen the missed call, so its screen can be dismissed.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineIncomingCall (constructor)
Use it to build a sample ring for previews and tests.
Builds a ring by hand — for previews and tests of your own call screen or listener; the SDK makes the real ones from each push.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
callId | String | required | The call's ID: your server's call_id, Graine's for a ring it sent, or the one startCall returned. |
reason | String? | nil | Why the agent is calling — the agent hears it as call_reason. |
callerName | String? | nil | The name the call shows, over GraineCallsConfig.displayName. |
variables | [String: String] | [:] | Prompt variables by name, for this call or conversation; masked on the device before they leave it. |
hosted | Bool | false | Graine rang it (the push said hosted: "1"); its outcomes are reported to Graine too. |
agentId | String? | nil | The agent the push says the call is from; the ring goes to the configured agent with that id. |
Returns — a new GraineIncomingCall.
Errors — none.
GraineIncomingCall
Read it to know what a ring carries.
An agent's call as its push described it, delivered to GraineCallsListener.onIncomingCall(_:). A value type.
What to expect — When a ring reaches this device (a push from Graine or your server) — before the customer answers. It carries only what the ring shows: nothing personal.
| Field | Type | Default | Meaning |
|---|---|---|---|
let callId: String | String | required | The server's id for this call; every outcome carries it back. |
let reason: String? | String? | nil | The reason for the agent's call, which the prompt reads as call_reason. A call Graine rang keeps its reason on Graine, so it is nil there. |
let callerName: String? | String? | nil | A name to ring with that takes precedence over GraineCallsConfig.displayName. |
let variables: [String: String] | [String: String] | [:] | Extra prompt variables for this call, from the push. |
let hosted: Bool | Bool | false | Graine rang this call itself (the push says hosted: "1"); the SDK reports its outcomes to Graine. |
let agentId: String? | String? | nil | The agent the push says the call is from; the ring is routed to that configured agent (the default agent when none matches). |
Its functions: init — each with its parameters, response, errors and an example.
GraineMissedCall (constructor)
Use it to build a sample missed call for previews and tests.
Builds a missed call by hand — for previews and tests of a call screen; the SDK keeps the real one as GraineCalls.missedCall.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
callId | String | required | The call's ID: your server's call_id, Graine's for a ring it sent, or the one startCall returned. |
callerName | String | required | The name it rang with. |
reason | String? | nil | Why the agent called, when the push said. |
hosted | Bool | false | Rung by Graine, as a call booked for this customer. |
at | Date | Date() | When it was missed. |
agent | String? | nil | The name of the configured agent that rang; callBack() calls that agent. |
Returns — a new GraineMissedCall.
Errors — none.
GraineMissedCall
Read it to show a missed ring and offer to call back.
A ring nobody answered, kept as GraineCalls.missedCall so a call screen can say so and offer to call back. A value type.
What to expect — A ring nobody answered, kept for a call back (the call screen shows it for 30 seconds). at is when it rang.
| Field | Type | Default | Meaning |
|---|---|---|---|
let callId: String | String | required | The missed call's id: the push's call_id. |
let callerName: String | String | required | The name it rang with. |
let reason: String? | String? | nil | Why the agent called, when the push said. |
let hosted: Bool | Bool | false | Rung by Graine, as a call booked for this customer. |
let at: Date | Date | Date() | When it was missed. |
let agent: String? | String? | nil | The name of the configured agent that rang; callBack() calls that agent. |
Its functions: init — each with its parameters, response, errors and an example.
GraineHostedCallsSettings
Read it to know whether Graine rings this app, the booking rules it carries, and the dashboard's say on the calls' audio.
Whether Graine rings this app's devices, the booking settings and the In-app call audio setting, as the agent's settings carry them: GraineSessionConfig.calls. Made by the SDK.
What to expect — GraineSessionConfig.calls is nil when the agent's settings carry no calls section: nothing rings and nobody can book. Inside it, a missing value falls back: no ringing, every platform, the app's own ring timeout, booking off, and no say on the calls' audio (the app's callTransport decides).
| Field | Type | Default | Meaning |
|---|---|---|---|
let hosted: Bool | Bool | false | This app's devices are rung by Graine (the workspace has uploaded its push credentials). |
let platforms: [String] | [String] | [] | Which platforms Graine rings: ios, android. |
let ringTimeoutSeconds: Int? | Int? | nil | How long a ring Graine sent lasts before it is missed: the dashboard's Ring for, 20–60 seconds (a value outside is held to that range); nil uses the app's own ringTimeout. |
let callName: String? | String? | nil | The ring name the dashboard set for the agent's calls, if it set one. |
let usersCanBook: Bool | Bool | false | Whether customers are allowed to book calls from inside the app (scheduleCall). |
let maxDaysAhead: Int? | Int? | nil | The furthest ahead a booking may be, in days. |
let minLeadMinutes: Int? | Int? | nil | The soonest a booking may be, in minutes. |
let maxPendingPerUser: Int? | Int? | nil | Calls one customer may have waiting. |
let audioTransport: GraineCallTransport? | GraineCallTransport? | nil | The agent's In-app call audio setting (agent → Calls & scheduling): .websocket for Standard — every native call with this agent, in both directions, runs on the WebSocket whatever the app's GraineCallsConfig.callTransport — .webrtc for WebRTC (recommended), nil when the settings say nothing. Only .websocket changes anything: .webrtc and nil leave the app's callTransport to decide. It comes with the agent's settings, so a change reaches each install at the app's next start — no app release. |
var ringsThisPlatform: Bool { get } | Bool | — | Graine rings iOS devices for this agent: hosted, and platforms is empty or includes ios. |
Permissions and the setup check
What a call needs from the phone, and from your app's setup, in one place. The SDK never asks on its own at launch: it asks for the microphone when a call starts without it, and requestCallPermissions() asks earlier, when your app chooses. callSetupProblems says what your app's setup still lacks — each problem is also logged once, at configure, with its fix (subsystem ai.graine.inapp). A problem is never an exception: calls still work where they can.
GraineCalls.callPermissions
Read it to see which call permissions the phone holds now.
The call permissions as the phone holds them when read: the microphone — .granted, .denied (not asked yet) or .blocked (refused: only Settings can change it) — and .notNeeded for notifications and the full-screen ring, which iOS does not need: CallKit rings and draws its own screen. Safe from any thread.
Returns — a GraineCallPermissions. Read it again when your app returns to the foreground: the customer may have changed Settings.
Errors — none.
GraineCalls.requestCallPermissions
Call it at a moment you choose — onboarding, a "Call me" screen — to ask for what a call needs.
Asks for the call permissions the system can still ask for — on iOS the microphone, once — and answers them as they stand after. A microphone already granted or refused is not asked again: the answer comes at once. The SDK never asks on its own at launch; a call that starts without the microphone still asks then, as before. The completion runs on the main thread.
Returns — a GraineCallPermissions, after the customer answers (at once when there was nothing to ask).
Errors — none.
GraineCalls.openCallSettings
Call it to send the customer to the Settings page where a call permission is switched on.
Opens your app's page in the Settings app, where the microphone (and, for an app that posts notifications, its notifications) is switched on. Safe from any thread: it opens from the main thread.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
item | GraineCallPermission | required | The permission the customer should change: .microphone, .notifications or .fullScreen. On iOS each opens your app's page. |
Returns — Bool: true when Settings was asked to open.
Errors — none.
GraineCalls.callSetupProblems
Read it to learn what your app's setup still lacks for calls, in plain words.
What the app's setup still lacks for calls, read now — each a stable code and a message with the exact fix. The same problems are logged once each, as warnings, from configure on. Empty when nothing is missing.
| Code | When |
|---|---|
microphone_usage_missing | Info.plist has no NSMicrophoneUsageDescription: iOS closes the app when a call asks for the microphone. |
background_audio_missing | UIBackgroundModes lacks audio: a call goes silent when the screen locks. |
background_voip_missing | UIBackgroundModes lacks voip: iOS does not wake the app for a ring. |
push_entitlement_missing | The build is signed without Push Notifications — read from the profile a development or ad hoc build embeds; an App Store build embeds none, so nothing is claimed. |
voip_pushes_not_enabled | enableVoIPPushes() has not run 15 seconds after configure (nor does the app hand over its own registry's token): calls can't ring. |
no_voip_token | No VoIP token 15 seconds after VoIP pushes were enabled. Never reported on the simulator, which gets no token. |
microphone_blocked | The microphone is refused: only Settings can let calls hear the customer. |
hosted_ringing_off | A session is known and no configured agent's workspace has push credentials for iOS: Graine can't ring this phone. |
Returns — [GraineCallSetupProblem], the build's problems first, then what this launch has or has not done.
Errors — none.
GraineCallPermission
Use it to name a call permission.
public enum GraineCallPermission: String, CaseIterable, Sendable — a permission a WhatsApp-style call needs. Raw values are the words every Graine SDK uses.
What to expect — Three permissions, the same on every SDK. On iOS only the microphone is ever asked for; the other two are .notNeeded.
| Case | Meaning |
|---|---|
case microphone | The customer's voice. Asked by the SDK when a call starts without it, or by requestCallPermissions(). |
case notifications | A call that rings while the app is closed — an Android permission. iOS rings through CallKit, which needs none. |
case fullScreen | A ring that fills a locked screen — Android 14+. iOS: CallKit's own screen, nothing to allow. |
GraineCallPermissionStatus
Read it to tell whether a call permission is allowed, can still be asked for, or only Settings can change it.
public enum GraineCallPermissionStatus: String, Sendable — where a call permission stands. Raw values are the words every Graine SDK uses.
What to expect — .granted and .notNeeded need nothing; .denied means requestCallPermissions() can ask; .blocked means only Settings can change it, so offer openCallSettings(_:).
| Field | Type | Default | Meaning |
|---|---|---|---|
var isSatisfied: Bool { get } | Bool | — | Granted, or nothing to grant. |
| Case | Meaning |
|---|---|
case granted | Allowed. |
case denied | Not allowed yet: requestCallPermissions() can ask for it. On iOS, the microphone never asked. |
case blocked | Refused for good: only the Settings app can change it. On iOS, the microphone once refused. |
case notNeeded | This phone needs no such permission. |
GraineCallPermissions
Read it to see every call permission at once.
public struct GraineCallPermissions: Equatable, Sendable — every call permission's status, as callPermissions and requestCallPermissions() give them.
What to expect — A snapshot: read callPermissions again for the latest. allGranted is the one check before offering calls.
| Field | Type | Default | Meaning |
|---|---|---|---|
let microphone: GraineCallPermissionStatus | GraineCallPermissionStatus | — | The microphone. |
let notifications: GraineCallPermissionStatus | GraineCallPermissionStatus | — | Notifications for a ring — .notNeeded on iOS. |
let fullScreen: GraineCallPermissionStatus | GraineCallPermissionStatus | — | The full-screen ring — .notNeeded on iOS. |
var allGranted: Bool { get } | Bool | — | Every permission is granted or not needed: calls can ring and connect. |
init builds one (for a test or a preview); subscript reads one permission by name: permissions[.microphone].
GraineCallSetupProblem
Read it to show or log what your app's setup still lacks.
public struct GraineCallSetupProblem: Equatable, Sendable — one thing the app's setup still lacks for calls, from callSetupProblems.
What to expect — The code is stable and the same on every SDK (the list is under GraineCalls.callSetupProblems); the message is a sentence for the developer, with the exact fix.
| Field | Type | Default | Meaning |
|---|---|---|---|
let code: String | String | — | Stable: act on it, or count it. |
let message: String | String | — | What is missing and how to fix it, in plain words. |
Booking with the SDK's sheet
Let the customer book a call with no screen of your own: the SDK draws the booking sheet in your agent's design, books the call, and Graine rings this iPhone at that time. New in 0.8.0.
Booking a call: the SDK's sheet
Read this to know what the customer sees on the booking sheet, when it can book, and what each refusal says — before you offer it.
When it can book. bookingAvailable is true only when all three hold, for the agent you ask:
- Graine rings this iPhone: your workspace has In-app calling set up (
GraineHostedCallsSettings.hosted) and its platforms list is empty or names iOS; - the agent lets customers book (Users, from your app —
schedulingRules.usersCanBook); - the customer is identified with the trait the agent's Identity setting names (
identify; a placeholder such as"guest"is nobody).
The first two come from the agent's settings — this launch's, or the ones kept on the device; the third is read once this launch's settings have loaded, so bookingAvailable is false until then. Read it again after identify and after onSessionReady.
What the sheet shows, top to bottom, drawn in the agent's design from the Garage — its colours, typeface, corners and light or dark — as the chat window is:
- Book a call, and under it "call name will call you" — the agent's Name on the incoming call, else the chat window's title, else the heading (
"Assistant"when none is set) — with a close button. - Day — a chip for each day from the first day that still has a time to offer (usually today) to the furthest day the agent allows: "Today", "Tomorrow", then the weekday and date in the device's language ("Thu 16 Oct"). The chosen day is in the accent; the row scrolls sideways.
- Time — the system's time wheel, every 5 minutes, holding only the chosen day's times: from
earliest(now:)rounded up to the next 5 minutes tolatest(now:)rounded down to 5 minutes. Changing the day keeps the time where that day has it, else its nearest. - The time-zone line: the device's time zone and the time picked, such as "Europe/London · Thu 16 Oct, 6:00 PM".
- What's it about? (optional) — one line, up to 120 characters (a line break becomes a space), filled with the
reasonyou pass, which the customer may change. Sent as the booking's reason; left blank, none is sent. - Book call — the accent, full width, with a spinner while it books. While a booking is on its way the sheet cannot be closed.
Once booked, the sheet says "Booked — call name will call you on day, time" — always the weekday and date, never "Today": "Booked — Acme Assistant will call you on Thu 16 Oct, 6:00 PM" — with Done. The booking is scheduleCall's, so the chat's Upcoming calls card refreshes at once.
Refusals stay in the sheet as one line under Book call, in the danger colour; the customer picks again. The numbers are the agent's own rules:
| Code | The line the sheet shows |
|---|---|
too_soon | "Pick a time at least 5 minutes from now" — the agent's lead time ("1 minute" for one). The offer moves on to the times still possible |
too_far | "Pick a time within 14 days" |
outside_window | "That time is outside the hours we can call — pick another" |
too_many_pending | "You already have 3 calls booked — cancel one first" |
scheduling_off | "Booking a call isn't available right now" |
identity_required | "Sign in to book a call" |
anything else — rate_limited, unavailable, network, … | "Couldn't book just now — try again" |
When it cannot book. Opened while bookingAvailable is false, the sheet still opens, shows the scheduling_off or identity_required line, keeps Book call off and books nothing; presentBooking also logs a warning. When the rules leave no time to offer, there is no day to pick and Book call stays off.
Where it appears. On an iPhone, a sheet the customer can drag closed; on an iPad — when the app's window is at least 600 points on its shorter side — a centred panel 420 points wide (an iPad app in a narrow split gets the iPhone's sheet). That is presentBooking and GraineBookingViewController; .graineBookingSheet uses SwiftUI's own sheet, and GraineBookingView fills whatever you put it in.
In the chat window. Where bookingAvailable is true and the design keeps the booked-calls card (Booked calls in the dashboard, GraineChatTokens.showUpcomingCalls), GraineChatView and the bar's chat window show Book a call: under the booked calls in the Upcoming calls card, or alone as a compact row in the card's place when none are booked — never when the design hides the card. It opens this sheet, and a booking refreshes the card.
GraineInApp.bookingAvailable
Read it to decide whether to show your own "Call me" button.
Whether this agent can be booked from the app now: Graine rings this iPhone, the agent lets customers book, and the customer is identified with the trait the agent's Identity setting names — the rule in Booking a call: the SDK's sheet. Each agent answers for itself. It is worked out when you read it and is not published, so read it again after identify and after onSessionReady; it is false until this launch's settings have loaded. The chat window's Book a call follows the same rule.
Parameters — none.
Returns — Bool. Example value: true.
Errors — none.
GraineInApp.presentBooking
Call it to let the customer book a call on the SDK's own sheet, from anywhere in a UIKit or SwiftUI app.
Shows the booking sheet over whatever is on screen — the key window's top view controller: a sheet on an iPhone, a centred panel on an iPad. Returns once the sheet has closed — by Done, its close button or a swipe — with the booked call, or nil when the customer closed it without booking. Also returns nil at once, showing nothing, when no window is on screen yet (the log says so) or a booking sheet is already open: one booking at a time. Check bookingAvailable before offering it; opened where it is false, the sheet says why and books nothing. It runs on the main actor: from other code, call it in a Task.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | String? | nil | What "What's it about? (optional)" starts with, such as "Talk through my order"; the customer may change it. Up to 120 characters; kept with the booking for the agent and never put in a push. |
Returns — GraineScheduledCall? — its fields: GraineScheduledCall. Example value: GraineScheduledCall(id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: 2026-10-16 12:30:00 +0000, status: .scheduled, source: .app, reason: "Talk through my order").
Errors — none thrown: a refusal stays in the sheet as one line (the refusals) and the customer picks again; closing after a refusal returns nil.
View.graineBookingSheet
Use it in SwiftUI to present the booking sheet from a Bool binding, the way .sheet(isPresented:) works.
Presents the booking sheet while isPresented is true, as a SwiftUI sheet. onBooked hears the call the moment it is booked — before the customer taps Done. The sheet sets isPresented back to false itself on Done or its close button; a swipe down closes it too, except while a booking is on its way.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
isPresented | Binding<Bool> | required | Shows the sheet while true; set back to false when it closes. |
graine | GraineInApp? | nil | The configured agent to book with — another agent's (GraineInApp.named("support")) or the default when left out. |
reason | String? | nil | What "What's it about? (optional)" starts with; the customer may change it. |
onBooked | ((GraineScheduledCall) -> Void)? | nil | Called on the main thread with the booked call, the moment it is booked. |
Returns — some View: your view, with the sheet attached.
Errors — none: refusals stay in the sheet.
GraineBookingView (constructor)
Use it to show the booking sheet's content in a presentation of your own — a full-screen cover, a popover, a panel.
The sheet's content as a SwiftUI view: the header, the day chips, the time wheel, the time-zone line, "What's it about? (optional)" and Book call, then the booked line and Done. It fills the space it is given on the design's surface and draws nothing before an agent is configured. You present it and you dismiss it: onClose says when.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to book with — another agent's (GraineInApp.named("support")) or the default when left out. |
reason | String? | nil | What "What's it about? (optional)" starts with; the customer may change it. |
onBooked | ((GraineScheduledCall) -> Void)? | nil | Called on the main thread with the booked call, the moment it is booked. |
onClose | (GraineScheduledCall?) -> Void | required | The customer is done — Done or the close button: the booked call, or nil when nothing was booked. Dismiss the view here. A dismissal your own presentation makes (a swipe) does not call it. |
Returns — a new GraineBookingView.
Errors — none: refusals stay inside the view.
GraineBookingViewController (constructor)
Use it in a UIKit app to present the booking sheet yourself.
The booking sheet as a view controller that sets its own presentation: a page sheet on an iPhone; on an iPad — the app's window at least 600 points on its shorter side — a centred form sheet 420 × 640 points. Present it with present(_:animated:). It dismisses itself on Done or its close button; a swipe down closes it too, except while a booking is on its way. done is called exactly once, after it has closed, however it closed. Made in code only — not from a storyboard. presentBooking(reason:) presents one for you.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
graine | GraineInApp? | nil | The configured agent to book with — another agent's (GraineInApp.named("support")) or the default when left out. |
reason | String? | nil | What "What's it about? (optional)" starts with; the customer may change it. |
done | ((GraineScheduledCall?) -> Void)? | nil | Called once, on the main thread, after the sheet has closed: the booked call, or nil when nothing was booked. |
Returns — a new GraineBookingViewController.
Errors — none: refusals stay in the sheet.
GraineBookingView
Use it when you present the booking sheet your own way in SwiftUI.
public struct GraineBookingView: View — the booking sheet's content, drawn by the SDK in the agent's design. Made with GraineBookingView(graine:reason:onBooked:onClose:).
What to expect — It reads the agent's booking rules when it appears, and again if the agent's settings arrive while it is open; it books with scheduleCall, and a refusal stays inside it as one line. It never dismisses itself: onClose tells you when to.
| Property | Type | Meaning |
|---|---|---|
var body: some View | some View | SwiftUI's view body; apps do not call it. |
Its functions: init — with its parameters, response, errors and an example.
GraineBookingViewController
Use it when a UIKit screen presents the booking sheet.
public final class GraineBookingViewController: UIViewController, UIAdaptivePresentationControllerDelegate — the booking sheet hosted for UIKit, drawn in the agent's design.
What to expect — Presented, it adapts to the device (a sheet or a centred panel); it cannot be swiped away while a booking is on its way; done is called exactly once, after it has closed.
| Member | What it does |
|---|---|
override func viewDidLoad() | UIKit's lifecycle callback, which builds the sheet; apps never call it. |
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) | UIKit's callback when the customer swipes the sheet away; it calls done. Apps never call it. |
Its functions: init — with its parameters, response, errors and an example.
The scheduled-calls list
The calls booked for this customer — by the agent in a conversation, by the customer in your app or on the SDK's sheet, by your team or through the API — with the rules a booking follows, and the chat's Upcoming calls card that shows them.
GraineInApp.schedulingRules
Read it to offer only the times the agent accepts on a booking screen of your own.
The rules for booking calls with this agent, from its settings — this launch's, or before they load, the last ones kept on the device; with neither, the defaults with booking off (usersCanBook false). scheduleCall checks a time against them before it sends anything, and the SDK's booking sheet offers only times between earliest(now:) and latest(now:). Read it again after onSessionReady.
Parameters — none.
Returns — GraineSchedulingRules — its fields: GraineSchedulingRules. Example value: GraineSchedulingRules(usersCanBook: true, minLeadMinutes: 5, maxDaysAhead: 14, maxPendingPerUser: 3, ringTimeoutSeconds: 45, callName: "Acme Assistant", hostedRinging: true, platforms: [.ios, .android]).
Errors — none.
GraineInApp.scheduleCall
Call it to book a call from a booking screen of your own.
Asks the agent to call this customer at date; Graine rings this device then, on the system's call screen. Waits for the agent's settings, then checks on the device before sending anything: scheduling_off when the agent does not let customers book, too_soon before schedulingRules.earliest(now:), too_far after latest(now:). reason (trimmed, cut to 120 characters) is what the agent is told the call is for; it never appears in the push. The time is sent with the device's offset and time zone; the booking is the identified customer's, or this install's when nobody is identified. Returns the booked call (status scheduled) and refreshes the chat's upcoming calls.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at date | Date | required | When the agent should ring: between schedulingRules.earliest() and latest(). Sent with the device's offset and time zone. |
reason | String? | nil | What the call is about, at most 120 characters — the agent hears it as call_reason when it rings. |
Returns — GraineScheduledCall — its fields: GraineScheduledCall. Example value: GraineScheduledCall(id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: 2026-10-16 12:30:00 +0000, status: .scheduled, source: .app, reason: "Talk through my order").
Errors — GraineServiceError. On the device, before anything is sent (status 0), in the booking sheet's words: scheduling_off — "Booking a call isn't available right now"; too_soon — "Pick a time at least 5 minutes from now" ("1 minute" for one); too_far — "Pick a time within 14 days" (the numbers are the agent's rules). From Graine, in its own words: outside_window, too_many_pending, identity_required, device_limit, invalid_request, rate_limited, network, unavailable, invalid_key, origin_refused. unknown("unreadable_answer") — "The booking was accepted, but its answer did not describe the call." — when Graine took the booking but its answer could not be read: refresh scheduledCalls() before booking again. The SDK's booking sheet shows each as one line instead (Booking a call: the SDK's sheet).
GraineInApp.scheduledCalls
Call it to list the customer's calls still to come.
This customer's calls still to come — this install's when nobody is identified — upcoming first. Waits for the agent's settings first.
Parameters — none.
Returns — [GraineScheduledCall] — its fields: GraineScheduledCall. Example value: [GraineScheduledCall(id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: 2026-10-16 12:30:00 +0000, status: .scheduled, source: .app, reason: "Talk through my order")].
Errors — GraineServiceError: network, rate_limited, unavailable, invalid_key, origin_refused
GraineInApp.cancelScheduledCall
Call it when the customer calls off a booked call.
Calls off one of this customer's booked calls (this install's when nobody is identified); not_found for anything else. Returns the call's status afterwards — cancelled, also when Graine's answer names none.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The booking's id (a UUID), from GraineScheduledCall.id. |
Returns — GraineCallStatus — its fields: GraineCallStatus. Example value: .cancelled.
Errors — GraineServiceError: not_found, network, rate_limited, unavailable, invalid_key, origin_refused
GraineChat.upcomingCalls
Read it to show the customer's booked calls in your own UI — the chat window's Upcoming calls card draws the same list.
This customer's calls still to come (this install's when nobody is identified), soonest first, at most three: only calls with a time that are still upcoming (isUpcoming). Fetched when the chat opens, after the agent finishes a reply — it may have just booked one — at most every 10 seconds, after any booking (scheduleCall, and so the booking sheet), and on refreshUpcomingCalls(). Only where Graine rings this app; elsewhere it stays empty. A cancelled call leaves the list once Graine confirms. Published on the main thread, so SwiftUI redraws as it changes.
Parameters — none.
Returns — [GraineScheduledCall] — its fields: GraineScheduledCall. Example value: [GraineScheduledCall(id: "6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44", at: 2026-10-16 12:30:00 +0000, status: .scheduled, source: .app, reason: "Talk through my order")].
Errors — none: a list that could not be fetched keeps what it had.
GraineChat.refreshUpcomingCalls
Call it after a booking made outside the chat, so the chat's Upcoming calls card shows it.
Fetches the upcoming calls again — after booking one elsewhere, say.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineChat.cancelUpcomingCall
Call it from your own list to cancel a call shown in the chat's Upcoming calls.
Cancels one of the card's calls. Its row says "Cancelling…" until Graine has it, then leaves upcomingCalls; a failure leaves the row saying "Couldn’t cancel · tap to retry". Nothing is thrown — use cancelScheduledCall(id:) to handle the error yourself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The booking's id, from GraineChat.upcomingCalls. |
Returns — nothing; it returns when done.
Errors — none.
GraineScheduledCall (constructor)
Use it to build sample booked calls for previews and tests.
Builds a booked call by hand — for previews and tests of a screen that lists calls; scheduleCall and scheduledCalls() make the real ones.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The booking's id (a UUID) — what cancelScheduledCall(id:) takes. |
at | Date? | required | When it rings; nil only when Graine's time for it could not be read. |
status | GraineCallStatus | .scheduled | Where the booked call is: scheduled, ringing, answered, … |
source | GraineCallSource? | nil | Who booked it: the agent, your app, your team or the API; nil when not known. |
reason | String? | nil | What the call is about, as it was booked (up to 120 characters). |
Returns — a new GraineScheduledCall.
Errors — none.
GraineScheduledCall
Read it to show one booked call.
A call the agent will make to this customer, booked for later: returned by scheduleCall(at:reason:) and scheduledCalls(), and listed in GraineChat.upcomingCalls. When its time comes, Graine rings this device on the system's call screen.
What to expect — From scheduleCall (status scheduled), scheduledCalls() (upcoming first) and the chat's upcoming calls. id is a UUID. at is a moment (null only when Graine's time could not be read): show it in the device's time zone. reason is at most 120 characters, or null. isUpcoming is scheduled or ringing; canCancel is scheduled only. A status or source newer than the SDK arrives as unknown, carrying its word.
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | required | Graine's id for the booking — what cancelScheduledCall(id:) takes. |
let at: Date? | Date? | required | When it rings. Nil only when Graine's time for it could not be read. |
let status: GraineCallStatus | GraineCallStatus | .scheduled | Where the booked call is — see GraineCallStatus. |
let source: GraineCallSource? | GraineCallSource? | nil | Who booked it; nil when Graine does not say. |
let reason: String? | String? | nil | Why, as it was booked (up to 120 characters); nil when none was given. |
var isUpcoming: Bool { get } | Bool | — | Still to come: scheduled, or ringing now. |
var canCancel: Bool { get } | Bool | — | It can still be called off: scheduled, not rung yet. |
Its functions: init — each with its parameters, response, errors and an example.
GraineCallStatus (constructor)
Use it to turn a status word from your server into a GraineCallStatus.
The status for a wire word (trimmed, any case); unknown for any other word.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | A status word as Graine or your server sends it — scheduled, no_device, … |
Returns — a new GraineCallStatus: .unknown(word) for a word this build does not know.
Errors — none.
GraineCallStatus
Read it to show where a booked call is.
Where a booked call is. A status this build does not know arrives as unknown, carrying its word.
What to expect — scheduled → ringing → answered → completed; or declined (nothing rings that customer again for 6 hours), missed (rang out), failed, cancelled (called off before it rang), expired (its time passed while it could not ring: more than 10 minutes late, or no opening left in the calling hours), no_device (no device of the customer's seen in the last 30 days — a browser cannot ring). A status newer than this SDK arrives as unknown, carrying its word: give every switch a default.
| Field | Type | Default | Meaning |
|---|---|---|---|
var wire: String { get } | String | — | The word on the wire: scheduled, …, no_device; an unknown status's own word. |
var description: String { get } | String | — | The same as wire, for printing and logs. |
| Case | Meaning |
|---|---|
case scheduled | Wire scheduled. Booked, waiting for its time. |
case ringing | Wire ringing. Ringing on the customer's device. |
case answered | Wire answered. Picked up; the conversation is on. |
case completed | Wire completed. The conversation ended. |
case declined | Wire declined. The customer declined it. |
case missed | Wire missed. Nobody answered within the ring timeout. |
case failed | Wire failed. It could not connect. |
case cancelled | Wire cancelled. Called off. |
case expired | Wire expired. It could not ring in time. |
case noDevice | Wire no_device. There was no device belonging to this customer for Graine to ring. |
case unknown(String) | A status this build does not know, with its word. |
Its functions: init — each with its parameters, response, errors and an example.
GraineCallSource (constructor)
Use it to turn a source word into a GraineCallSource.
The source for a wire word (trimmed, any case); sdk and app are both .app.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | A source word — agent, sdk (or app), dashboard or api. |
Returns — a new GraineCallSource: .unknown(word) for any other word.
Errors — none.
GraineCallSource
Read it to show who booked a call.
Who booked a call. A source this build does not know arrives as unknown, carrying its word.
What to expect — Who booked it: the agent in a conversation, your app (the word Graine sends is sdk), your team in the dashboard, or the API. A source newer than this SDK arrives as unknown, carrying its word.
| Field | Type | Default | Meaning |
|---|---|---|---|
var wire: String { get } | String | — | The word on the wire; sdk for .app. |
var description: String { get } | String | — | The same as wire, for printing and logs. |
| Case | Meaning |
|---|---|
case agent | Wire agent. The agent, in a conversation. |
case app | Wire sdk. The app's own booking, through scheduleCall. |
case dashboard | Wire dashboard. Your team, in the dashboard. |
case api | Wire api. The API. |
case unknown(String) | A source this build does not know, with its word. |
Its functions: init — each with its parameters, response, errors and an example.
GraineSchedulingRules (constructor)
Use it to build rules for previews and tests of a booking screen.
Builds a set of rules by hand — for previews and tests of a booking screen; GraineInApp.schedulingRules gives the agent's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
usersCanBook | Bool | false | Whether customers are allowed to book calls from inside the app. |
minLeadMinutes | Int | 5 | The soonest a booking may be, in minutes from now. |
maxDaysAhead | Int | 14 | The furthest ahead a booking may be, in days. |
maxPendingPerUser | Int | 3 | How many calls one customer may have waiting. |
ringTimeoutSeconds | Int | 45 | How long a ring Graine sends lasts before it is missed, in seconds. |
callName | String? | nil | The name the agent's calls ring with. |
hostedRinging | Bool | false | Whether Graine rings this app's devices. |
platforms | [GraineRingPlatform] | [] | Which platforms Graine rings; empty means every platform. |
Returns — a new GraineSchedulingRules.
Errors — none.
GraineSchedulingRules.earliest
Call it for the earliest time your booking screen should offer.
The soonest time a call may be booked for, which is now plus minLeadMinutes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
now | Date | Date() | The moment to count from; now when left out. |
Returns — Date: the earliest moment a booking may be for. Example value: now plus 5 minutes.
Errors — none.
GraineSchedulingRules.latest
Call it for the latest time your booking screen should offer.
The latest: now plus maxDaysAhead days.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
now | Date | Date() | The moment to count from; now when left out. |
Returns — Date: the latest moment a booking may be for. Example value: now plus 14 days.
Errors — none.
GraineSchedulingRules
Read it to offer only the times the agent accepts.
The rules for booking calls, from the agent's settings: GraineInApp.schedulingRules. scheduleCall checks a time against them before it asks, and a booking screen can offer only times between earliest(now:) and latest(now:).
What to expect — A property: no request. Read from the agent's settings when they load; before that the defaults hold, with booking off (iOS and Android use the last rules kept on the device). scheduleCall refuses on the device first — scheduling_off, too_soon (before earliest(now) or not in the future), too_far (after latest(now)) — with status 0; Graine then checks the calling hours and the calls already waiting. ringTimeoutSeconds is 20–60 (45 by default): every SDK holds a value outside that range to it.
| Field | Type | Default | Meaning |
|---|---|---|---|
let usersCanBook: Bool | Bool | false | Customers may book calls from the app. False when the agent's settings say nothing. |
let minLeadMinutes: Int | Int | 5 | The soonest a booking may be, in minutes from now. |
let maxDaysAhead: Int | Int | 14 | The furthest ahead a booking may be, in days. |
let maxPendingPerUser: Int | Int | 3 | Calls one customer may have waiting. |
let ringTimeoutSeconds: Int | Int | 45 | How long a ring from Graine goes on before it counts as missed. |
let callName: String? | String? | nil | The ring name the dashboard set for the agent's calls, if it set one. |
let hostedRinging: Bool | Bool | false | This app's devices are rung by Graine (the workspace has uploaded its push credentials). |
let platforms: [GraineRingPlatform] | [GraineRingPlatform] | [] | Which platforms Graine rings; empty means every platform. |
Its functions: init, earliest, latest — each with its parameters, response, errors and an example.
GraineRingPlatform
Read it to see which platforms Graine rings.
A platform Graine rings, in GraineSchedulingRules.platforms. Raw values are the wire words.
What to expect — Give every switch over it a default case.
| Case | Meaning |
|---|---|
case ios | iOS devices. |
case android | Android devices. |
Design
The brand's design from the Garage, as tokens: the SDK's own views draw from them, and your own UI can too. A design reaches the SDK with no release; without one, the views draw from the dashboard's appearance settings.
GraineInApp.design
Read it to get both of the brand's token sets — light and dark — and the brand's choice between them.
The brand's design from the agent's settings (the Garage), resolved by Graine into two complete token sets. Nil before the settings load, and when the agent has no design — the SDK's views then draw from the dashboard's appearance settings, as before designs. Most apps read theme instead, which has already picked the set to draw.
Parameters — none.
Returns — GraineDesign? — its fields: GraineDesign. Example value: GraineDesign(scheme: .auto, preset: nil, light: …, dark: …) — both sets complete.
Errors — none.
GraineInApp.theme
Read it to draw your own UI in the brand's design right now.
The design tokens to draw right now: the brand's fixed scheme, else the device's current light or dark, picked again whenever the device switches. Nil without a design. Up to date when onSessionReady is called; GraineListener.onTheme reports each change.
Parameters — none.
Returns — GraineThemeTokens? — its fields: GraineThemeTokens. Example value: a token set whose colors.accent is "#3740B8".
Errors — none.
GraineDesign.tokens
Call it to get the token set for the device's light or dark appearance.
Which tokens to draw: the brand's fixed scheme, else the device's current appearance — light when the device has not said (nil). Ask again when the device switches.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
deviceDark | Bool? | required | Whether the device is in dark mode now; nil when it has not said (then light). |
Returns — GraineThemeTokens — its fields: GraineThemeTokens.
Errors — none.
GraineDesign.parse
Call it only to read a design you hold as JSON; the SDK reads the agent's for you.
Reads the design value of an agent's settings. Nil when there is none worth drawing — absent, not an object, or either scheme without a complete set of colours; nil is not an error. Used by the SDK; apps rarely need it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | Any? | required | The design value of the agent's settings, as JSON. |
Returns — GraineDesign?. Example value: nil.
Errors — none.
GraineDesign
Read it to get both of the brand's token sets and the brand's choice between them.
The brand's design: two complete token sets, light and dark, and the brand's choice between them — GraineInApp.design. Resolved by Graine, so a new design style reaches the SDK as tokens with no release.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let scheme: GraineDesignScheme | GraineDesignScheme | .auto | A fixed scheme, or .auto to follow the device. |
let preset: String? | String? | — | The design's preset name — informational; the tokens are already resolved. |
let light: GraineThemeTokens | GraineThemeTokens | — | The token set for light appearance. |
let dark: GraineThemeTokens | GraineThemeTokens | — | The token set for dark appearance. |
Its functions: tokens, parse — each with its parameters, response, errors and an example.
GraineDesignScheme
Read it to know whether the brand fixed light or dark.
Which of a design's two token sets to draw. Raw values are the wire words.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case auto | Follow the device's light or dark. |
case light | Always the light set. |
case dark | Always the dark set. |
GraineThemeTokens
Use it to draw your own UI in the brand's design.
One scheme's design tokens — flat numbers and #RRGGBB strings, lengths in points — for an app that draws its own UI in the brand's design: GraineInApp.theme, onTheme or GraineDesign.tokens(deviceDark:). A missing or malformed value falls back to Graine Classic's, and a scheme without a complete set of colours makes the whole design nil.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let preset: String | String | — | The design style these tokens came from. |
let scheme: String | String | — | Tells which of the design's two sets this is — "light" or "dark". |
let colors: Colors | GraineThemeTokens.Colors | — | The palette — see GraineThemeTokens.Colors. |
let font: Typography | GraineThemeTokens.Typography | — | Typefaces, sizes, weights and spacing — see GraineThemeTokens.Typography. |
let shape: Shape | GraineThemeTokens.Shape | — | Corners, outline width and the launcher's outline — see GraineThemeTokens.Shape. |
let spacing: Spacing | GraineThemeTokens.Spacing | — | Spacing — see GraineThemeTokens.Spacing. |
let elevation: Elevation | GraineThemeTokens.Elevation | — | Shadows, or a solid offset — see GraineThemeTokens.Elevation. |
let motion: Motion | GraineThemeTokens.Motion | — | How things move — see GraineThemeTokens.Motion. |
let icons: Icons | GraineThemeTokens.Icons | — | Icon style and the launcher glyph — see GraineThemeTokens.Icons. |
let glass: Glass? | GraineThemeTokens.Glass? | — | Translucent surfaces; nil for opaque ones — see GraineThemeTokens.Glass. |
let bubbles: GraineBubbleStyle | GraineBubbleStyle | — | How chat messages sit on the page. |
let chrome: GraineDesignChrome | GraineDesignChrome | — | .window: the chat window, the caption card and every component card open as desktop windows. |
let chat: GraineChatTokens? | GraineChatTokens? | — | The chat window's own tokens; nil from an older server, when the chat draws from colors. |
let call: GraineCallTokens? | GraineCallTokens? | — | The call screen's tokens; nil from an older server, when the call screen draws as it did before designs. |
Its functions: launcherGlyphUrl — each with its parameters, response, errors and an example.
GraineThemeTokens.Colors
Read it to colour your own UI exactly as the agent's design does.
public struct GraineThemeTokens.Colors — the palette of one scheme, as GraineThemeTokens.colors: seventeen colours, each a #RRGGBB string in upper case.
What to expect — Never partial. A design must carry all seventeen colours, each a valid colour, in both its light and its dark set; if any is missing or malformed, the SDK treats the whole design as absent (GraineInApp.design is nil) and draws from the dashboard's appearance settings instead. There are no defaults to fall back to.
| Field | Type | Default | Meaning |
|---|---|---|---|
let background: String | String | — | The page colour behind everything in the chat window — use it for your own full-screen backgrounds. |
let surface: String | String | — | The voice bar, the chat header and sheets (the booking sheet included) — use it for your own bars and panels. |
let surfaceRaised: String | String | — | Cards, the composer field and unselected chips — use it for anything that sits on a surface. |
let text: String | String | — | Primary text. |
let textMuted: String | String | — | Secondary text, captions and status lines. |
let border: String | String | — | Hairlines and outlines. |
let accent: String | String | — | Primary buttons, the launcher, the send button and anything selected. |
let onAccent: String | String | — | Text and icons drawn on the accent, already checked for contrast. |
let accentSoft: String | String | — | Chips, selected rows and the focus halo; the customer's bubbles in the minimal style. |
let success: String | String | — | Success states, such as the online dot in a header on the surface colour. |
let warning: String | String | — | Warnings. |
let danger: String | String | — | The end-call button, and errors such as the booking sheet's refusal line. |
let userBubble: String | String | — | The customer's chat bubbles in the filled style. |
let userBubbleText: String | String | — | Text on the customer's bubbles. |
let agentBubble: String | String | — | The agent's chat bubbles in the filled style. |
let agentBubbleText: String | String | — | Text on the agent's bubbles. |
let scrim: String | String | — | What dims the app behind a modal sheet, drawn at 40% opacity. |
GraineThemeTokens.Typography
Read it to set your own text in the design's typefaces, sizes and weights.
public struct GraineThemeTokens.Typography — the type of one scheme, as GraineThemeTokens.font: two families, a size scale, weights, letter spacing and line height.
What to expect — A missing or malformed value falls back to Graine Classic's, shown as the default. The SDK downloads a family the design names, from Graine, unless your app ships it (GraineConfig.fonts); the SDK's own views draw the system font at the same weight until it arrives, or if it never does.
| Field | Type | Default | Meaning |
|---|---|---|---|
let body: String | String | "system" | The family for body text: "system" is the platform's own font (San Francisco); any other name is a family to load. |
let heading: String | String | "system" | The family for titles and headings, read the same way. |
let scale: Scale | GraineThemeTokens.Typography.Scale | caption 12 · body 15 · title 17 · display 22 | The text sizes, in points. |
let weights: Weights | GraineThemeTokens.Typography.Weights | body 400 · strong 600 · title 600 | The weights, as CSS numbers. |
let letterSpacing: Pair | GraineThemeTokens.Typography.Pair | body 0 · title -0.01 | Letter spacing in em — multiply by the size for points; held to -0.5 to 0.5. |
let lineHeight: Pair | GraineThemeTokens.Typography.Pair | body 1.45 · title 1.25 | Line height as a multiple of the size. |
struct Scale, struct Weights, struct Pair | nested types | — | See GraineThemeTokens.Typography.Scale, .Weights and .Pair. |
GraineThemeTokens.Typography.Scale
Read it to size your own text on the design's scale.
public struct GraineThemeTokens.Typography.Scale — four sizes, in points, as GraineThemeTokens.font.scale.
What to expect — Each is more than 0 and at most 200; a value that is not falls back to Graine Classic's.
| Field | Type | Default | Meaning |
|---|---|---|---|
let caption: Double | Double | 12 | Small text: captions, labels and status lines. |
let body: Double | Double | 15 | Body text and chat messages. |
let title: Double | Double | 17 | Titles, such as a header's. |
let display: Double | Double | 22 | The largest text, for display. |
GraineThemeTokens.Typography.Weights
Read it to weight your own text the way the design does.
public struct GraineThemeTokens.Typography.Weights — three weights as CSS numbers, as GraineThemeTokens.font.weights.
What to expect — Each is a whole number from 100 to 900 (a value outside is held to that range and rounded); a missing one falls back to Graine Classic's. 400 is regular and 600 semibold.
| Field | Type | Default | Meaning |
|---|---|---|---|
let body: Int | Int | 400 | Regular text. |
let strong: Int | Int | 600 | Emphasis: labels and buttons. |
let title: Int | Int | 600 | Titles. |
GraineThemeTokens.Typography.Pair
Read it for a value that differs between body text and titles.
public struct GraineThemeTokens.Typography.Pair — one value for body text and one for titles; used for letterSpacing (in em) and lineHeight (a multiple of the size).
What to expect — Filled in from Graine Classic's when the design does not say.
| Field | Type | Default | Meaning |
|---|---|---|---|
let body: Double | Double | see Typography | The value for body text. |
let title: Double | Double | see Typography | The value for titles. |
GraineThemeTokens.Shape
Read it for the design's corners, outline width and launcher shape.
public struct GraineThemeTokens.Shape — as GraineThemeTokens.shape.
What to expect — Filled in from Graine Classic's when the design does not say: the radii above, a 1-point outline and a round launcher.
| Field | Type | Default | Meaning |
|---|---|---|---|
let radius: Radius | GraineThemeTokens.Radius | 8 · 12 · 18 · 24 | The corner radii. |
let borderWidth: Double | Double | 1 | The width of every outline, in points, from 0 to 8. |
let launcher: GraineLauncherShape | GraineLauncherShape | .circle | The floating launcher's outline. |
GraineThemeTokens.Radius
Read it to round your own corners the way the design does.
public struct GraineThemeTokens.Radius — four corner radii, in points, as GraineThemeTokens.shape.radius.
What to expect — Each is 0 to 200; a missing or negative one falls back to Graine Classic's.
| Field | Type | Default | Meaning |
|---|---|---|---|
let sm: Double | Double | 8 | Small corners: a component's chips, and the square launcher. |
let md: Double | Double | 12 | Buttons and text fields. |
let lg: Double | Double | 18 | Cards, and the rounded-square launcher. |
let xl: Double | Double | 24 | The roundest corners: pill-shaped chips such as the chat's starters and the booking sheet's days. |
GraineThemeTokens.Spacing
Read it to space your own UI on the design's rhythm.
public struct GraineThemeTokens.Spacing — as GraineThemeTokens.spacing, in points.
What to expect — Each is 0 to 200; a missing or negative one falls back to Graine Classic's.
| Field | Type | Default | Meaning |
|---|---|---|---|
let unit: Double | Double | 4 | The base step: other spacing is a multiple of it. |
let cardPadding: Double | Double | 14 | The padding inside a card. |
let gap: Double | Double | 10 | The gap between items in a list or row. |
GraineThemeTokens.Elevation
Read it to give your own cards and floating views the design's depth.
public struct GraineThemeTokens.Elevation — as GraineThemeTokens.elevation.
What to expect — When the design says nothing about elevation, Graine Classic's soft shadows and no hard offset. When it names a shadow as empty, that shadow is nil: draw none.
| Field | Type | Default | Meaning |
|---|---|---|---|
let card: Shadow? | GraineThemeTokens.Shadow? | Classic's card shadow | The shadow under cards; nil for none. |
let floating: Shadow? | GraineThemeTokens.Shadow? | Classic's floating shadow | The shadow under floating things — the bar, the launcher, sheets; nil for none. |
let hardOffset: Offset? | GraineThemeTokens.Offset? | nil | A solid offset shadow, drawn instead of soft ones by designs that use it; nil for none. |
GraineThemeTokens.Shadow
Read it to draw a soft shadow the way the design does.
public struct GraineThemeTokens.Shadow — one soft shadow, used by GraineThemeTokens.Elevation; lengths in points.
What to expect — Values are held to their ranges; a missing one takes the value of Graine Classic's shadow for that place (the card's or the floating one's, shown as card · floating).
| Field | Type | Default | Meaning |
|---|---|---|---|
let y: Double | Double | 2 · 8 | How far down the shadow falls, -64 to 64. |
let blur: Double | Double | 8 · 24 | How soft it is: the blur radius, 0 to 200. |
let spread: Double | Double | 0 · 0 | How far it grows past the shape, -64 to 64. |
let opacity: Double | Double | 0.08 · 0.16 | How dark it is, 0 to 1, applied to color. |
let color: String | String | "#000000" | The shadow's colour, #RRGGBB. |
GraineThemeTokens.Offset
Read it to draw the solid, offset shadow some designs use instead of a soft one.
public struct GraineThemeTokens.Offset — a hard shadow: the shape repeated behind itself, moved by x and y points, drawn in colors.text. As GraineThemeTokens.elevation.hardOffset.
What to expect — Present only when the design uses one; each value is held to -24 to 24.
| Field | Type | Default | Meaning |
|---|---|---|---|
let x: Double | Double | — | How far right the shadow sits, in points (negative: left). |
let y: Double | Double | — | How far down the shadow sits, in points (negative: up). |
GraineThemeTokens.Motion
Read it to move your own UI at the design's pace.
public struct GraineThemeTokens.Motion — as GraineThemeTokens.motion.
What to expect — Filled in from Graine Classic's when the design does not say: 200 ms, "standard", no bounce. Ask effectiveDurationMs(reduceMotion:) rather than reading durationMs, so Reduce Motion is honoured.
| Field | Type | Default | Meaning |
|---|---|---|---|
let durationMs: Double | Double | 200 | How long a transition runs, 0 to 2,000 milliseconds; 0 means no transitions. |
let easing: String | String | "standard" | The curve: "standard", "emphasized", "spring", "linear" or "none" (no movement); a newer word is drawn as "standard". |
let bounce: Double | Double | 0 | How much a "spring" overshoots, 0 to 1. |
func effectiveDurationMs(reduceMotion: Bool) -> Double | Double | — | The duration to use: 0 when Reduce Motion is on or the easing is "none", else durationMs. See GraineThemeTokens.Motion.effectiveDurationMs. |
GraineThemeTokens.Motion.effectiveDurationMs
Call it to time your own transitions the way the design does, while honouring the system's Reduce Motion.
How long a transition runs, in milliseconds: 0 — no movement — when Reduce Motion is on or the design's easing is "none"; otherwise the design's durationMs (never below 0). The system's setting always wins over the brand's choice.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reduceMotion | Bool | required | Whether Reduce Motion is on — UIAccessibility.isReduceMotionEnabled, or SwiftUI's accessibilityReduceMotion environment value. |
Returns — Double: milliseconds, 0 for no transition. Example value: 200.
Errors — none.
GraineThemeTokens.Icons
Read it to match the design's icon style and the launcher's glyph.
public struct GraineThemeTokens.Icons — as GraineThemeTokens.icons.
What to expect — Filled in from Graine Classic's when the design does not say: outline icons, a 2-point stroke, the waveform glyph. A design that shows the brand's logo on the launcher has no glyph (nil).
| Field | Type | Default | Meaning |
|---|---|---|---|
let style: String | String | "outline" | The style of the SDK's own icons: "outline", "filled", "duotone" or "sharp". |
let stroke: Double | Double | 2 | The line width of icons and stroked glyphs, 0.5 to 4 points. |
let launcher: Glyph? | GraineThemeTokens.Glyph? | the waveform | The launcher's glyph; nil when the launcher shows the brand's logo. |
GraineThemeTokens.Glyph
Read it to draw the brand's launcher glyph yourself, from its path.
public struct GraineThemeTokens.Glyph — one glyph as SVG path data on a 24 × 24 grid, as GraineThemeTokens.icons.launcher.
What to expect — When the design says nothing about its glyph, Graine Classic's "waveform". An app that would rather not draw paths asks launcherGlyphUrl(origin:points:scale:) for an image instead.
| Field | Type | Default | Meaning |
|---|---|---|---|
let id: String | String | "waveform" | The glyph's name, lower-case — what GraineGlyph.url takes. |
let path: String | String | the waveform's path | The drawing: SVG path data on a 24 × 24 grid. |
let stroke: Bool | Bool | true | True: draw it as a line icons.stroke wide; false: fill it. |
GraineThemeTokens.Glass
Read it to draw translucent surfaces the way a glass design does.
public struct GraineThemeTokens.Glass — as GraineThemeTokens.glass, which is nil for an opaque design.
What to expect — Present only for a design with translucent surfaces; a missing value inside it falls back to 20-point blur and 0.72 opacity.
| Field | Type | Default | Meaning |
|---|---|---|---|
let blur: Double | Double | 20 | How much the background behind a translucent surface is blurred, 0 to 200 points. |
let surfaceOpacity: Double | Double | 0.72 | The opacity colors.surface is drawn at over that blur, 0 to 1. |
GraineThemeTokens.launcherGlyphUrl
Call it to get the launcher glyph as an image when you draw your own launcher.
The launcher glyph as an image URL, in colors.onAccent, for a host that draws its own launcher and would rather not parse SVG. points × scale is the physical size, so it is sharp on every screen. Nil when the brand chose its logo.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
origin | String? | required | Where Graine is served — your GraineConfig.baseUrl; https://www.graine.ai when nil or empty. |
points | Double | required | How big you draw it, in points. |
scale | Double | required | The screen's scale (UIScreen.main.scale), so the image is sharp: points × scale pixels. |
Returns — String?. Example value: "…".
Errors — none.
GraineChatTokens
Read it to draw a chat window of your own the way the agent's design does.
How the chat window is drawn — its header, its composer, its words: GraineThemeTokens.chat, nil from an older server. A missing or malformed field falls back on its own, as listed.
What to expect — The chat's framing, as the SDK's own chat window uses it. Public so a chat window of your own can reuse it.
| Field | Type | Default | Meaning |
|---|---|---|---|
let title: String | String | "" | Title shown in the header; when empty, the dashboard's heading is used. |
let subtitle: String | String | "" | Under the title; empty means "Online" or "Connecting…". |
let avatar: String | String | "logo" | The header's mark: logo, glyph or none. |
let placeholder: String | String | "Message…" | The composer's placeholder. |
let showTimestamps: Bool | Bool | false | Show each message's time. |
let suggestions: [String] | [String] | [] | Up to four replies (40 characters each) offered before the customer has said anything. |
let background: String | String | colors.background | Behind the messages. |
let pattern: String | String | "none" | Over the thread: none or dots. |
let patternColor: String | String | colors.border | The dots' colour (unused with no pattern). |
let headerBackground: String | String | colors.surface | The header's fill. |
let headerText: String | String | colors.text | The header's title colour. |
let headerSubtext: String | String | colors.textMuted | The header's subtitle colour. |
let composerBackground: String | String | colors.surfaceRaised | The composer's fill. |
let composerText: String | String | colors.text | The composer's text colour. |
let composerPlaceholder: String | String | colors.textMuted | The placeholder's colour. |
let composerBorder: String | String | colors.border | The composer's outline. |
let size: GraineChatSize | GraineChatSize | .compact | How big the window opens: the dashboard's Window size. |
let showUpcomingCalls: Bool | Bool | true | The card of the customer's booked calls at the top of the chat (Booked calls in the dashboard). |
let upcomingTitle: String | String | "" | That card's title (Card title); empty means "Upcoming call" / "Upcoming calls". |
let showHistory: Bool | Bool | true | The conversation kept from before, on screen (Earlier messages). Off, the agent still has it. |
let newConversation: Bool | Bool | true | The header's compose control (New conversation button). Turning it off does not stop GraineChat.newConversation() from working. |
GraineCallTokens
Read it to draw a call screen of your own the way the agent's design does.
How the SDK's call screen (GraineCallView) is drawn while the app is open — the system's own call screen is the system's: GraineThemeTokens.call, nil from an older server. A missing or malformed field falls back to the screen the SDK drew before designs, as listed.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let background: String | String | "#0E0F11" | The screen's fill (the gradient's top, with backgroundTo). |
let backgroundTo: String? | String? | nil | When set, the background is drawn as a gradient: background at the top, blending into this at the bottom. |
let text: String | String | "#F5F5F4" | The name and the agent's caption. |
let textMuted: String | String | "#A0A5AE" | The status line, button labels and the customer's caption. |
let avatar: String | String | "logo" | logo, glyph or initials; a logo or glyph the brand lacks falls back to the other, then to the initial. |
let avatarShape: String | String | "circle" | circle, squircle or rounded-square. |
let avatarFill: String | String | "#318CE7" | Behind a glyph or initial. |
let avatarText: String | String | "#FFFFFF" | The glyph or initial. |
let buttonFill: String | String | "#333435" | A control's fill. |
let buttonIcon: String | String | "#F5F5F4" | A control's icon. |
let buttonActiveFill: String | String | "#F5F5F4" | A control that is on (muted, the speaker). |
let buttonActiveIcon: String | String | "#0E0F11" | That control's icon. |
let endFill: String | String | "#DC2626" | The end button's fill. |
let endIcon: String | String | "#FFFFFF" | The end button's icon. |
let endShape: String | String | "round" | round or pill. |
let acceptFill: String | String | "#16A34A" | The Answer button's fill. |
let acceptIcon: String | String | "#FFFFFF" | The Answer button's icon. |
let declineFill: String | String | "#DC2626" | The Decline button's fill. |
let declineIcon: String | String | "#FFFFFF" | The Decline button's icon. |
let callbackFill: String | String | "#318CE7" | The Call back button's fill, on a missed call. |
let callbackIcon: String | String | "#FFFFFF" | The Call back button's icon. |
let captions: Bool | Bool | true | Show the line being said. |
let timer: Bool | Bool | true | Show the call's timer. |
let controls: [String] | [String] | ["mute", "speaker", "chat", "end"] | The controls in order, from mute, speaker, chat and end; end is always added. The iOS call screen leaves chat out, because the chat is held during a native call. |
let ringtone: String | String | "system" | system, graine or soft — the system's call screen rings with it when the app sets no ringtoneSound. |
let statusText: StatusText | GraineCallTokens.StatusText | see below | The status words. |
GraineCallTokens.StatusText
Read it for the brand's words on the call screen.
The status words on the SDK's call screen. A value type made by the SDK.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let ringing: String | String | "Incoming call" | While an agent's call rings. |
let connecting: String | String | "Connecting…" | While the conversation opens. |
let ended: String | String | "Call ended" | After a normal end. |
let scheduled: String | String | "Scheduled call" | While a ring Graine sent for a call booked for this customer rings. |
let missed: String | String | "Missed call" | Shown for a ring no one answered; the screen then gives the option to call back. |
GraineLauncherShape
Read it to draw the launcher in the brand's shape.
The floating launcher's outline: GraineThemeTokens.Shape.launcher. Raw values are the wire words.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case circle | Fully round. |
case squircle | Continuous corners, the way iOS draws its own icons. |
case roundedSquare = "rounded-square" | A square with the design's large corner radius. |
case square | A square with the design's small corner radius. |
case pill | 56 points high and as wide as its label. |
GraineBubbleStyle
Read it to draw chat bubbles in the brand's style.
How chat messages sit on the page: GraineThemeTokens.bubbles. Raw values are the wire words.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case filled | Each speaker's messages on their own colour. |
case outline | Both transparent and outlined: the customer's in the accent, the agent's in the border colour. |
case minimal | Agent messages show as plain text on the page, while the customer's sit on the soft accent. |
GraineDesignChrome
Read it to frame windows the way the brand chose.
How a surface that holds content is framed: GraineThemeTokens.chrome. A value this build does not know, or none at all, is none.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case none | A plain surface. |
case window | The chat window, the caption card and every component card open as desktop windows: a title bar with window controls and a monospaced, lower-case title. |
GraineGlyph.url
Call it to get any glyph as an image URL, in a colour and size you choose.
The image URL for glyph id in color. origin: where Graine is served (https://www.graine.ai when empty). sizePx: physical pixels (points × screen scale), held to 16–512. stroke: held to 0.5–4, in quarter steps.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
origin | String? | required | Where Graine is served — your GraineConfig.baseUrl; https://www.graine.ai when nil or empty. |
id | String | required | The glyph's id, such as GraineThemeTokens.Glyph.id ("waveform"). |
color | String | required | The colour to draw it in, #RRGGBB (white when it is not a colour). |
sizePx | Double | required | Physical pixels — points × the screen's scale — held to 16–512. |
stroke | Double | 2 | The stroke width for a stroked glyph, held to 0.5–4 in quarter steps. |
Returns — String. Example value: "…".
Errors — none.
GraineGlyph
Use it to turn a glyph into an image URL.
The launcher glyph as an image URL, for an app that cannot draw SVG path data. Static members, safe from any thread.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Property | Type | Meaning |
|---|---|---|
static let assetVersion = 2 | Int | Raised by Graine whenever a glyph's drawing changes, so a long-cached image never shows an old one. |
Its functions: url — each with its parameters, response, errors and an example.
GraineNativeAppearance
Read it only when you draw from the dashboard's appearance settings, without a design.
The dashboard's appearance settings, translated for a phone. Used by the SDK's own views; apps do not need it.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let accent: String | String | — | Always a valid #rgb or #rrggbb. |
let heading: String | String | — | The heading of the panel, such as "Talk to our AI assistant" — this is not the assistant's name. |
let logoUrl: String? | String? | — | The brand's logo as an absolute URL, or nil when none is set. |
let logoPlate: GraineLogoPlate | GraineLogoPlate | — | The backing behind the logo when the logo replaces the launcher glyph. |
let radius: Double | Double | — | Corner rounding, clamped to what the view draws. |
GraineLogoPlate
Read it to place the brand's logo the way the dashboard chose.
What the brand's logo sits on wherever it stands in for the launcher glyph — the launcher, the bar's avatar, the chat header. Raw values are the wire words.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Case | Meaning |
|---|---|
case accent | The shape's own accent fill — for a white or light mark. The default. |
case white | White with a hairline border — for a coloured or dark logo, or an opaque image. |
GraineWidgetPalette
Read it only to colour components when the agent has no design.
The colours a component is drawn in when the agent has no design, as #RRGGBB. Used by the SDK's own views; apps do not need it.
What to expect — Resolved from the agent's design in the Garage when the agent's settings load; before that, and from an older server, every field holds its default. A missing or malformed value falls back on its own; reading these never throws. Most apps never need them: the SDK's own views draw from them.
| Field | Type | Default | Meaning |
|---|---|---|---|
let surface: String | String | — | The card's fill. |
let border: String | String | — | Hairlines. |
let text: String | String | — | Primary text. |
let muted: String | String | — | Secondary text. |
let onAccent: String | String | — | Picked by luminance to read clearly on the accent, so even a yellow accent gets a legible label. |
let mode: String | String | — | Either "light" or "dark", once "auto" has been resolved using the device's setting. |
Going live
What to check before your app ships: the logs to turn on while you build, the privacy details the SDK brings, the version to quote when you ask for help, and the limits a busy app can meet.
Debug logging
Turn it on in debug builds to see exactly what the SDK did and what Graine answered — "the agent did not speak" is answered in one read of this log.
With GraineConfig.debug on, the SDK logs every message between itself and its engine page, both ways, and the page's own console; on native calls it also logs CallKit activating and deactivating the call's audio. The log is under the subsystem ai.graine.inapp — filter on it in Xcode's console or the Console app. Variables are logged as their keys only and the screen as its name only — never a value. The engine page can also be inspected in Safari's Web Inspector (iOS 16.4 and later), only while debug is on. Warnings and errors — a call that could not connect, a device that could not be registered for calls, an action the agent declares that this build does not register — are logged whether debug is on or not. Turn debug off in release builds.
Privacy manifest
Read it to fill in your App Store privacy details with what the SDK itself does.
The SDK ships a privacy manifest (PrivacyInfo.xcprivacy; with CocoaPods, inside the GraineInApp_Privacy bundle) that Xcode adds to your app's privacy report. It declares the required-reason APIs the SDK calls — system boot time (reason 35F9.1, for timing) and UserDefaults (reason CA92.1, for its own settings) — no tracking and no tracking domains. The optional WebRTC package ships its own (with CocoaPods, inside the GraineInAppRTC_Privacy bundle): system boot time (reason 35F9.1), no tracking. What your app collects through the agent — the customer's voice, what they type, the screens you describe — belongs in your App Privacy details; identifiers in what you describe are masked on the device first (GraineMasking).
Before you ship
Go through it once before release, so the first customer's call works.
- The publishable key is the live agent's (
pk_live_…), andapp://is in that agent's Allowed domains — without it every request is refused andonErrorsaysno_originororigin_not_allowed. NSMicrophoneUsageDescriptionis inInfo.plist; for calls, Push Notifications and theaudioandvoipbackground modes are on (Permissions and capabilities).calls.enableVoIPPushes()runs at every launch, inapplication(_:didFinishLaunchingWithOptions:), when your agent's calls ring this app.checkActions()logs nounhandledaction: every action declared on the agent is registered.identifyruns at sign-in andidentify(nil)at sign-out, so memory and bookings follow the right customer.GraineConfig.debugis off in release builds.- You log
GraineVersion.namewith your app's own version, for support. - With WebRTC calls:
GraineInAppRTCis the same version asGraineInApp, and itsTHIRD-PARTY-NOTICES.mdis in your app's open-source notices.
GraineVersion
Read it to log or report which SDK version your app ships.
The SDK's version, sent with the SDK's requests so the call record tells builds apart.
What to expect — A constant: "0.10.3" in this release. Include it when you ask for support. The WebRTC package's GraineRTCVersion.name is always the same.
| Field | Type | Default | Meaning |
|---|---|---|---|
static let name: String | String | "0.9.0" | This build's version — read it to log or show which SDK your app ships. |
Rate limits and quotas
Read it to know the limits a busy app can meet, and what happens past them.
| Limit | Value | Where it applies |
|---|---|---|
| Bookings from one install | 10 an hour | scheduleCall, and the agent's bookings in that install's conversations |
| Bookings in a workspace | 1,000 an hour | All in-app bookings |
| Calls waiting per customer | 3 by default (1–10) | At most this many upcoming calls per user |
| Devices per customer | 2 by default (1–5) | Devices per user; idle devices free their place after 30 days |
| Rings per customer per day | 3 by default (1–10) | Ring each user at most, in the customer's own day |
| Quiet period after a decline | 6 hours | No ring for that customer |
| Notice and horizon | 5 minutes (2–1440) · 14 days (1–30) | With at least this much notice · Booked at most this far ahead |
| Ring time · re-ring | 45 s (20–60) · once after 10 min (0–120) | Ring for · After a missed call, ring once more after |
| Lateness | 10 minutes | A booked call more than 10 minutes late is not rung — also one held while the workspace was out of credit or the agent was off |
| Booking reason · variables | 120 characters · 12 variables, about 1 KB | Bookings and calls |
| Memories listed | At most 100, newest first | memories() |
| Conversation kept | 50 messages, 7 days after the last | Conversations continue (since 0.7.0) |
| Requests per key and address, per minute | Starting a conversation 30 (and 300 per key); high-quality audio sessions 10 (120 per key); booking, listing, cancelling, device registration, memory list and sign-out 30 (300 per key); call outcomes 60 (600 per key); forgetting memory 10 (120 per key); listing memories also 30 per install | Answered rate_limited with Retry-After |
| Action handlers · component data | 6 seconds (5.5 in the chat and on a native call) · 8 seconds | After that, the agent is told the action did not finish; a component shows its authored content |
Events and listeners
What the agent tells your app as it happens. Add a GraineListener to an agent; implement only the callbacks you need. Calls have their own listener — see GraineCallsListener under Calls the user places.
GraineInApp.addListener
Call it to hear what the agent reports; keep a reference to the listener.
Hear everything this agent reports. Held weakly — keep your own reference. Adding the same listener twice has no effect. Callbacks arrive on the main thread.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | GraineListener | required | Your listener; held weakly, so keep your own reference to it. Adding it twice has no effect. |
Returns — nothing; it returns when done.
Errors — none.
GraineInApp.removeListener
Call it when a listener should stop hearing the agent.
Stops reporting to that listener.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | GraineListener | required | The listener to stop telling. |
Returns — nothing; it returns when done.
Errors — none.
GraineListener
Implement it to hear everything an agent reports.
Everything an agent reports, added with GraineInApp.addListener(_:); every method has a default that does nothing, so implement only what you use. Called on the main thread; listeners are held weakly, so keep your own reference.
What to expect — Implement only the callbacks you need; the others do nothing. Called on the main thread.
| Property | Type | Meaning |
|---|---|---|
extension GraineListener | — | An empty default for every callback, so your listener implements only the ones it needs. |
Its functions: onSessionReady, onCallState, onCaption, onWidget, onWidgetPending, onEnded, onMicDenied, onQuality, onNetwork, onReconnecting, onTransport, onConnectivity, onActionRequested, onActionCompleted, onFriction, onTheme, onChatMessage, onError — each with its parameters, response, errors and an example.
GraineListener.onSessionReady
Implement it to act once the agent's settings have loaded — to read bookingAvailable or the design, say.
The agent's settings loaded (once per launch). GraineInApp.theme is already up to date when this is called.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
session | GraineSessionConfig | required | The agent's settings, the same as GraineInApp.session from now on. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onCallState
Implement it to follow the in-app call connecting and ending.
The in-app call connected, started connecting, or stopped. Called only when either value changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
connected | Bool | required | The in-app call is up. |
connecting | Bool | required | The call is being placed or re-established. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onCaption
Implement it to show the words being said on the in-app call.
A line being said on the in-app call, again as it fills in; live is true while it is still being recognised or spoken.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | GraineRole | required | Who said it: the agent or the customer. |
text | String | required | The words of the line so far; a live line arrives again, longer, as it fills in. |
live | Bool | required | The words are still arriving. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onWidget
Implement it to draw the agent's components yourself.
The agent drew a component, in a call or in the chat (also when reopenComponent() puts one back). Mount GraineWidgetView to draw it, or draw it yourself and answer with GraineInApp.answerComponent(widgetId:data:summary:); GraineWidget(frame:) reads the map.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widget | [String: Any] | required | The component as a map — widget_id, kind, data, fallback_text, … — which GraineWidget(frame:) reads for you. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onWidgetPending
Implement it to show a loader while a component's content loads.
A component is on its way and its content is loading: draw your loader.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hint | [String: Any] | required | What is known of the coming component — its kind, such as "product_list" — so your loader can take its shape. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onEnded
Implement it to react when the in-app call is over.
The in-app call is over, however it ended. Once per call.
Parameters — none.
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onMicDenied
Implement it to guide the customer to Settings when the microphone is refused.
The microphone was refused — NotAllowedError when the permission is denied. Also called when a native call (calls) could not get the microphone.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | String | required | "NotAllowedError" when the permission is denied, "NotFoundError" when there is no usable microphone. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onQuality
Implement it to show the connection's quality.
The connection's quality, in one word. Called only when the word changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
quality | GraineConnectionQuality | required | The connection in one word: excellent, good, poor, lost or unknown. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onNetwork
Implement it to show that the network went or came back.
The engine page saw the network go or come back.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
online | Bool | required | True when the network came back, false when it went. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onReconnecting
Implement it to show that the call is being re-established.
The network interrupted the call and it is being re-established (true), or that is over (false). Not an end.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
active | Bool | required | True while the call is being re-established; false once it is back (or over). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onTransport
Implement it to log what carries the call's audio, and why.
What carries the call's audio, and why it is not the transport asked for. A reason of not_configured is a setting that never fixes itself, so it is also reported through onError.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
transport | GraineTransport | required | What carries the call's audio now. |
requested | GraineTransport | required | What was asked for — GraineConfig.transport, else the dashboard's Call audio setting. |
reason | String? | required | Why the two differ, as a code — not_configured means high-quality audio is not set up for the workspace; nil when they match. |
message | String? | required | The same reason in a sentence, for your logs; nil when there is none. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onConnectivity
Implement it to show whether the engine page is loaded, and offer a retry when it failed.
The engine page loading, ready, retrying (with the reload attempt and why) or failed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | GraineConnectivity | required | loading, ready, retrying or failed. |
attempt | Int | required | How many reloads have been scheduled so far: 0 for the first load, then 1, 2, … up to the length of GraineConfig.Timing.retryDelaysMs. |
reason | String? | required | Why a load is being retried or failed, for your logs; nil otherwise. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onActionRequested
Implement it to see when the agent asks for one of your actions.
The agent asked for one of your actions.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name, as registered with registerAction and declared on the agent. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onActionCompleted
Implement it to see the answer the agent got for an action.
The answer the agent received for that action — your handler's, or the SDK's: refused ("This app build does not implement …") for an action with no handler, error for a throw or a handler that ran out of time.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name. |
result | GraineActionResult | required | The answer the agent received: ok, refused or error, with its message and data. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onFriction
Implement it to react in your UI when the customer looks stuck.
The customer looks stuck: on one screen, with a field outstanding, through a stretch of a live conversation. The agent is told right after; this is for the app — badge a control, count it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
signal | GraineFrictionSignal | required | What looks stuck: the screen, the outstanding field and how long, in detail and data. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onTheme
Implement it to restyle your own UI when the brand's tokens arrive or the device switches between light and dark.
The brand's design tokens for the device's current appearance — when the agent's settings arrive, and again when the device switches between light and dark. Never called when the agent has no design.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
tokens | GraineThemeTokens? | required | The token set to draw now — the same as GraineInApp.theme. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onChatMessage
Implement it to badge or open your chat when the agent sends a message.
The agent finished a message in the chat. turn.proactive is true when it spoke up without being asked — open or badge your chat for those if the customer is not looking at it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turn | GraineChatTurn | required | The agent's finished message: its text, when it was said, and proactive. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
GraineListener.onError
Implement it to catch the configuration mistakes your team must fix.
Something your team must fix — a rejected key, an agent that does not accept app://, an engine page that could not load. Meant for you, never shown to the customer.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | What is wrong, in a sentence for your team — never shown to the customer. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens, with the parameters above as its payload.
Errors — none.
Errors
A method that asks Graine on the customer's behalf fails with one error type, GraineServiceError, carrying a stable code. Branch on the code; show the message, which is written for the customer. Errors never carry the identifier, traits or a push token.
Error codes and their fixes
Read it to branch on a refusal and fix its cause.
| Code | In this SDK | HTTP | Means | Raised by | Fix |
|---|---|---|---|---|---|
invalid_key | invalidKey | 401 | The publishable key is not one, or belongs to no agent | Every request to Graine | Copy the key again from Agent → Embed & Widgets → Install |
origin_refused | originRefused | 403 | The app or site is not allowed: app:// (apps) or the site's host is missing from Allowed domains | Every request | Add app://, or the exact host |
rate_limited | rateLimited | 429 | Too many requests from this install, key or address | Every request | Wait retryAfterSeconds (the Retry-After header), then retry |
invalid_request | invalidRequest | 400 | A field is missing or malformed — a time without its offset, a reason over 120 characters | scheduleCall, device registration | Fix the request (Flutter refuses a long reason on the device) |
identity_required | identityRequired | 400 | No customer is identified, or the identifier is a placeholder | memories, forgetMemory, bookings that need a customer | identify() with the identifier first |
device_limit | deviceLimit | 403 | This install is not one of the customer's allowed devices | memories, forgetMemory, newConversation, scheduleCall | Sign out on another device, raise Devices per user, or Reset devices for the customer |
scheduling_off | schedulingOff | 403 (0 on the device) | Users, from your app is off | scheduleCall | Turn it on, or hide booking when usersCanBook is false |
too_soon | tooSoon | 400 (0 on the device) | Earlier than earliest(now) | scheduleCall | Offer times from earliest(now) |
too_far | tooFar | 400 (0 on the device) | Later than latest(now) | scheduleCall | Offer times up to latest(now) |
outside_window | outsideWindow | 400 | Outside the agent's Calling Window (or, for agent and team bookings, inside quiet hours), in the customer's time zone | scheduleCall, agent and dashboard bookings | Show the message (it names a time that works); offer another time |
too_many_pending | tooManyPending | 409 | The customer already has the most calls waiting | scheduleCall | Cancel one first |
not_found | notFound | 404 | No such booking for this customer or install | cancelScheduledCall | Refresh scheduledCalls() |
unavailable | unavailable | 503 (504 on a timeout) | Graine could not answer just now — including a booking refused because the agent is switched off or out of credit | Every request | Retry with backoff; check the agent is on and the workspace has credit |
network | network | — | No answer came back: offline, or the connection dropped | Every request | Retry when online |
unknown | unknown(raw) | other | A code newer than the SDK, or an unexpected answer (carrying its word) | Every request | Log the code and message; show the message |
When Graine gives no words of its own, message is the SDK's: "Graine could not be reached. Check the connection and try again." for network, or "Graine refused the request (status)." with the HTTP status. A baseUrl that is not a URL is invalid_request: baseUrl "…" is not a URL.
When the bar or chat cannot start, the reason arrives through onError (and React Native's launcher error), not as a coded exception: key_missing, key_malformed (publishable keys start with pk_live_), key_unknown (401 — copy the key again); no_origin (403 — add app://); no_domains_configured (403 — the list is empty); origin_not_allowed (403 — add the exact host); not_configured (high-quality audio is not enabled for the workspace; calls use the standard path). A refused microphone arrives as onMicDenied("NotAllowedError") (or NotFoundError for no usable microphone); React Native and Flutter calls configured wrongly reject with invalid_options, naming the option.
GraineServiceError (constructor)
Use it to build errors for tests of your own error handling.
Builds an error by hand — for tests and previews of your own error handling; the SDK throws the real ones.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
code | GraineErrorCode | required | The stable code to branch on, such as .tooSoon. |
message | String | required | The sentence message will carry — fit to show the customer. |
status | Int | 0 | The HTTP status to pretend; 0 for a refusal made on the device or no answer. |
retryAfterSeconds | Int? | nil | For .rateLimited: the seconds to wait before trying again; nil when not said. |
Returns — a new GraineServiceError.
Errors — none.
GraineServiceError
Catch it from every method that asks Graine, and branch on its code.
What the methods that ask Graine throw — memories(), forgetMemory(), scheduleCall, scheduledCalls() and cancelScheduledCall: a refusal from Graine, or a request that got no answer. Branch on code; message is a sentence fit to show.
What to expect — Thrown or passed by every method that asks Graine. Branch on code, and show message — a sentence fit for the customer that never carries the identifier, traits or a push token. retryAfterSeconds comes with rate_limited when Graine said how long. A refusal made on the device before any request has status 0.
| Field | Type | Default | Meaning |
|---|---|---|---|
let code: GraineErrorCode | GraineErrorCode | required | Why — the stable part to branch on. |
let message: String | String | required | A sentence fit to show the customer: Graine's own words when it gave some, else the SDK's — "Graine could not be reached. Check the connection and try again." for network, "Graine refused the request (status)." otherwise. |
let retryAfterSeconds: Int? | Int? | nil | For rateLimited: the seconds Graine asked to wait, when it said. |
let status: Int | Int | 0 | The HTTP status; 0 when no answer came back. |
var errorDescription: String? { get } | String? | — | The same as message, so localizedDescription shows it. |
Its functions: init — each with its parameters, response, errors and an example.
GraineErrorCode (constructor)
Use it to turn an error word into a GraineErrorCode.
The code for a wire word (trimmed, any case); unknown for any other word.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | An error word as Graine sends it — too_soon, device_limit, … |
Returns — a new GraineErrorCode: .unknown(word) for a word this build does not know.
Errors — none.
GraineErrorCode
Branch on it to handle each kind of refusal.
Why Graine refused a request, as a stable code; each case has its wire word. A word this build does not know is unknown, carrying it.
What to expect — invalid_key · origin_refused · rate_limited · invalid_request · identity_required · device_limit · scheduling_off · too_soon · too_far · outside_window · too_many_pending · not_found · unavailable · network (no answer came back) · unknown, which carries a code newer than this SDK. The error codes section says when each happens and how to fix it.
| Field | Type | Default | Meaning |
|---|---|---|---|
var wire: String { get } | String | — | The word on the wire: too_soon, …; an unknown code's own word. |
var description: String { get } | String | — | The same as wire, for printing and logs. |
| Case | Meaning |
|---|---|
case invalidKey | Wire invalid_key. The publishable key is not one, or belongs to no agent (HTTP 401). |
case originRefused | Wire origin_refused. The app is not allowed: app:// is missing from the agent's Allowed domains (HTTP 403). |
case rateLimited | Wire rate_limited. Too many requests; wait retryAfterSeconds when Graine said how long (HTTP 429). |
case invalidRequest | Wire invalid_request. A field is missing or malformed (HTTP 400 or 422), or baseUrl is not a URL. |
case identityRequired | Wire identity_required. No customer is identified, or the identifier is a placeholder. |
case deviceLimit | Wire device_limit. This install is not one of the customer's allowed devices. |
case schedulingOff | Wire scheduling_off. Customers may not book calls with this agent; checked on the device first. |
case tooSoon | Wire too_soon. Earlier than schedulingRules.earliest(now:); checked on the device first. |
case tooFar | Wire too_far. Later than schedulingRules.latest(now:); checked on the device first. |
case outsideWindow | Wire outside_window. Outside the agent's calling hours, in this device's time zone. |
case tooManyPending | Wire too_many_pending. The customer already has the most calls waiting. |
case notFound | Wire not_found. No such booking for this customer or install (HTTP 404). |
case unavailable | Wire unavailable. Graine could not answer just now (HTTP 503); try again with backoff. |
case network | Wire network. No answer came back: offline, or the connection dropped. |
case unknown(String) | A code this build does not know, or an unexpected answer, carrying its word — for example http_500. |
Its functions: init — each with its parameters, response, errors and an example.
GraineCallsHostError
Read it only if you maintain a React Native or Flutter bridge.
Why GraineCallsHost.configure(_:) refused its options. Used by the React Native and Flutter bridges; native iOS apps do not see it.
What to expect — Thrown only by configure, before anything changes: the options already in force stay. The message names the option that is wrong — fix the code that builds the options.
| Field | Type | Default | Meaning |
|---|---|---|---|
let code: String | String | — | "invalid_options". |
let message: String | String | — | A sentence for the developer naming the option that is wrong, such as callScreen must be "sdk" or "app". |
var errorDescription: String? { get } | String? | — | The same as message, so localizedDescription shows it. |
Lower-level building blocks
Public so a bridge for another framework, or a fully custom UI, can reuse them; an integration does not need them. The CallKit and PushKit callbacks are here too: the system calls them, never your app.
GraineCallsHost
Use it only if you build a bridge for another framework; native apps use GraineInApp.shared.calls.
The bridge the React Native and Flutter packages use to drive GraineCalls with JSON-safe values (strings, numbers, booleans, null, lists and string-keyed maps); apps on native iOS do not call it — GraineInApp.shared.calls is the Swift API. Reached as GraineCallsHost.shared; it runs on the main thread, and configure, restore, snapshot, startCall, installId and addSink wait for their answer when called from another thread.
What to expect — A bridge calls restore() first at every launch, then configure once its own code runs; the options are kept for the next cold start, and every event goes to the sinks it adds — outcomes and rings that came while nothing listened (the last 20) go to the first sink.
| Property | Type | Meaning |
|---|---|---|
@objc static let shared = GraineCallsHost() | GraineCallsHost | The one host per process. Used by the React Native and Flutter bridges. |
typealias Sink = (_ event: String, _ payload: [String: Any]) -> Void | — | Receives every event on the main thread: its name — state (the snapshot, at most once per turn of the main loop), outcome, incoming, voipToken or action — and its payload. |
Its functions: restore, configure, snapshot, enableVoIPPushes, handlePush, startCall, answer, decline, end, setMuted, setSpeaker, setIdentity, setMemory, installId, setVariables, setScreen, setActions, resolveAction, addSink, removeSink — each with its parameters, response, errors and an example. Permissions and the setup check, for a bridge, are below.
GraineCallsHost: permissions and the setup check
Use them only from a bridge: GraineCalls' permissions and setup check, as JSON-safe values.
| Method | What it does |
|---|---|
GraineCallsHost.callPermissions() | {microphone, notifications, fullScreen}, each granted, denied, blocked or notNeeded — readable before configure. |
GraineCallsHost.requestCallPermissions(_:) | Asks for what the system can still ask for (the microphone), when the bridge's app chooses; done gets the same map after, on the main thread. |
GraineCallsHost.openCallSettings(_:) | Opens Settings for microphone, notifications or fullScreen (full_screen and any case too); false for any other word. |
GraineCallsHost.callSetupProblems() | [{code, message}]: what the app's setup still lacks — empty when nothing is, and before configure. |
Returns — as above. Errors — none.
GraineCallsHost.restore
Call it first thing at launch, from a bridge, so a VoIP push that woke the app rings.
At launch, before any JavaScript or Dart runs: applies the options the last configure kept, and enables VoIP pushes again if they were, so a push that woke a terminated app rings. True when something was restored; false when nothing was kept or this launch is already configured.
Parameters — none.
Returns — Bool. Example value: true.
Errors — none.
GraineCallsHost.configure
Call it from a bridge to configure calls with JSON-safe options.
Configures the calls and keeps the options for the next cold start; the first call makes the SDK instance. Options: publishableKey and displayName (required), baseUrl, ringtoneSound, ringTimeoutMs (30,000 by default), appVersion, callScreen ("sdk" or "app"), callTransport ("auto" — the default — "websocket" or "webrtc": GraineCallsConfig.callTransport), debug, android, ios (iconTemplate, includesCallsInRecents) and agents (more agents by name, each {publishableKey, baseUrl?}). Unknown keys are ignored; a known key of the wrong type is refused, naming it. A new key, base URL or debug setting takes effect at the next launch.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options | [String: Any]? | required | The options as a map: the keys above; unknown keys are ignored. |
Returns — nothing; it returns when done.
Errors — GraineCallsHostError: invalid_options
GraineCallsHost.snapshot
Call it from a bridge to draw the call screen from one map.
The call as a call screen draws it, every key always present: state, callId, direction, reason, callerName, muted, speakerOn, agentSpeaking, caption, connectedAt (milliseconds since the epoch), error, agent, transport ("webrtc" or "websocket" once decided, else null) and transportFallbackReason (GraineCalls.transportFallbackReason, else null).
Parameters — none.
Returns — [String: Any].
Errors — none.
GraineCallsHost.enableVoIPPushes
Call it from a bridge to receive the agent's calls as VoIP pushes.
Turns on VoIP pushes for the agent's calls, both now and on each launch that follows. The token arrives as a voipToken event — again on every call, the last one known.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.handlePush
You do not need it on iOS: it exists for the Android side of a bridge.
Always false on iOS, where a VoIP push reaches PushKit and the SDK directly. It exists for the Android side of the bridges.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
data | [String: Any]? | required | The push's data, exactly as the messaging service received it. |
Returns — Bool. Example value: false.
Errors — none.
GraineCallsHost.startCall
Call it from a bridge when the customer calls the agent.
Starts the customer's call to the agent; the options are {reason?, variables?, agent?}. Returns the call's id — or nil if it is not configured, a call is already live, or no agent goes by that name.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
options | [String: Any]? | required | { reason, variables, agent }: why the customer calls (the agent hears it as call_reason), prompt variables for this call, and the configured agent's name (the default agent when left out). |
Returns — String?. Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — none.
GraineCallsHost.answer
Call it from a bridge to answer the ringing call.
Answers the ringing call.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.decline
Call it from a bridge to decline the ringing call.
Declines the ringing call (on iOS, end while it rings); does nothing otherwise.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.end
Call it from a bridge to hang up.
Hangs up.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setMuted
Call it from a bridge's mute control.
Mutes or unmutes the customer.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
muted | Bool | required | true to stop sending the customer's voice; false to send it again. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setSpeaker
Call it from a bridge's speaker control.
The loudspeaker or the earpiece.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Bool | required | true for the loudspeaker; false for the earpiece or a headset. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setIdentity
Call it from a bridge when the signed-in customer changes.
Tells the SDK who is signed in, as GraineInApp.identify does; pass nil when they sign out. Held until the instance exists.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
traits | [String: Any]? | required | Who the customer is (see identify). |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setMemory
Call it from a bridge when the customer changes their memory choice.
Passes on the current customer's choice about memory, as GraineInApp.setMemory does. Held until the instance exists.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | Bool | required | true to remember this customer's conversations; false to stop. Kept on the device for them. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.installId
Call it from a bridge to send the same install id the native calls send.
This install's id, kept in the Keychain — the one the native calls send, so a device is one install whichever layer speaks. Nil before configure or restore.
Parameters — none.
Returns — String?. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none.
GraineCallsHost.setVariables
Call it from a bridge with the whole map of variables, each time it changes.
The variables set while the app runs become exactly this map: a key the last map set and this one leaves out is removed. Reaches every agent; masked on the device, as GraineInApp.setVariables masks them.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
values | [String: Any]? | required | Every prompt variable the app sets, by name — the whole map each time; nil removes them all. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setScreen
Call it from a bridge to describe the screen.
The current screen in the form {screen, title?, journey?, fields?, error?, data?, availableActions?}; pass nil, or leave out the screen name, when there is none.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screen | [String: Any]? | required | What the customer can see now: the screen's name and title, its fields and where they are in a journey. nil / null clears it. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.setActions
Call it from a bridge to say which actions it answers.
The actions the caller answers, and its sink's token: each request for one is sent as an action event to the sink that last listed a non-empty set, and waits for resolveAction within the SDK's six seconds. Names no longer listed are unregistered.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
names | [String] | required | The action names the bridge answers; a name left out of a later list is unregistered. |
sink | Int? | required | The token addSink returned for the sink that answers these actions. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.resolveAction
Call it from a bridge to answer an action request.
Replies to an action request with {status: "ok" | "refused" | "error", message?, data?}. The first answer wins.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
requestId | String | required | The requestId of the action event being answered. |
result | [String: Any]? | required | The answer: status ("ok", "refused" or "error"), an optional message for the agent and optional data. |
Returns — nothing; it returns when done.
Errors — none.
GraineCallsHost.addSink
Call it from a bridge to start receiving call events.
Starts receiving events; returns the sink's token. Outcomes and rings that came while nothing listened (the last 20) are delivered to the first sink at once.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sink | Sink | required | The function that receives every event on the main thread: its name and its payload. |
Returns — Int. Example value: 3.
Errors — none.
GraineCallsHost.removeSink
Call it from a bridge to stop receiving call events.
Stops that sink. Action requests it had not answered are refused.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
token | Int | required | The token addSink returned for that sink. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.providerDidReset
You never call it: CallKit calls it when the system resets every call.
CallKit's callback when the system resets every call; the SDK ends its call — a ring is reported .missed, a call still connecting .failed("The system ended the call."), a call under way .ended(.failed). Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when a call is being placed.
CallKit's callback for a call being placed; the SDK opens the conversation. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
perform action | CXStartCallAction | required | The call CallKit asks the SDK to place. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when the customer answers.
CallKit's callback for an answer; the SDK answers at once and settles the microphone after. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
perform action | CXAnswerCallAction | required | The ring CallKit asks the SDK to answer. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when the customer hangs up or declines.
CallKit's callback for a hang-up or decline. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
perform action | CXEndCallAction | required | The call CallKit asks the SDK to end or decline. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when the customer mutes on the system's call screen.
CallKit's callback for mute from the system's call screen. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
perform action | CXSetMutedCallAction | required | The mute or unmute CallKit asks for. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when the system asks to put the call on hold.
CallKit's callback for hold; an agent call is never put on hold, so the action fails and the system offers "End & Accept" instead. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
perform action | CXSetHeldCallAction | required | The hold CallKit asks for; always refused. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when it hands the call's audio to the app.
CallKit's callback when the system hands over the audio; the call's audio starts here, and a conversation that was waiting for its audio is connected (state → active). With GraineConfig.debug on, it is logged. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
didActivate audioSession | AVAudioSession | required | The call's audio session, as CallKit hands it over. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when it takes the call's audio back.
CallKit's callback when the system takes the audio back; the microphone and speaker are released, and the app's own audio session is restored after the call. With GraineConfig.debug on, it is logged. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
didDeactivate audioSession | AVAudioSession | required | The call's audio session, as CallKit takes it back. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.provider
You never call it: CallKit calls it when one of its actions timed out.
CallKit's callback for an action that timed out; logged. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
provider | CXProvider | required | The SDK's own CallKit provider. |
timedOutPerforming action | CXAction | required | The action that timed out; logged. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.pushRegistry
You never call it: PushKit calls it with a new VoIP token.
PushKit's callback with a new VoIP token, handled as described for enableVoIPPushes. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
registry | PKPushRegistry | required | The SDK's own push registry, made by enableVoIPPushes. |
didUpdate pushCredentials | PKPushCredentials | required | The push credentials PushKit handed over. |
for type | PKPushType | required | The push type; only .voIP is handled, anything else is ignored. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.pushRegistry
You never call it: PushKit calls it when the VoIP token is invalidated.
PushKit's callback when the VoIP token is invalidated; onToken gets nil. Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
registry | PKPushRegistry | required | The SDK's own push registry, made by enableVoIPPushes. |
didInvalidatePushTokenFor type | PKPushType | required | The push type whose token iOS invalidated; only .voIP is handled. |
Returns — nothing; it returns when done.
Errors — none.
GraineCalls.pushRegistry
You never call it: PushKit calls it with each VoIP push.
PushKit's callback with a VoIP push, passed to reportIncomingCall(payload:completion:). Apps do not call it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
registry | PKPushRegistry | required | The SDK's own push registry, made by enableVoIPPushes. |
didReceiveIncomingPushWith payload | PKPushPayload | required | The VoIP push; its dictionaryPayload is what rings. |
for type | PKPushType | required | The push type; only .voIP is handled, anything else is ignored. |
completion | () -> Void | required | PushKit's completion handler, called once the push has been reported to CallKit. |
Returns — nothing; it returns when done.
Errors — none.
GraineChatViewController.viewDidLoad
You never call it: UIKit calls it to build the chat screen.
UIKit's lifecycle callback, which builds the chat screen. Apps do not call it.
Parameters — none.
Returns — nothing; it returns when done.
Errors — none.
GraineProtocol
Read it only if you host the engine page yourself; the SDK uses it for you.
The message names the SDK and Graine's engine page exchange, and the helpers that build and read those messages. Used by the SDK itself; apps do not need it.
What to expect — The SDK sends and reads these for you; they are public so a custom UI can reuse them.
| Property | Type | Meaning |
|---|---|---|
enum Out | nested type | The names of the messages the SDK sends into the page — see GraineProtocol.Out. |
enum In | nested type | The names of the messages the page sends out — see GraineProtocol.In. |
static let bridgeName = "ReactNativeWebView" | String | The name of the bridge object the engine page looks for, so one page serves every platform. |
static let messageHandlerName = "graine" | String | The web view message handler the bridge object forwards to. |
static let actionDeadlineMs = 6_000 | Int | The longest an action handler can run before the agent learns the action did not happen. |
Its functions: engineUrl, deliver, out, parse — each with its parameters, response, errors and an example.
GraineProtocol.Out
Read it only if you host the engine page yourself: the names of the messages the SDK sends into it.
public enum GraineProtocol.Out — each message type as a static let constant. Used by the SDK itself; an app does not need them.
What to expect — Fixed strings, the same on every platform. Build a message with GraineProtocol.out(_:_:).
| Constant | Value | Meaning |
|---|---|---|
static let startCall | "graine:start-call" | Place the in-app call. |
static let endCall | "graine:end-call" | Hang up. |
static let setMuted | "graine:set-muted" | Stop or resume sending the customer's voice: muted. |
static let warm | "graine:warm" | Prepare the connection while idle, so a later start connects at once. |
static let variables | "graine:variables" | The prompt variables, whenever they change: variables. |
static let appContext | "graine:app-context" | What the customer is looking at: the current screen, its fields and recent events. |
static let appActionResult | "graine:app-action-result" | The app's answer to an action the agent asked for. |
static let appEvent | "graine:app-event" | Something the agent may speak up about (GraineVoice.reportEvent): name, data. |
static let widgetResult | "graine:widget-result" | The customer's answer to a component (GraineVoice.answerWidget): widgetId, data, summary. |
static let identity | "graine:identity" | Who the customer is for memory, and their memory choice — used from the next conversation. |
GraineProtocol.In
Read it only if you host the engine page yourself: the names of the messages the page sends out.
public enum GraineProtocol.In — each message type as a static let constant. GraineProtocol.parse(_:) turns a message into a PageMessage.
What to expect — Fixed strings, the same on every platform; a message of any other type is ignored.
| Constant | Value | Meaning |
|---|---|---|
static let ready | "graine:ready" | The page has loaded and is ready for a call. |
static let call | "graine:call" | The call's state changed: connected, connecting. |
static let muted | "graine:muted" | The customer was muted or unmuted: muted. |
static let caption | "graine:caption" | A line being said: role, text, live. |
static let widget | "graine:widget" | The agent drew a component: widget. |
static let widgetPending | "graine:widget-pending" | A component is on its way: widget, what is known of it so far. |
static let appAction | "graine:app-action" | The agent asked for one of your actions: action with action_id, name and arguments. |
static let ended | "graine:ended" | The call is over. |
static let micDenied | "graine:mic-denied" | The microphone was refused: reason. |
static let transport | "graine:transport" | What carries the audio, and why it differs from what was asked: transport, requested, reason, message. |
static let quality | "graine:quality" | One connection measurement: sample with rtt, jitter and loss. |
static let network | "graine:network" | The network went or came back: online. |
static let reconnecting | "graine:reconnecting" | The call is being re-established, or that is over: active. |
static let progress | "graine:progress" | Where a connect is: stage — registering, dialling, retrying, switching or failed. |
GraineProtocol.engineUrl
Call it only if you host the engine page yourself, to build its address.
The engine page's URL for an agent, transport and page version; idle loads it without connecting anything.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
baseUrl | String | required | "https://www.graine.ai" unless your workspace is self-hosted. |
voiceAgentId | String | required | The id in-app calls are placed under (GraineSessionConfig.webcallAgentId). |
transport | GraineTransport | required | The transport the page should use: .websocket or .webrtc. |
cacheKey | String | required | The engine page's version (GraineSessionConfig.pageVersion), so a new version is a new address and the same one is served from cache. |
idle | Bool | required | Load the page without connecting anything, so a later start is quick. |
publishableKey | String | required | The agent's publishable key, pk_live_…, from Agent → Embed & Widgets → Install. |
accent | String? | required | The accent colour, #RRGGBB (the # optional); nil leaves it out. |
Returns — String: the page's address. Example value: "https://www.graine.ai/embed/…?mode=voice&branding=0&embed=1&transport=ws&b=…&idle=1&pk=pk_live_…".
Errors — none.
GraineProtocol.deliver
Call it only if you host the engine page yourself, to post a message into it.
The JavaScript that delivers one message into the page; nil when the message cannot be serialised (dropped, never a crash).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | [String: Any] | required | One message for the page, as GraineProtocol.out builds it. |
Returns — String?: the script that posts it into the page; nil when it cannot be written as JSON. Example value: "window.postMessage({\"type\":\"graine:set-muted\",\"muted\":true}, \"*\"); true;".
Errors — none.
GraineProtocol.out
Call it only if you host the engine page yourself, to build a message for it.
One outgoing message: its type and its fields as JSON values.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
type | String | required | The message's type — one of the GraineProtocol.Out names. |
fields | [String: Any?] | [:] | The message's other fields, by name; each becomes a JSON value (nil becomes null). |
Returns — [String: Any]: the message, with type set. Example value: ["type": "graine:set-muted", "muted": true].
Errors — none.
GraineProtocol.parse
Call it only if you host the engine page yourself, to read a message from it.
One message from the page, typed; nil for anything that is not one of the page's messages.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | String? | required | One message from the page, as JSON text. |
Returns — PageMessage? — its fields: PageMessage. Example value: .ready.
Errors — none.
PageMessage
Read it only if you host the engine page yourself.
Every message the engine page sends, typed — returned by GraineProtocol.parse(_:). Used by the SDK itself; apps do not need it.
What to expect — The SDK sends and reads these for you; they are public so a custom UI can reuse them.
| Case | Meaning |
|---|---|
case ready | The page is mounted and ready. |
case call(connected: Bool, connecting: Bool) | The call's state. |
case muted(Bool) | The microphone was muted or unmuted. |
case caption(role: GraineRole, text: String, live: Bool) | A caption line. |
case widget([String: Any]) | A component the agent drew. |
case widgetPending([String: Any]) | A component on its way. |
case appAction(actionId: String, name: String, arguments: [String: Any]) | The agent asked for an action. |
case ended | The call is over. |
case micDenied(reason: String) | The microphone was refused. |
case transport(transport: GraineTransport, requested: GraineTransport, reason: String?, message: String?) | What carries the audio, and why. |
case quality(rttMs: Double?, jitterMs: Double?, loss: Double?) | One quality measurement. |
case network(online: Bool) | The network went or came back. |
case reconnecting(active: Bool) | The call is being re-established, or that is over. |
case progress(GraineConnectProgress) | Where a connect is. |
Appendix: the Objective-C interface
Use it from an Objective-C app, or from a bridge that reaches Swift through the Objective-C runtime (the Kotlin Multiplatform SDK binds it): the same SDK through GraineObjC… classes, with Foundation types and completion blocks in place of async and Swift enums. Every selector is fixed with @objc(…), so it never moves when Swift names change. Swift apps use the API above.
Objective-C
Each table gives the member's Swift name, its Objective-C form, and what it is for.
Errors and JSON in Objective-C
Read it to handle a refusal from Objective-C, and to use the …JSON methods from a bridge.
- Errors. Every completion's
NSErroris in the domainai.graine.inapp(GraineObjC.errorDomain).error.userInfo[@"code"]is the stable code to branch on —identity_required,too_soon,network, … (Error codes and their fixes);error.userInfo[@"retryAfterSeconds"]is how long a rate limit asked you to wait, when Graine said;error.localizedDescriptionis the sentence to show;error.codeis the HTTP status, 0 when the refusal was made on the device or no answer came back. An error that is not one of Graine's refusals arrives with the codeserver_error. - JSON. The
…JSONmethods carry what Foundation values cannot carry exactly across a bridge (a whole number against a boolean, a missing key against a null one): JSON text in, JSON text out, with times as milliseconds since 1970. - Threads. Completion blocks, listeners and observers run on the main thread; the methods may be called from any thread.
GraineObjC
Use it as the entry point from Objective-C: configure an agent, say who the customer is, describe the screen, register actions and book calls.
@interface GraineObjC : NSObject — one per configured agent, over the same agent GraineInApp is; made by configureWithBaseUrl:… and found again with current or named:. Who the customer is, the screen and the phone's calls are the app's, shared by every agent; the chat, the voice bar, actions and requests are each agent's own.
| Member | Objective-C | What it does |
|---|---|---|
chat | @property (readonly) GraineObjCChat *chat | The agent's typed conversation: open it, send, close it, and read its state. |
calls | @property (readonly) GraineObjCCalls *calls | Native calls on CallKit and VoIP pushes — one call at a time for the whole app; a call placed here is with this agent. |
voice | @property (readonly) GraineObjCVoice *voice | The voice bar's in-app call, for an app that draws its own controls. |
errorDomain | @property (class, readonly) NSString *errorDomain | "ai.graine.inapp": the domain of every NSError this interface hands out, to recognise them. |
configure(baseUrl:publishableKey:) | + (GraineObjC *)configureWithBaseUrl:(NSString *)baseUrl publishableKey:(NSString *)publishableKey | Configure the default agent — once, at launch; a second call returns the first. |
configure(baseUrl:publishableKey:variables:) | + configureWithBaseUrl:publishableKey:variables: | The same, with prompt variables known at build time (an NSDictionary). |
configure(baseUrl:publishableKey:appVersion:debug:endOnBackground:captureTaps:transport:variablesJSON:) | + configureWithBaseUrl:publishableKey:appVersion:debug:endOnBackground:captureTaps:transport:variablesJSON: | All the options at once, for a bridge. transport takes ws, rtc or nil (nil leaves it to the dashboard), and variablesJSON takes a JSON object. |
configure(…, name:) | + configureWithBaseUrl:publishableKey:appVersion:debug:endOnBackground:captureTaps:transport:variablesJSON:name: | An agent by name, with its own publishable key; an empty name is the default agent, and a name already configured returns that agent unchanged. Never nil in practice. |
configure(…, name:, chatSize:) | + configureWithBaseUrl:publishableKey:appVersion:debug:endOnBackground:captureTaps:transport:variablesJSON:name:chatSize: | The same, with the chat window's size: compact, large or full; nil or any other word follows the dashboard's Window size. |
named(_:) | + (nullable GraineObjC *)named:(NSString *)name | The agent configured under that name — here or from Swift — or nil. |
current | @property (class, readonly, nullable) GraineObjC *current | The default agent, else the first configured; nil before any is. |
name | @property (readonly) NSString *name | The name given to this agent when it was configured — default for the default agent. |
installId | @property (readonly) NSString *installId | This install's id, kept in the Keychain — for your support team; the agent never sees it. |
memoryChoice | @property (readonly) BOOL memoryChoice | The current customer's memory choice: YES until they turn it off. |
identify(_:) | - identify: | Who is signed in, as an NSDictionary of traits; nil signs out. Keeps the customer's memory choice. |
identify(_:memory:) | - identify:memory: | Sets the signed-in customer and whether their conversations are remembered. |
identifyJSON(_:) | - identifyJSON: | The bridge form of identify:, with the traits as a JSON object; passing nil or null signs out. |
identifyJSON(_:memory:) | - identifyJSON:memory: | identify:memory: for a bridge, the traits as a JSON object. |
setMemory(_:) | - setMemory: | Turn remembering the current customer's conversations on (YES) or off (NO), from their next conversation. |
forgetMemory(completion:) | - forgetMemoryWithCompletion: | Delete what the agent remembers about the signed-in customer; the block gets how many memories were deleted, or the error (identity_required, device_limit, …). |
memories(completion:) | - memoriesWithCompletion: | What the agent remembers about the signed-in customer, newest first: an array of GraineObjCMemory, or the error. |
memoriesJSON(completion:) | - memoriesJSONWithCompletion: | The same for a bridge: a JSON array of {id, text, createdAt, updatedAt}, times in milliseconds. |
setVariables(_:) | - setVariables: | Prompt variables from the app's data, merged into those set: NSNull under a key removes it, nil clears them all. |
setVariablesJSON(_:) | - setVariablesJSON: | setVariables: for a bridge, the variables as a JSON object. |
currentVariables | @property (readonly) NSDictionary<NSString *, NSString *> *currentVariables | Every variable the agent will receive, merged in precedence order — the dashboard's defaults, the build's, setVariables:, then the traits from identify: — values as strings, already masked: GraineInApp.currentVariables(). |
currentVariablesJSON | @property (readonly) NSString *currentVariablesJSON | The same as a JSON object, for a bridge. |
setScreen(_:) | - setScreen: | What the customer is looking at: screen, title, journey, fields, error, data, availableActions; nil, or no screen, clears it. |
setScreenJSON(_:) | - setScreenJSON: | setScreen: for a bridge, the screen as a JSON object. |
updateField(_:) | - updateField: | One field changed — name, label, value, status, error, optional — merged into the current screen by name. |
updateFieldJSON(_:) | - updateFieldJSON: | updateField: for a bridge, the field as a JSON object. |
removeField(_:) | - removeField: | Take a field off the current screen, by name, when it leaves the screen. |
clearScreen(_:) | - clearScreen: | The customer left a screen; with an id, only if it is still the one described. |
track(_:data:) | - track:data: | A product event that informs the agent without interrupting. |
trackEvent(_:dataJSON:) | - trackEvent:dataJSON: | track:data: for a bridge, the data as a JSON object. |
tap(_:) | - tap: | Record a tap on a named control; the agent sees tap: and the name. |
noteInteraction() | - noteInteraction | The customer did something that is not a field edit — scrolled, opened a picker — so the clock that spots a stuck customer restarts. |
registerAction(_:handler:) | - registerAction:handler: | Something the agent can do in your app: the handler runs on the main thread with the arguments and calls its answer block once, from any thread, within six seconds. Returns a GraineObjCRegistration. |
registerJSONAction(_:handler:) | - registerJSONAction:handler: | The same for a bridge: the arguments arrive as a JSON object, and the answer block takes the status (ok, refused, error), the message and the data as a JSON object (or nil). |
scheduleCall(at:reason:completion:) | - scheduleCallAt:reason:completion: | Book a call for that time from a screen of your own; the block gets the booked GraineObjCScheduledCall, or the refusal (scheduling_off, too_soon, outside_window, …). |
scheduleCallJSON(at:reason:completion:) | - scheduleCallJSONAt:reason:completion: | The same for a bridge: the call as JSON {id, at, status, source, reason}. |
scheduledCalls(completion:) | - scheduledCallsWithCompletion: | The customer's calls still to come, soonest first. |
scheduledCallsJSON(completion:) | - scheduledCallsJSONWithCompletion: | The same for a bridge: a JSON array of {id, at, status, source, reason}, times in milliseconds. |
cancelScheduledCall(_:completion:) | - cancelScheduledCall:completion: | Call off one booked call; the block gets its status afterwards (cancelled), or the error (not_found, …). |
bookingAvailable | @property (readonly) BOOL bookingAvailable | Whether this agent can be booked from the app now — Graine rings this iPhone, the agent lets customers book, the customer is identified. Show your own "Call me" button only then; read it again after identify: and the sessionReady event. |
session | @property (readonly, nullable) GraineObjCSession *session | The agent's settings, once they have loaded; nil before. |
sessionJSON | @property (readonly, nullable) NSString *sessionJSON | The settings as JSON, for a bridge; nil before they have loaded. |
themeJSON | @property (readonly, nullable) NSString *themeJSON | The brand's design tokens to draw right now (GraineInApp.theme) as JSON — the same keys and words on every platform, the chat's and the call's tokens included — for UI of your own; nil before the settings load and when the agent has no design. The theme event says when they change. |
addListener(_:) | - addListener: | Hear every event the agent reports (held weakly, so keep a reference). |
removeListener(_:) | - removeListener: | Stop telling that listener. |
observeEvents(_:) | - (GraineObjCRegistration *)observeEvents:(void (^)(NSString *name, NSString *json))observer | For a bridge: delivers each event's name with its payload as JSON, until you stop the registration. |
GraineObjC (view controllers)
Use them to show the SDK's own screens from Objective-C, or from a bridge that hosts native views.
The SDK's screens as UIKit view controllers, drawn in the agent's design exactly as the SwiftUI views are.
| Member | Objective-C | What it does |
|---|---|---|
chatViewController(onClose:) | - (UIViewController *)chatViewControllerWithOnClose:(nullable void (^)(void))onClose | The chat as a screen; onClose draws the header's close control (nil: none — the navigation it sits in closes it). It connects as soon as it appears and hangs up once it is gone. |
callViewController() | - (UIViewController *)callViewController | The in-app call screen, used while the app is open — ringing, connecting, the call itself, ended, and a missed ring with Call back. |
voiceBarViewController(name:onClose:) | - voiceBarViewControllerWithName:onClose: | The voice bar, filling the view it is given and drawn at its bottom: name beside the status (nil: the dashboard's heading); onClose when the customer hung up or closed the bar. It mounts the engine when it appears. |
presentBooking(reason:completion:) | - presentBookingWithReason:completion: | The booking sheet over whatever is on screen; the block gets the booked call once it closes, or nil. See GraineObjC.presentBooking. |
GraineObjC.presentBooking
Call it to let the customer book a call on the SDK's own sheet from Objective-C.
In Swift: func presentBooking(reason: String?, completion: @escaping (GraineObjCScheduledCall?) -> Void). Shows the booking sheet over whatever is on screen — a sheet on an iPhone, a centred panel on an iPad — exactly as GraineInApp.presentBooking(reason:) does (Booking a call: the SDK's sheet). The block runs once, on the main thread, after the sheet has closed: the booked call, or nil when nothing was booked — including when no window is on screen yet or a booking sheet is already open. Safe to call from any thread. Check bookingAvailable first: opened where it is NO, the sheet says why and books nothing.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | NSString * (nullable) | required | What "What's it about? (optional)" starts with; nil leaves it empty. The customer may change it; up to 120 characters. |
completion | block | required | Called once after the sheet has closed, with the booked GraineObjCScheduledCall or nil. |
Returns — nothing; the block receives a GraineObjCScheduledCall (its members below), or nil.
Errors — none: a refusal stays in the sheet as one line, and the customer picks again.
GraineObjCListener
Implement it to hear the SDK's events in an Objective-C app.
@protocol GraineObjCListener — added with addListener: on GraineObjC, held weakly, called on the main thread. Its one method is required.
| Member | Objective-C | What it does |
|---|---|---|
graineEvent(_:payload:json:) | - (void)graineEvent:(NSString *)name payload:(NSDictionary *)payload json:(NSString *)json | Called once per event with its name, its payload, and the same payload as JSON text. |
The events, by name:
| Event | Payload | When |
|---|---|---|
sessionReady | agentId, webcallAgentId, memoryEnabled, declaredActions, identity (field, devicesPerUser), calls (hosted, platforms, ringTimeoutSeconds, callName, usersCanBook, maxDaysAhead, minLeadMinutes, maxPendingPerUser) | The agent's settings have loaded — read bookingAvailable again now |
caption | role (agent or user), text, live | A line on the in-app call, again as it fills in |
voiceEnded | nothing | The in-app call is over |
widget | widget: the component | The agent drew a component |
widgetPending | hint: what is known of the coming component | A component is on its way |
chatMessage | turn: id, role, text, live, delivery, at (milliseconds), proactive, restored, channel | The agent finished a chat message |
actionRequested | name | The agent asked for one of your actions |
actionCompleted | name, status, message | The answer the agent received for it |
friction | name, screen, detail | The customer looks stuck |
micDenied | reason | The microphone was refused |
theme | the design's tokens, as themeJSON has them (empty when there are none) | The tokens to draw changed: the settings arrived, or the device switched between light and dark |
error | message | Something your team must fix |
GraineObjCRegistration
Keep it to stop what you registered or observed.
@interface GraineObjCRegistration : NSObject — what registerAction:handler: and every observe… method return.
| Member | Objective-C | What it does |
|---|---|---|
unregister() | - unregister | Stops it: the action is unregistered, or the observer no longer called. Safe to call more than once. |
GraineObjCActionResult
Use it to answer an action the agent asked for.
@interface GraineObjCActionResult : NSObject — passed to the answer block of registerAction:handler:. The agent speaks about exactly this, so say what actually happened.
| Member | Objective-C | What it does |
|---|---|---|
status | @property (readonly) NSString *status | How it turned out: ok, refused or error. |
message | @property (readonly) NSString *message | A sentence for the agent — not the customer — about what happened; the agent decides how to phrase it. |
data | @property (readonly) NSDictionary *data | A structured result the agent can reason about; empty unless given. |
ok(_:) | + (GraineObjCActionResult *)ok:(NSString *)message | It happened. |
ok(_:data:) | + ok:data: | It happened, with a structured result. |
refused(_:) | + refused: | It will not happen, and why — often the most useful answer. |
error(_:) | + error: | It was tried and failed. |
GraineObjCMemory
Read it to list one thing the agent remembers about the customer.
@interface GraineObjCMemory : NSObject — what memoriesWithCompletion: returns, newest first, already redacted.
| Member | Objective-C | What it does |
|---|---|---|
identifier | @property (readonly) NSString *identifier | The memory's id, as Graine keeps it. |
text | @property (readonly) NSString *text | What is remembered, in a sentence — to show the customer. |
createdAt | @property (readonly, nullable) NSDate *createdAt | When it was first remembered; nil when Graine did not say. |
updatedAt | @property (readonly, nullable) NSDate *updatedAt | When it last changed; nil when Graine did not say. |
GraineObjCScheduledCall
Read it to show one booked call.
@interface GraineObjCScheduledCall : NSObject — a call booked to ring this customer (or this install) later: what scheduleCallAt:reason:completion:, scheduledCallsWithCompletion: and presentBookingWithReason:completion: return.
| Member | Objective-C | What it does |
|---|---|---|
identifier | @property (readonly) NSString *identifier | The booking's id (a UUID) — what cancelScheduledCall:completion: takes. |
at | @property (readonly, nullable) NSDate *at | When it rings; nil only when Graine's time could not be read. Show it in the device's time zone. |
status | @property (readonly) NSString *status | Where it is: scheduled, ringing, answered, completed, declined, missed, failed, cancelled, expired, no_device — or a newer word; give every check a default. |
source | @property (readonly, nullable) NSString *source | Who booked it: agent, sdk (your app), dashboard or api; nil when Graine does not say. |
reason | @property (readonly, nullable) NSString *reason | What the call is about, as it was booked (up to 120 characters); nil when none was given. |
isUpcoming | @property (readonly) BOOL isUpcoming | Still to come: scheduled, or ringing now. |
canCancel | @property (readonly) BOOL canCancel | It can still be called off: scheduled, not rung yet. |
GraineObjCSession
Read it to see what the dashboard says about the agent, once its settings have loaded.
@interface GraineObjCSession : NSObject — GraineObjC.session.
| Member | Objective-C | What it does |
|---|---|---|
agentId | @property (readonly) NSString *agentId | The agent's id, as the dashboard shows it. |
memoryEnabled | @property (readonly, nullable) NSNumber *memoryEnabled | A boolean: whether the agent remembers customers between conversations; nil when the settings do not say. |
identityField | @property (readonly) NSString *identityField | The trait that names the customer: user_id unless the dashboard chose email, phone or a custom trait. |
devicesPerUser | @property (readonly) NSInteger devicesPerUser | How many devices one customer's memory may be used from, 1 to 5 (2 by default). |
usersCanBook | @property (readonly) BOOL usersCanBook | Customers may book calls from the app. |
hostedRinging | @property (readonly) BOOL hostedRinging | Graine rings this workspace's customers itself (In-app calling is set up). |
callName | @property (readonly, nullable) NSString *callName | The ring name the dashboard set for the agent's calls, if it set one. |
json | @property (readonly) NSString *json | The same settings as JSON — the text sessionJSON gives. |
GraineObjCCalls
Use it to place, answer and draw native calls from Objective-C.
@interface GraineObjCCalls : NSObject — GraineObjC.calls: calls on CallKit and VoIP pushes. The customer calls the agent (startCallWithReason:variables:), or the agent rings the customer (enableVoIPPushes at every launch). A call connects as GraineCalls.startCall describes: one 20-second deadline, and connected only once its audio runs.
| Member | Objective-C | What it does |
|---|---|---|
configure(displayName:ringtoneSound:ringTimeout:includesCallsInRecents:) | - configureWithDisplayName:ringtoneSound:ringTimeout:includesCallsInRecents: | How calls ring and what they show — once, at launch, before enableVoIPPushes. An empty name is your app's; a ringTimeout of 0 or less is 30 seconds. It starts the road the audio takes again from auto: set callTransport after it. |
callTransport | @property NSString *callTransport | The road the calls' audio takes, in both directions (GraineCallsConfig.callTransport): auto — the default, WebRTC when the GraineInAppRTC package is in the app and the dashboard allows it — websocket or webrtc; any other word is auto. Applies from the next call. |
enableVoIPPushes() | - enableVoIPPushes | Receive the agent's calls as VoIP pushes; call it at every launch. |
startCall(reason:variables:) | - (nullable NSString *)startCallWithReason:(nullable NSString *)reason variables:(nullable NSDictionary *)variables | The customer calls this agent: returns the call's id, or nil when another call is live. |
startCallJSON(reason:variablesJSON:) | - startCallJSONWithReason:variablesJSON: | The same for a bridge, the variables as a JSON object. |
answer() | - answer | Picks up the ringing call when you draw your own call screen. |
decline() | - decline | Decline the ringing call; nothing happens when none is ringing. |
end() | - end | Hang up — or decline, while it rings. |
setMuted(_:) | - setMuted: | Stop (YES) or resume (NO) sending the customer's voice, from your own mute control. |
setSpeaker(_:) | - setSpeaker: | The loudspeaker (YES) or the earpiece or headset (NO), from your own speaker control. |
callBack() | - (nullable NSString *)callBack | Return the missed call: the new call's id, or nil when there is nothing to return or a call is live. |
dismissMissedCall() | - dismissMissedCall | The customer has seen the missed call, so the call screen can go. |
state | @property (readonly) NSString *state | Where the call is: idle, ringing, connecting, active or ended. |
endReason | @property (readonly, nullable) NSString *endReason | Why it ended, while state is ended: local, remote, failed, declined or missed; nil otherwise. |
callId | @property (readonly, nullable) NSString *callId | The current (or just-ended) call's id; nil with no call. |
callerName | @property (readonly) NSString *callerName | The name the call shows; empty with no call. |
direction | @property (readonly, nullable) NSString *direction | incoming (the agent rang) or outgoing (the customer called); nil with no call. |
muted | @property (readonly) BOOL muted | The customer's voice is not being sent — draw your mute control from it. |
speakerOn | @property (readonly) BOOL speakerOn | The audio is on the loudspeaker — draw your speaker control from it. |
agentSpeaking | @property (readonly) BOOL agentSpeaking | The agent's voice is playing right now — animate an avatar from it. |
hosted | @property (readonly) BOOL hosted | Graine rang this call: one booked for the customer ("Scheduled call"). |
agent | @property (readonly, nullable) NSString *agent | The agent the call is with, by name (default for the default agent); nil with no call. |
showsCallScreen | @property (readonly) BOOL showsCallScreen | A call screen has something to show — a call, or a missed ring to call back: present one while it is YES. |
transport | @property (readonly, nullable) NSString *transport | The road the call's audio runs on: webrtc or websocket; nil before it is decided — read it once state is active. See GraineCalls.transport. |
transportFallbackReason | @property (readonly, nullable) NSString *transportFallbackReason | Why a call that tried WebRTC runs on the WebSocket: mint_failed, register_failed, no_response, no_answer, ice_timeout or error (what each means); nil otherwise. |
snapshotJSON | @property (readonly) NSString *snapshotJSON | The call as JSON, for a bridge: every property above (transport and transportFallbackReason included), plus caption (role, text, live) and missedCall (callId, callerName, reason, hosted, at). |
addListener(_:) | - addListener: | Hear the calls (held weakly, on the main thread). |
removeListener(_:) | - removeListener: | Stop telling that listener. |
observe(_:) | - observe: | For a bridge: the block runs after every change to the call, until the registration is stopped. |
observeOutcomes(_:) | - observeOutcomes: | Hands a bridge every outcome as JSON: {callId, outcome, reason, durationSeconds, hosted, agent}. |
observeIncoming(_:) | - observeIncoming: | For a bridge: each ring as JSON {callId, callerName, reason, hosted, agent} — agent when known. |
callPermissionsJSON | @property (readonly) NSString *callPermissionsJSON | The call permissions as JSON {microphone, notifications, fullScreen}, each granted, denied, blocked or notNeeded (GraineCalls.callPermissions). |
requestCallPermissionsJSON(completion:) | - requestCallPermissionsJSONWithCompletion: | Asks for what the system can still ask for (GraineCalls.requestCallPermissions); the block gets the permissions as callPermissionsJSON has them, on the main thread. |
openCallSettings(_:) | - (BOOL)openCallSettings:(NSString *)item | Opens Settings for microphone, notifications or fullScreen: YES when Settings was asked to open, NO for any other word. |
callSetupProblemsJSON | @property (readonly) NSString *callSetupProblemsJSON | What the app's setup still lacks for calls, as JSON [{code, message}] (GraineCalls.callSetupProblems); [] when nothing is missing. |
GraineObjCCallsListener
Implement it to hear the calls in an Objective-C app — for your own call screen and your server.
@protocol GraineObjCCallsListener — added with addListener: on GraineObjCCalls, held weakly, called on the main thread. All three methods are required.
| Member | Objective-C | What it does |
|---|---|---|
graineCallsChanged(_:) | - (void)graineCallsChanged:(GraineObjCCalls *)calls | Something about the call changed — its state, its controls or its caption: read the properties of the GraineObjCCalls you are handed. At most once per turn of the main loop. |
graineCallOutcome(_:) | - (void)graineCallOutcome:(GraineObjCCallOutcome *)outcome | What became of a call — send it to your server, with its call id. |
graineIncomingCall(_:callerName:hosted:) | - (void)graineIncomingCall:(NSString *)callId callerName:(nullable NSString *)callerName hosted:(BOOL)hosted | An agent's call is ringing on this device: its id, the name it rings with (nil when the push named none), and whether Graine rang it. |
GraineObjCCallOutcome
Read it to tell your server what became of a call.
@interface GraineObjCCallOutcome : NSObject — what graineCallOutcome: and observeOutcomes: report. Every ring reports ringing first, then the rest.
| Member | Objective-C | What it does |
|---|---|---|
callId | @property (readonly) NSString *callId | The call's id: your server's call_id, Graine's for a ring it sent, or the one startCallWithReason:variables: returned. |
outcome | @property (readonly) NSString *outcome | ringing, answered, declined, missed, failed or ended. |
reason | @property (readonly, nullable) NSString *reason | Why: busy or no_answer with missed; local, remote or failed with ended; with failed, the sentence (up to 200 characters) — such as "The call could not connect within 20 seconds." or "The call's audio could not start."; nil otherwise. |
durationSeconds | @property (readonly, nullable) NSNumber *durationSeconds | How long a call that connected lasted, in whole seconds — with ended only. |
hosted | @property (readonly) BOOL hosted | Graine rang it: a call booked by the agent, the customer or your team. |
agent | @property (readonly, nullable) NSString *agent | The name of the agent on the call (default for the default agent), or nil if that is unknown. |
json | @property (readonly) NSString *json | The same outcome as JSON text, ready to send. |
GraineObjCVoice
Use it to drive the voice bar's in-app call from your own controls.
@interface GraineObjCVoice : NSObject — GraineObjC.voice: the agent talking inside your app.
| Member | Objective-C | What it does |
|---|---|---|
attach() | - attach | Mount the engine in the key window, idle, so a later start connects quickly; start does it by itself. |
start() | - start | Place the in-app call, mounting the engine first if needed; asks for the microphone when the app does not hold it. |
end() | - end | Hang up the in-app call. |
setMuted(_:) | - setMuted: | Stop (YES) or resume (NO) sending the customer's voice. |
setCaptionsOn(_:) | - setCaptionsOn: | The captions switch: YES shows the line being said in the voice bar, NO hides it; the call goes on (GraineVoice.setCaptionsOn(_:)). |
clearCaptions() | - clearCaptions | Forget what has been said without ending the call (GraineVoice.clearCaptions()). |
connected | @property (readonly) BOOL connected | A call is up — draw your hang-up control from it. |
connecting | @property (readonly) BOOL connecting | A call is being placed or re-established — show that it is connecting. |
muted | @property (readonly) BOOL muted | The customer's voice is not being sent — draw your mute control from it. |
captionsOn | @property (readonly) BOOL captionsOn | The captions switch: YES (the default) until it is turned off — draw your CC control from it. |
snapshotJSON | @property (readonly) NSString *snapshotJSON | For a bridge, the state as JSON: {connected, connecting, muted, captionsOn, captions}, each caption {role, text, live}. |
observe(_:) | - observe: | The block runs on the main thread after every change, until the registration is stopped. |
GraineObjCChat
Use it to drive and draw the typed conversation from Objective-C.
@interface GraineObjCChat : NSObject — GraineObjC.chat: open when the customer is in the chat, send:, close when they leave, and the state read whenever a listener hears a change.
| Member | Objective-C | What it does |
|---|---|---|
turns | @property (readonly) NSArray<GraineObjCChatTurn *> *turns | What has been said, oldest first — draw one bubble per turn. |
status | @property (readonly) NSString *status | The connection: idle, connecting, online, reconnecting or offline. |
reconnectAttempt | @property (readonly) NSInteger reconnectAttempt | The current attempt number (out of six) during a reconnect; 0 at any other time. |
typing | @property (readonly) BOOL typing | The agent is working on a reply — show a typing bubble. |
isOpen | @property (readonly) BOOL isOpen | The customer is in the chat: open was called and close was not. |
upcomingCalls | @property (readonly) NSArray<GraineObjCScheduledCall *> *upcomingCalls | The customer's calls still to come, soonest first, at most three — the chat window's Upcoming calls card (GraineChat.upcomingCalls). |
snapshotJSON | @property (readonly) NSString *snapshotJSON | The whole chat as JSON, for a bridge: {turns, status, attempt, typing, open, upcomingCalls}, each turn {id, role, text, live, delivery, at, proactive, restored, channel}, each upcoming call {id, at, status, source, reason}. |
open() | - open | Puts the customer in the chat, connecting and keeping the connection up. |
open(size:) | - openWithSize: | The same, at a size for this open: compact, large or full; nil or another word follows the app's chat size, then the dashboard's. |
newConversation(completion:) | - newConversationWithCompletion: | Start a fresh conversation; the block gets nil, or Graine's refusal — the chat is then as it was. |
close() | - close | The customer left: hang up. |
send(_:) | - send: | Send what the customer typed: shown at once, sent now or when the chat connects. |
retry() | - retry | Connects right away and sends whatever was left unsent. |
clear() | - clear | Empty the chat on screen; the conversation goes on, a message still waiting is still sent, and nothing Graine keeps is touched (GraineChat.clear()). |
answerWidget(id:dataJSON:summary:) | - answerWidgetWithId:dataJSON:summary: | Answer a component the agent drew, from a UI of your own, on the conversation that is live (GraineInApp.answerComponent): dataJSON the customer's choice as a JSON object (nil: none), summary the sentence that is their answer. |
refreshUpcomingCalls() | - refreshUpcomingCalls | Fetch the upcoming calls again — after a booking made elsewhere; the card refreshes itself otherwise. |
cancelUpcomingCall(_:) | - cancelUpcomingCall: | Cancel one of the upcoming calls, by its id: it leaves upcomingCalls once Graine has called it off. |
addListener(_:) | - addListener: | Hear the chat change (held weakly, on the main thread). |
removeListener(_:) | - removeListener: | Stop telling that listener. |
observe(_:) | - observe: | For a bridge: the block runs on the main thread after every change, until the registration is stopped. |
GraineObjCChatListener
Implement it to redraw your chat in an Objective-C app.
@protocol GraineObjCChatListener — added with addListener: on GraineObjCChat, held weakly, called on the main thread. Its one method is required.
| Member | Objective-C | What it does |
|---|---|---|
graineChatChanged(_:) | - (void)graineChatChanged:(GraineObjCChat *)chat | The chat changed — its turns, its status, the typing indicator or the upcoming calls: read the GraineObjCChat you are handed and redraw. At most once per turn of the main loop. |
GraineObjCChatTurn
Read it to draw one message of the chat.
@interface GraineObjCChatTurn : NSObject — one message in GraineObjCChat.turns: the customer's, or one reply of the agent's.
| Member | Objective-C | What it does |
|---|---|---|
identifier | @property (readonly) NSString *identifier | Stable for the life of the transcript — t1, t2, … — so you can update a bubble in place. |
role | @property (readonly) NSString *role | Who said it: user (the customer) or agent. |
text | @property (readonly) NSString *text | What was said; an agent's words grow as they stream in. |
live | @property (readonly) BOOL live | The agent's words are still arriving: draw them as provisional. |
delivery | @property (readonly) NSString *delivery | Where a customer's message is: sending, sent or failed (then retry sends it); always sent for the agent's. |
at | @property (readonly) NSDate *at | When it was said — for an agent's turn, its first words. |
proactive | @property (readonly) BOOL proactive | The agent spoke up without being asked — badge your chat if the customer is not looking. |
restored | @property (readonly) BOOL restored | Carried over from an earlier conversation and brought back when the chat opened (the customer returned). |
channel | @property (readonly) NSString *channel | chat, or voice for a turn said on a call — draw a run of them as one "Voice call" row, not their words. |
What's new in 0.9.0
- Calls on WebRTC, opt-in. A separate package,
GraineInAppRTC(https://github.com/GraineAI/inapp-ios-rtc, orpod 'GraineInAppRTC', :git => 'https://github.com/GraineAI/inapp-ios-rtc.git', :tag => '0.10.3'), always the same version asGraineInApp: add it besideGraineInAppand the native calls — the ones the customer places and the ones your agent rings — carry their audio over WebRTC, with a jitter buffer, loss concealment and the platform's echo canceller. Nothing else changes in the app;GraineRTC.install()exists for builds that strip code nothing references.GraineInAppitself still has no dependencies. See Add WebRTC calls. - A call WebRTC cannot carry continues on the WebSocket inside the same call — no second ring, "Connecting…" a moment longer. A call the agent cannot take at all (no credit left, the agent switched off) ends with that reason.
GraineCallsConfig.callTransport:.auto(the default),.websocketor.webrtc. The agent's In-app call audio setting (GraineHostedCallsSettings.audioTransport) can keep its calls on the WebSocket without an app release.calls.transportsays which road carried a call andcalls.transportFallbackReasonwhy one that tried WebRTC did not. Objective-C:callTransport,transportandtransportFallbackReasononGraineObjCCalls;GraineCallsHost: thecallTransportoption, and both values in the snapshot.- With the package in the app, a call Graine rings gets ready while it rings, so answering connects at once; a declined or missed ring lets go of what it prepared.
chat.clear()empties the chat on screen — the screen only: the conversation goes on and nothing Graine keeps is touched. Objective-C:clearonGraineObjCChat.answerComponent(widgetId:data:summary:)answers a component from a UI of your own, on the conversation that is live — the chat or the call. Objective-C:answerWidgetWithId:dataJSON:summary:onGraineObjCChat, beside the chat'supcomingCalls,refreshUpcomingCallsandcancelUpcomingCall:.- The captions switch:
voice.captionsOn(on by default),setCaptionsOn(_:)andclearCaptions()—GraineAgentBarshows the line being said only while it is on, andcaptionskeeps what is said either way. Objective-C: the same onGraineObjCVoice. GraineChatRows: the rows a chat window draws — a bubble per message, one "Voice call · 6:05 PM" row per earlier call, a day line where the day changes — for a chat of your own, withdayLabel,callLabel,callSpoken,clock,offersStartersandoffersNewConversation.voice.viewOpened()/viewClosed(): a voice UI of your own on screen counts as the bar does, so a page still loading behind it is reported asslowConnect.- Objective-C:
currentVariablesandcurrentVariablesJSON, andthemeJSONwith athemeevent, onGraineObjC. - Variables over a conversation's kilobyte are trimmed to fit, not all dropped:
who_calledandcall_reasonride first, and the biggest of the rest are left off, one at a time, until the rest fit. scheduleCall's own refusals say the booking sheet's words: "Booking a call isn't available right now", "Pick a time at least 5 minutes from now" ("1 minute" for one) and "Pick a time within 14 days".
What's new in 0.8.0
- Booking a call, drawn by the SDK.
GraineInApp.presentBooking(reason:)shows the booking sheet in your agent's design — a sheet on iPhone, a centred panel on iPad: the day, the time on the system's time wheel every 5 minutes, the phone's time zone, "What's it about? (optional)" and Book call. It returns the booked call, or nil when the customer closes it without booking; a refusal stays in the sheet as one line in plain words. SwiftUI:.graineBookingSheet(isPresented:graine:reason:onBooked:)andGraineBookingView; UIKit:GraineBookingViewController; Objective-C:presentBookingWithReason:completion:. See Booking with the SDK's sheet. bookingAvailable, on each agent and onGraineObjC: Graine rings this iPhone, the agent lets customers book, and the customer is identified. Show your own "Call me" button only then.- Book a call in the chat window, where booking is available: under the booked calls in the Upcoming calls card, or alone in the card's place when none are booked — never when the design hides the card. A booking refreshes the card.
- A call never sits on "Connecting…". Everything before the conversation opens has one 20-second deadline; past it the call fails with "The call could not connect within 20 seconds."
- A call is connected once it can be heard. If CallKit has not started the call's audio 3 seconds after the conversation opens, the SDK starts it itself, once; with still no audio 3 seconds later the call fails with "The call's audio could not start." — instead of staying open and silent.
- Calls start at once on the configuration the SDK already has — this launch's, or the one kept on the device — and refresh it behind the call.
- Prompt variables ride the ticket. They are no longer added to the connection's address, for a call or a chat; only the direct connection, used when no ticket can be had, still carries them.
- With
debugon, CallKit activating and deactivating a call's audio is logged.
The full list for every platform: Changelog.
Every symbol, A to Z
Every public symbol of the iOS SDK — 1023 in all — by name, each linked to where this page documents it: a type or function first, then its members.
GraineActionCheck—undeclared·unhandled·usableGraineActionResult—data·error·init·message·ok·refused·statusGraineActionResult.Status—error·ok·refusedGraineAgentBar—accessory·initGraineBookingView—initGraineBookingViewController—initGraineBubbleStyle—filled·minimal·outlineGraineCallDirection—incoming·outgoingGraineCallEndReason—declined·failed·local·missed·remoteGraineCallOutcome—answered·declined·ended·endReason·error·failed·missed·wireGraineCallPermission—fullScreen·microphone·notificationsGraineCallPermissions—allGranted·fullScreen·init·microphone·notifications·subscriptGraineCallPermissionStatus—blocked·denied·granted·isSatisfied·notNeededGraineCalls—addListener·agent·agentSpeaking·answer·callBack·callerName·callId·callPermissions·callSetupProblems·caption·config·configure·connectedAt·direction·dismissMissedCall·enableVoIPPushes·end·hosted·missedCall·muted·openCallSettings·removeListener·reportIncomingCall·requestCallPermissions·setMuted·setSpeaker·showsCallScreen·speakerOn·startCall·state·transcript·transport·transportFallbackReason·voipTokenUpdatedGraineCallsConfig—callTransport·displayName·iconTemplateImageData·includesCallsInRecents·init·ringTimeout·ringtoneSoundGraineCallSetupProblem—code·init·messageGraineCallsHost—addSink·answer·callPermissions·callSetupProblems·configure·decline·enableVoIPPushes·end·handlePush·installId·openCallSettings·removeSink·requestCallPermissions·resolveAction·restore·setActions·setIdentity·setMemory·setMuted·setScreen·setSpeaker·setVariables·shared·Sink·snapshot·startCallGraineCallsHostError—code·errorDescription·messageGraineCallsListener—onCallOutcome·onCallState·onIncomingCallGraineCallSource—agent·api·app·dashboard·description·init·unknown·wireGraineCallState—active·connecting·ended·idle·isLive·ringingGraineCallStatus—answered·cancelled·completed·declined·description·expired·failed·init·missed·noDevice·ringing·scheduled·unknown·wireGraineCallTokens—acceptFill·acceptIcon·avatar·avatarFill·avatarShape·avatarText·background·backgroundTo·buttonActiveFill·buttonActiveIcon·buttonFill·buttonIcon·callbackFill·callbackIcon·captions·controls·declineFill·declineIcon·endFill·endIcon·endShape·ringtone·statusText·text·textMuted·timerGraineCallTokens.StatusText—connecting·ended·missed·ringing·scheduledGraineCallTransport—auto·from·webrtc·websocketGraineCallView—initGraineCaption—init·live·role·textGraineChat—awaitingFirstMessage·cancelUpcomingCall·clear·close·isOpen·newConversation·online·open·openedSize·refreshUpcomingCalls·retry·send·status·turns·typing·upcomingCallsGraineChatCallLine—at·first·id·lastGraineChatChannel—chat·voiceGraineChatDay—date·today·yesterdayGraineChatDayLine—at·date·day·keyGraineChatRow—day·id·turn·voiceGraineChatRows—build·callLabel·callSpoken·clock·dayLabel·offersNewConversation·offersStartersGraineChatSize—compact·full·largeGraineChatStatus—connecting·idle·isConnecting·offline·online·reconnectingGraineChatTokens—avatar·background·composerBackground·composerBorder·composerPlaceholder·composerText·headerBackground·headerSubtext·headerText·newConversation·pattern·patternColor·placeholder·showHistory·showTimestamps·showUpcomingCalls·size·subtitle·suggestions·title·upcomingTitleGraineChatTurn—at·channel·delivery·id·live·proactive·restored·role·textGraineChatTurn.Delivery—failed·sending·sentGraineChatView—initGraineChatViewController—initGraineConfig—appVersion·baseUrl·captureTaps·chatSize·debug·endOnBackground·fonts·friction·init·publishableKey·timing·transport·variablesGraineConfig.Timing—bootMs·endWatchdogMs·hardCapMs·init·retryDelaysMs·slowConnectMs·stallMsGraineConnectionQuality—classify·excellent·good·lost·poor·unknownGraineConnectivity—failed·loading·ready·retryingGraineConnectProgress—failed·stageGraineConnectStage—dialling·registering·retrying·switchingGraineDesign—dark·light·parse·preset·scheme·tokensGraineDesignChrome—none·windowGraineDesignScheme—auto·dark·lightGraineErrorCode—description·deviceLimit·identityRequired·init·invalidKey·invalidRequest·network·notFound·originRefused·outsideWindow·rateLimited·schedulingOff·tooFar·tooManyPending·tooSoon·unavailable·unknown·wireGraineField—error·init·isOptional·label·name·status·valueGraineField.Status—empty·filled·invalid·locked·pendingGraineFrictionConfig—init·jsonGraineFrictionConfig.Screen—idleAfterMs·ignore·init·messageGraineFrictionSignal—cooldownSeconds·data·detail·maxPerSession·name·openLauncher·screenGraineGlyph—assetVersion·urlGraineHostedCallsSettings—audioTransport·callName·hosted·maxDaysAhead·maxPendingPerUser·minLeadMinutes·platforms·ringsThisPlatform·ringTimeoutSeconds·usersCanBookGraineIdentitySettings—devicesPerUser·field·init·standardGraineInApp—ActionHandler·addListener·all·answerComponent·bookingAvailable·calls·cancelScheduledCall·chat·checkActions·clearScreen·config·configure·current·currentVariables·DeferredActionHandler·design·forgetMemory·identify·installId·memories·memoryChoice·name·named·noteInteraction·presentBooking·registerAction·removeField·removeListener·reopenComponent·scheduleCall·scheduledCalls·schedulingRules·session·setMemory·setScreen·setVariables·shared·tap·theme·track·updateField·voiceGraineIncomingCall—agentId·callerName·callId·hosted·init·reason·variablesGraineLauncher—initGraineLauncherShape—circle·pill·roundedSquare·square·squircleGraineListener—onActionCompleted·onActionRequested·onCallState·onCaption·onChatMessage·onConnectivity·onEnded·onError·onFriction·onMicDenied·onNetwork·onQuality·onReconnecting·onSessionReady·onTheme·onTransport·onWidget·onWidgetPendingGraineLoader—initGraineLoaderLook—color·kind·periodMs·px·skeleton·textGraineLoaderText—component·connecting·forMoment·init·reconnecting·thinkingGraineLogoPlate—accent·whiteGraineMasking—addRule·clearCustomRules·deep·maskGraineMemory—createdAt·id·init·text·updatedAtGraineMissedCall—agent·at·callerName·callId·hosted·init·reasonGraineNativeAppearance—accent·heading·logoPlate·logoUrl·radiusGraineObjC—addListener·bookingAvailable·calls·callViewController·cancelScheduledCall·chat·chatViewController·clearScreen·configure·current·currentVariables·currentVariablesJSON·errorDomain·forgetMemory·identify·identifyJSON·installId·memories·memoriesJSON·memoryChoice·name·named·noteInteraction·observeEvents·presentBooking·registerAction·registerJSONAction·removeField·removeListener·scheduleCall·scheduleCallJSON·scheduledCalls·scheduledCallsJSON·session·sessionJSON·setMemory·setScreen·setScreenJSON·setVariables·setVariablesJSON·tap·themeJSON·track·trackEvent·updateField·updateFieldJSON·voice·voiceBarViewControllerGraineObjCActionResult—data·error·message·ok·refused·statusGraineObjCCallOutcome—agent·callId·durationSeconds·hosted·json·outcome·reasonGraineObjCCalls—addListener·agent·agentSpeaking·answer·callBack·callerName·callId·callPermissionsJSON·callSetupProblemsJSON·callTransport·configure·decline·direction·dismissMissedCall·enableVoIPPushes·end·endReason·hosted·muted·observe·observeIncoming·observeOutcomes·openCallSettings·removeListener·requestCallPermissionsJSON·setMuted·setSpeaker·showsCallScreen·snapshotJSON·speakerOn·startCall·startCallJSON·state·transport·transportFallbackReasonGraineObjCCallsListener—graineCallOutcome·graineCallsChanged·graineIncomingCallGraineObjCChat—addListener·answerWidget·cancelUpcomingCall·clear·close·isOpen·newConversation·observe·open·reconnectAttempt·refreshUpcomingCalls·removeListener·retry·send·snapshotJSON·status·turns·typing·upcomingCallsGraineObjCChatListener—graineChatChangedGraineObjCChatTurn—at·channel·delivery·identifier·live·proactive·restored·role·textGraineObjCListener—graineEventGraineObjCMemory—createdAt·identifier·text·updatedAtGraineObjCRegistration—unregisterGraineObjCScheduledCall—at·canCancel·identifier·isUpcoming·reason·source·statusGraineObjCSession—agentId·callName·devicesPerUser·hostedRinging·identityField·json·memoryEnabled·usersCanBookGraineObjCVoice—attach·captionsOn·clearCaptions·connected·connecting·end·muted·observe·setCaptionsOn·setMuted·snapshotJSON·startGrainePhase—connecting·idle·listening·reconnecting·speaking·thinkingGraineProtocol—actionDeadlineMs·bridgeName·deliver·engineUrl·messageHandlerName·out·parseGraineProtocol.In—appAction·call·caption·ended·micDenied·muted·network·progress·quality·ready·reconnecting·transport·widget·widgetPendingGraineProtocol.Out—appActionResult·appContext·appEvent·endCall·identity·setMuted·startCall·variables·warm·widgetResultGraineRingPlatform—android·iosGraineRole—agent·customerGraineRTC—installGraineRTCVersion—nameGraineScheduledCall—at·canCancel·id·init·isUpcoming·reason·source·statusGraineSchedulingRules—callName·earliest·hostedRinging·init·latest·maxDaysAhead·maxPendingPerUser·minLeadMinutes·platforms·ringTimeoutSeconds·usersCanBookGraineScreen—availableActions·data·error·fields·init·journey·screen·titleGraineScreen.Journey—init·name·next·of·stepGraineServiceError—code·errorDescription·init·message·retryAfterSeconds·statusGraineSessionConfig—accent·agentId·appearance·calls·declaredActions·design·friction·identity·init·memoryEnabled·pageVersion·preferredTransport·schedulingRules·variables·webcallAgentId·wsHostGraineSurface—chat·voiceGraineThemeTokens—bubbles·call·chat·chrome·colors·elevation·font·glass·icons·launcherGlyphUrl·motion·preset·scheme·shape·spacingGraineThemeTokens.Colors—accent·accentSoft·agentBubble·agentBubbleText·background·border·danger·onAccent·scrim·success·surface·surfaceRaised·text·textMuted·userBubble·userBubbleText·warningGraineThemeTokens.Elevation—card·floating·hardOffsetGraineThemeTokens.Glass—blur·surfaceOpacityGraineThemeTokens.Glyph—id·path·strokeGraineThemeTokens.Icons—launcher·stroke·styleGraineThemeTokens.Motion—bounce·durationMs·easing·effectiveDurationMsGraineThemeTokens.Offset—x·yGraineThemeTokens.Radius—lg·md·sm·xlGraineThemeTokens.Shadow—blur·color·opacity·spread·yGraineThemeTokens.Shape—borderWidth·launcher·radiusGraineThemeTokens.Spacing—cardPadding·gap·unitGraineThemeTokens.Typography—heading·letterSpacing·lineHeight·scale·weightsGraineThemeTokens.Typography.Pair—titleGraineThemeTokens.Typography.Scale—caption·display·titleGraineThemeTokens.Typography.Weights—strong·titleGraineTransport—from·webrtc·websocket·wireGraineVersion—nameGraineVoice—answerWidget·attach·callFailed·captions·captionsOn·clearCaptions·connected·connecting·connectivity·connectStage·detach·end·isAttached·muted·online·phase·quality·ready·reconnecting·reportEvent·retry·setCaptionsOn·setMuted·slowConnect·start·viewClosed·viewOpenedGraineWidget—data·fallbackText·id·init·kind·preset·rawGraineWidgetPalette—border·mode·muted·onAccent·surface·textGraineWidgetPreset—cards·compact·gallery·list·spotlightGraineWidgetSurface—card·screen·sheetGraineWidgetView—initPageMessage—appAction·call·caption·ended·micDenied·muted·network·progress·quality·ready·reconnecting·transport·widget·widgetPendingView—graineBookingSheet·graineTap

