@ringg/react-native 0.0.1-alpha.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -197
- package/dist/index.d.mts +1355 -0
- package/dist/index.d.ts +1355 -0
- package/dist/index.js +5765 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5705 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -65
- package/LICENSE +0 -21
- package/app.plugin.js +0 -51
- package/lib/module/adapters/index.js +0 -4
- package/lib/module/adapters/index.js.map +0 -1
- package/lib/module/adapters/livekit-rn-adapter.js +0 -216
- package/lib/module/adapters/livekit-rn-adapter.js.map +0 -1
- package/lib/module/components/RinggWidget.js +0 -554
- package/lib/module/components/RinggWidget.js.map +0 -1
- package/lib/module/components/steps/ButtonsStep.js +0 -146
- package/lib/module/components/steps/ButtonsStep.js.map +0 -1
- package/lib/module/components/steps/CalendarStep.js +0 -190
- package/lib/module/components/steps/CalendarStep.js.map +0 -1
- package/lib/module/components/steps/ConfirmationStep.js +0 -175
- package/lib/module/components/steps/ConfirmationStep.js.map +0 -1
- package/lib/module/components/steps/FormStep.js +0 -346
- package/lib/module/components/steps/FormStep.js.map +0 -1
- package/lib/module/components/steps/InteractiveFlow.js +0 -249
- package/lib/module/components/steps/InteractiveFlow.js.map +0 -1
- package/lib/module/components/ui/ActionButton.js +0 -92
- package/lib/module/components/ui/ActionButton.js.map +0 -1
- package/lib/module/components/ui/CallControls.js +0 -168
- package/lib/module/components/ui/CallControls.js.map +0 -1
- package/lib/module/components/ui/FeedbackScreen.js +0 -180
- package/lib/module/components/ui/FeedbackScreen.js.map +0 -1
- package/lib/module/components/ui/Icons.js +0 -294
- package/lib/module/components/ui/Icons.js.map +0 -1
- package/lib/module/components/ui/MessageBubble.js +0 -80
- package/lib/module/components/ui/MessageBubble.js.map +0 -1
- package/lib/module/components/ui/TabSelector.js +0 -80
- package/lib/module/components/ui/TabSelector.js.map +0 -1
- package/lib/module/components/ui/WidgetHeader.js +0 -168
- package/lib/module/components/ui/WidgetHeader.js.map +0 -1
- package/lib/module/context/RinggProvider.js +0 -134
- package/lib/module/context/RinggProvider.js.map +0 -1
- package/lib/module/context/index.js +0 -4
- package/lib/module/context/index.js.map +0 -1
- package/lib/module/globals.d.js +0 -2
- package/lib/module/globals.d.js.map +0 -1
- package/lib/module/hooks/index.js +0 -10
- package/lib/module/hooks/index.js.map +0 -1
- package/lib/module/hooks/useRinggCall.js +0 -259
- package/lib/module/hooks/useRinggCall.js.map +0 -1
- package/lib/module/hooks/useRinggChat.js +0 -141
- package/lib/module/hooks/useRinggChat.js.map +0 -1
- package/lib/module/hooks/useRinggComponents.js +0 -236
- package/lib/module/hooks/useRinggComponents.js.map +0 -1
- package/lib/module/hooks/useRinggFeedback.js +0 -66
- package/lib/module/hooks/useRinggFeedback.js.map +0 -1
- package/lib/module/hooks/useRinggTheme.js +0 -39
- package/lib/module/hooks/useRinggTheme.js.map +0 -1
- package/lib/module/hooks/useRinggTranscripts.js +0 -70
- package/lib/module/hooks/useRinggTranscripts.js.map +0 -1
- package/lib/module/hooks/useRinggWidgetState.js +0 -51
- package/lib/module/hooks/useRinggWidgetState.js.map +0 -1
- package/lib/module/index.js +0 -56
- package/lib/module/index.js.map +0 -1
- package/lib/module/package.json +0 -1
- package/lib/typescript/adapters/index.d.ts +0 -2
- package/lib/typescript/adapters/index.d.ts.map +0 -1
- package/lib/typescript/adapters/livekit-rn-adapter.d.ts +0 -38
- package/lib/typescript/adapters/livekit-rn-adapter.d.ts.map +0 -1
- package/lib/typescript/components/RinggWidget.d.ts +0 -44
- package/lib/typescript/components/RinggWidget.d.ts.map +0 -1
- package/lib/typescript/components/steps/ButtonsStep.d.ts +0 -17
- package/lib/typescript/components/steps/ButtonsStep.d.ts.map +0 -1
- package/lib/typescript/components/steps/CalendarStep.d.ts +0 -16
- package/lib/typescript/components/steps/CalendarStep.d.ts.map +0 -1
- package/lib/typescript/components/steps/ConfirmationStep.d.ts +0 -16
- package/lib/typescript/components/steps/ConfirmationStep.d.ts.map +0 -1
- package/lib/typescript/components/steps/FormStep.d.ts +0 -17
- package/lib/typescript/components/steps/FormStep.d.ts.map +0 -1
- package/lib/typescript/components/steps/InteractiveFlow.d.ts +0 -23
- package/lib/typescript/components/steps/InteractiveFlow.d.ts.map +0 -1
- package/lib/typescript/components/ui/ActionButton.d.ts +0 -22
- package/lib/typescript/components/ui/ActionButton.d.ts.map +0 -1
- package/lib/typescript/components/ui/CallControls.d.ts +0 -22
- package/lib/typescript/components/ui/CallControls.d.ts.map +0 -1
- package/lib/typescript/components/ui/FeedbackScreen.d.ts +0 -24
- package/lib/typescript/components/ui/FeedbackScreen.d.ts.map +0 -1
- package/lib/typescript/components/ui/Icons.d.ts +0 -28
- package/lib/typescript/components/ui/Icons.d.ts.map +0 -1
- package/lib/typescript/components/ui/MessageBubble.d.ts +0 -17
- package/lib/typescript/components/ui/MessageBubble.d.ts.map +0 -1
- package/lib/typescript/components/ui/TabSelector.d.ts +0 -21
- package/lib/typescript/components/ui/TabSelector.d.ts.map +0 -1
- package/lib/typescript/components/ui/WidgetHeader.d.ts +0 -20
- package/lib/typescript/components/ui/WidgetHeader.d.ts.map +0 -1
- package/lib/typescript/context/RinggProvider.d.ts +0 -53
- package/lib/typescript/context/RinggProvider.d.ts.map +0 -1
- package/lib/typescript/context/index.d.ts +0 -3
- package/lib/typescript/context/index.d.ts.map +0 -1
- package/lib/typescript/hooks/index.d.ts +0 -15
- package/lib/typescript/hooks/index.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggCall.d.ts +0 -41
- package/lib/typescript/hooks/useRinggCall.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggChat.d.ts +0 -29
- package/lib/typescript/hooks/useRinggChat.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggComponents.d.ts +0 -52
- package/lib/typescript/hooks/useRinggComponents.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggFeedback.d.ts +0 -23
- package/lib/typescript/hooks/useRinggFeedback.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggTheme.d.ts +0 -40
- package/lib/typescript/hooks/useRinggTheme.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggTranscripts.d.ts +0 -30
- package/lib/typescript/hooks/useRinggTranscripts.d.ts.map +0 -1
- package/lib/typescript/hooks/useRinggWidgetState.d.ts +0 -38
- package/lib/typescript/hooks/useRinggWidgetState.d.ts.map +0 -1
- package/lib/typescript/index.d.ts +0 -44
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/package.json +0 -1
package/README.md
CHANGED
|
@@ -1,250 +1,290 @@
|
|
|
1
1
|
# @ringg/react-native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> ⚠️ **Alpha / pre-release.** Expect rough edges and breaking changes between
|
|
4
|
+
> versions. Pin an exact version if you need stability, and please report
|
|
5
|
+
> anything you hit. See **[Known issues](#known-issues)** below.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Embeddable chat + voice-call widget for [Ringg AI](https://ringg.ai) agents —
|
|
8
|
+
the React Native implementation. Drop `<RinggWidget />` over your app for text
|
|
9
|
+
chat, voice calls, interactive components (forms, calendars, quick replies,
|
|
10
|
+
Block Kit) and a post-call feedback screen.
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
It runs on the same headless brain as the web widget (`@ringg/core`, bundled
|
|
13
|
+
into this package), so conversation behaviour — reconnection, typing timing,
|
|
14
|
+
message ordering, optimistic sends — is identical across platforms. Only the
|
|
15
|
+
views are native.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
8
18
|
|
|
9
19
|
```bash
|
|
10
20
|
npm install @ringg/react-native
|
|
11
21
|
```
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
In your `app.json`:
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"expo": {
|
|
20
|
-
"plugins": ["@ringg/react-native"]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### 3. Rebuild
|
|
26
|
-
|
|
27
|
-
Voice calls require native modules, so you need a **development build** (Expo Go is not supported).
|
|
23
|
+
Then the peer dependencies, which must be installed in **your** app so there is
|
|
24
|
+
exactly one autolinked copy of each native module:
|
|
28
25
|
|
|
29
26
|
```bash
|
|
30
|
-
npx expo
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
npx expo run:android
|
|
27
|
+
npx expo install @livekit/react-native @livekit/react-native-webrtc livekit-client react-native-svg
|
|
28
|
+
# or, without Expo:
|
|
29
|
+
npm install @livekit/react-native @livekit/react-native-webrtc livekit-client react-native-svg
|
|
34
30
|
```
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Add a complete voice/text chat widget with one component. It renders a floating action button that opens an animated chat panel.
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
import { RinggWidget } from "@ringg/react-native";
|
|
42
|
-
|
|
43
|
-
export default function App() {
|
|
44
|
-
return (
|
|
45
|
-
<RinggWidget
|
|
46
|
-
config={{
|
|
47
|
-
agentId: "your-agent-id",
|
|
48
|
-
xApiKey: "your-api-key",
|
|
49
|
-
variables: {
|
|
50
|
-
callee_name: "Jane Doe",
|
|
51
|
-
},
|
|
52
|
-
}}
|
|
53
|
-
/>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
```
|
|
32
|
+
Requires React ≥ 18 · React Native ≥ 0.73.
|
|
57
33
|
|
|
58
|
-
|
|
34
|
+
**Expo Go will not work** — LiveKit ships native code, so you need a
|
|
35
|
+
[development build](https://docs.expo.dev/develop/development-builds/introduction/).
|
|
59
36
|
|
|
60
|
-
|
|
61
|
-
| ------------- | -------------------------- | ----------------------------------------------------------- |
|
|
62
|
-
| `config` | `RinggWidgetConfig` | Agent ID, API key, variables, and customization (see below) |
|
|
63
|
-
| `onEvent` | `(event, payload) => void` | Callback for widget lifecycle events |
|
|
64
|
-
| `triggerSize` | `number` | FAB button size in pixels (default: `56`) |
|
|
37
|
+
## Platform setup (required for voice)
|
|
65
38
|
|
|
66
|
-
|
|
39
|
+
**Expo** — add the LiveKit plugin and the permissions to `app.json`:
|
|
67
40
|
|
|
68
|
-
```
|
|
41
|
+
```json
|
|
69
42
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
43
|
+
"expo": {
|
|
44
|
+
"plugins": ["@livekit/react-native-expo-plugin"],
|
|
45
|
+
"ios": {
|
|
46
|
+
"infoPlist": {
|
|
47
|
+
"NSMicrophoneUsageDescription": "Voice calls use the microphone.",
|
|
48
|
+
"UIBackgroundModes": ["audio"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"android": {
|
|
52
|
+
"permissions": [
|
|
53
|
+
"android.permission.RECORD_AUDIO",
|
|
54
|
+
"android.permission.MODIFY_AUDIO_SETTINGS",
|
|
55
|
+
"android.permission.ACCESS_NETWORK_STATE",
|
|
56
|
+
"android.permission.BLUETOOTH_CONNECT"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
86
60
|
}
|
|
87
61
|
```
|
|
88
62
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
63
|
+
**Bare React Native** — the same keys, by hand: `NSMicrophoneUsageDescription`
|
|
64
|
+
and `UIBackgroundModes: [audio]` in `ios/<App>/Info.plist`, and the four
|
|
65
|
+
permissions above in `android/app/src/main/AndroidManifest.xml`.
|
|
92
66
|
|
|
93
|
-
|
|
94
|
-
<RinggWidget
|
|
95
|
-
config={config}
|
|
96
|
-
onEvent={(event, payload) => {
|
|
97
|
-
switch (event) {
|
|
98
|
-
case "ringg:conversation_status":
|
|
99
|
-
// { status: "started" | "ended", mode: "audio" | "text", callId: string }
|
|
100
|
-
break;
|
|
101
|
-
case "ringg:widget_status":
|
|
102
|
-
// { status: "maximised" | "minimised", mode: "audio" | "text" }
|
|
103
|
-
break;
|
|
104
|
-
case "ringg:feedback_status":
|
|
105
|
-
// { status: "submitted" | "skipped", callId: string, rating?: number }
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}}
|
|
109
|
-
/>
|
|
110
|
-
```
|
|
67
|
+
## Integrate in 3 steps
|
|
111
68
|
|
|
112
|
-
|
|
69
|
+
### 1 · Register the WebRTC globals
|
|
113
70
|
|
|
114
|
-
|
|
71
|
+
Once, at your app entry, **before** anything imports LiveKit:
|
|
115
72
|
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<RinggProvider
|
|
122
|
-
config={{
|
|
123
|
-
agentId: "your-agent-id",
|
|
124
|
-
xApiKey: "your-api-key",
|
|
125
|
-
variables: {},
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
<ChatScreen />
|
|
129
|
-
</RinggProvider>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
73
|
+
```ts
|
|
74
|
+
// index.js
|
|
75
|
+
import { registerGlobals } from "@livekit/react-native";
|
|
76
|
+
registerGlobals();
|
|
77
|
+
```
|
|
132
78
|
|
|
133
|
-
|
|
134
|
-
const call = useRinggCall();
|
|
135
|
-
const chat = useRinggChat();
|
|
79
|
+
### 2 · Build a transport and a controller
|
|
136
80
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
81
|
+
```tsx
|
|
82
|
+
import { useEffect, useMemo } from "react";
|
|
83
|
+
import {
|
|
84
|
+
RinggWidget,
|
|
85
|
+
createLiveKitTransport,
|
|
86
|
+
createNativeMicPermission,
|
|
87
|
+
createRinggWidgetController,
|
|
88
|
+
} from "@ringg/react-native";
|
|
89
|
+
import { createStaticUrlResolver } from "@ringg/core";
|
|
90
|
+
|
|
91
|
+
const URLS = {
|
|
92
|
+
dev: { backendUrl: "https://calling-dev.ringg.ai/ca/api/v0", livekitUrl: "wss://ringg-ai-dev-92tubwpz.livekit.cloud" },
|
|
93
|
+
stage: { backendUrl: "https://stage-api.ringg.ai/ca/api/v0", livekitUrl: "wss://mercury.webrtc-stage.ringg.ai" },
|
|
94
|
+
prod: { backendUrl: "https://prod-api.ringg.ai/ca/api/v0", livekitUrl: "wss://mercury.webrtc.ringg.ai" },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const RinggSupport = () => {
|
|
98
|
+
const { controller, livekit } = useMemo(() => {
|
|
99
|
+
const livekit = createLiveKitTransport();
|
|
100
|
+
const controller = createRinggWidgetController(
|
|
101
|
+
{
|
|
102
|
+
agentId: "<your-agent-id>",
|
|
103
|
+
authorization: "Bearer <your-token>",
|
|
104
|
+
title: "Support",
|
|
105
|
+
description: "How can we help?",
|
|
106
|
+
defaultTab: "text", // or "audio"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
transport: livekit.transport,
|
|
110
|
+
urlResolver: createStaticUrlResolver(URLS),
|
|
111
|
+
micPermission: createNativeMicPermission(),
|
|
112
|
+
},
|
|
143
113
|
);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<View style={{ flex: 1 }}>
|
|
148
|
-
<FlatList data={chat.messages} keyExtractor={(item) => String(item.timestamp)} renderItem={({ item }) => <Text style={{ alignSelf: item.isSelf ? "flex-end" : "flex-start" }}>{item.message}</Text>} />
|
|
149
|
-
|
|
150
|
-
{call.callMode === "audio" && <Button title={call.isMicEnabled ? "Mute" : "Unmute"} onPress={call.toggleMic} />}
|
|
114
|
+
return { controller, livekit };
|
|
115
|
+
}, []);
|
|
151
116
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
117
|
+
// Releases the microphone and the audio session — do not skip this.
|
|
118
|
+
useEffect(() => () => {
|
|
119
|
+
controller.destroy();
|
|
120
|
+
livekit.dispose();
|
|
121
|
+
}, [controller, livekit]);
|
|
160
122
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
164
|
-
}
|
|
123
|
+
return <RinggWidget controller={controller} room={livekit.room} />;
|
|
124
|
+
};
|
|
165
125
|
```
|
|
166
126
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Controls call lifecycle and microphone.
|
|
170
|
-
|
|
171
|
-
| Property | Type | Description |
|
|
172
|
-
| ----------------- | ------------------------------------------------- | --------------------------------------- |
|
|
173
|
-
| `startCall(mode)` | `(mediaType: "audio" \| "text") => Promise<void>` | Start a voice or text session |
|
|
174
|
-
| `endCall()` | `() => Promise<void>` | End the current session |
|
|
175
|
-
| `isConnected` | `boolean` | Whether the agent is connected |
|
|
176
|
-
| `isLoading` | `boolean` | Loading state during connect/disconnect |
|
|
177
|
-
| `connectionState` | `"connected" \| "disconnected" \| "reconnecting"` | Detailed connection state |
|
|
178
|
-
| `callMode` | `"audio" \| "text"` | Current session mode |
|
|
179
|
-
| `callId` | `string \| null` | Current call ID |
|
|
180
|
-
| `isMicEnabled` | `boolean` | Microphone state |
|
|
181
|
-
| `toggleMic()` | `() => Promise<void>` | Toggle microphone on/off |
|
|
182
|
-
| `error` | `string \| null` | Error message if connection failed |
|
|
183
|
-
| `clearError()` | `() => void` | Clear the error state |
|
|
184
|
-
|
|
185
|
-
### useRinggChat()
|
|
186
|
-
|
|
187
|
-
Send and receive text messages.
|
|
188
|
-
|
|
189
|
-
| Property | Type | Description |
|
|
190
|
-
| ------------------- | --------------------------------- | ----------------------------------- |
|
|
191
|
-
| `messages` | `ChatMessage[]` | All messages in chronological order |
|
|
192
|
-
| `sendMessage(text)` | `(text: string) => Promise<void>` | Send a message to the agent |
|
|
193
|
-
| `clearMessages()` | `() => void` | Clear message history |
|
|
127
|
+
Passing `room` is optional; it only enables the in-call audio visualizer.
|
|
194
128
|
|
|
195
|
-
|
|
129
|
+
### 3 · Mount it over your app
|
|
196
130
|
|
|
197
|
-
|
|
131
|
+
`RinggWidget` renders its own floating trigger and panel over whatever is
|
|
132
|
+
behind it, so make it the **last child** of your root view:
|
|
198
133
|
|
|
199
|
-
|
|
134
|
+
```tsx
|
|
135
|
+
<View style={{ flex: 1 }}>
|
|
136
|
+
<YourApp />
|
|
137
|
+
<RinggSupport />
|
|
138
|
+
</View>
|
|
139
|
+
```
|
|
200
140
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
141
|
+
Tap the trigger → the chat/voice panel opens. That is the whole integration.
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
`RinggWidgetConfig` — only `agentId` is required:
|
|
146
|
+
|
|
147
|
+
| Field | Type | Purpose |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `agentId` | `string` | your Ringg agent (**required**) |
|
|
150
|
+
| `authorization` | `string` | bearer token for your account |
|
|
151
|
+
| `title` / `description` | `string` | panel header text |
|
|
152
|
+
| `defaultTab` | `"audio" \| "text"` | which mode the panel opens in |
|
|
153
|
+
| `hideTabSelector` | `boolean` | pin the widget to one mode |
|
|
154
|
+
| `defaultExpanded` | `boolean` | open the panel on mount (no trigger) |
|
|
155
|
+
| `bypassStartScreen` | `boolean` | trigger tap starts the call directly |
|
|
156
|
+
| `bypassFeedbackScreen` | `boolean` | skip the post-call rating screen |
|
|
157
|
+
| `clientOrigin` | `string` | **required for real calls** — see below |
|
|
158
|
+
| `variables` | `Record<string, …>` | values for `{{placeholders}}` in agent prompts |
|
|
159
|
+
| `theme` | `WidgetTheme` | colours, radii, button style (gradients supported) |
|
|
160
|
+
| `logoUrl` / `logoStyles` | `string` / `PortableStyles` | branding in the header |
|
|
161
|
+
| `buttons` | `ButtonsConfig` | per-button copy, icons and styles |
|
|
162
|
+
| `legalDisclaimer` | `{ text, links }` | copy under the start buttons |
|
|
163
|
+
| `feedbackScreen` | `FeedbackScreenConfig` | rating screen copy and styling |
|
|
164
|
+
| `voiceCall` | `{ showAnimation, showTranscript }` | voice view options |
|
|
165
|
+
| `enabledSlashCommands` | `SlashCommand[]` | commands offered in the composer |
|
|
166
|
+
| `eventLogs` | `{ enabled, showIds }` | inline pills for agent-triggered actions |
|
|
167
|
+
|
|
168
|
+
`widgetPosition` and `innerWindowProps` are web-only and ignored here — the
|
|
169
|
+
panel sizes itself to the device.
|
|
170
|
+
|
|
171
|
+
## Caller identity — required for real calls
|
|
172
|
+
|
|
173
|
+
The backend allow-lists an agent's callers by the `Origin` header. A browser
|
|
174
|
+
sends it automatically; a native app sends nothing, so the webcall request is
|
|
175
|
+
rejected **before authentication is even considered**:
|
|
176
|
+
|
|
177
|
+
| response | meaning |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `400 Origin header is required` | no `clientOrigin` was set |
|
|
180
|
+
| `403 Client '…' is not allowed` | it was set, but is not on the agent's list |
|
|
181
|
+
| `401 Invalid credentials` | the token is wrong for that environment |
|
|
182
|
+
|
|
183
|
+
Pass your app's identity, and add the same string to the agent's allowed
|
|
184
|
+
clients in the dashboard:
|
|
204
185
|
|
|
205
|
-
|
|
186
|
+
```ts
|
|
187
|
+
import { Platform } from "react-native";
|
|
188
|
+
import { appOrigin, createRinggWidgetController } from "@ringg/react-native";
|
|
206
189
|
|
|
207
|
-
|
|
190
|
+
const BUNDLE_ID = Platform.OS === "android" ? "com.acme.app" : "com.acme.App";
|
|
208
191
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
192
|
+
createRinggWidgetController(
|
|
193
|
+
{ agentId: "…", authorization: "Bearer …", clientOrigin: appOrigin(BUNDLE_ID) },
|
|
194
|
+
ports,
|
|
195
|
+
);
|
|
196
|
+
```
|
|
214
197
|
|
|
215
|
-
|
|
198
|
+
`appOrigin` returns `<platform>://<bundleId>` — e.g. `android://com.acme.app`.
|
|
199
|
+
The bundle id is a parameter because React Native cannot read it without a
|
|
200
|
+
native module, and this package will not add one for a single string. Your app
|
|
201
|
+
already declares it, so a constant (or a read of your own app config) is
|
|
202
|
+
enough. `expo-application` reports the same value if you would rather ask the
|
|
203
|
+
OS — but it is native code, so adding it requires rebuilding the app, not just
|
|
204
|
+
restarting the bundler.
|
|
216
205
|
|
|
217
|
-
|
|
206
|
+
## Host events
|
|
218
207
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
| `theme` | `ResolvedWidgetTheme` | Full theme with defaults applied |
|
|
222
|
-
| `getSolidColor(color)` | `(color: string) => string` | Extract solid color from gradient |
|
|
223
|
-
| `buttonRadius` | `string` | Border radius for the current button style |
|
|
224
|
-
| `getContrastText(bg)` | `(bgColor: string) => string` | Get black or white text for a background |
|
|
208
|
+
The controller emits the same events as the web widget. On RN they are
|
|
209
|
+
in-memory rather than DOM events:
|
|
225
210
|
|
|
226
|
-
|
|
211
|
+
```ts
|
|
212
|
+
const unsubscribe = controller.eventBus.on("ringg:conversation_status", ({ status, mode, callId }) => {
|
|
213
|
+
analytics.track(`call_${status}`, { mode, callId });
|
|
214
|
+
});
|
|
215
|
+
```
|
|
227
216
|
|
|
228
|
-
|
|
217
|
+
Events: `ringg:widget_status`, `ringg:conversation_status`,
|
|
218
|
+
`ringg:feedback_status`, `ringg:calendar_booking`,
|
|
219
|
+
`ringg:component_acknowledgement`.
|
|
229
220
|
|
|
230
|
-
|
|
231
|
-
import { MessageBubble, WidgetHeader, CallControls, ActionButton, FeedbackScreen, TabSelector } from "@ringg/react-native";
|
|
232
|
-
```
|
|
221
|
+
## Agent-triggered app actions
|
|
233
222
|
|
|
234
|
-
|
|
223
|
+
Agents can fire host actions (`execute_dom_action` on the wire). On web these
|
|
224
|
+
become `CustomEvent`s; RN has no ambient event bus, so you supply the handler
|
|
225
|
+
and receive the same payload:
|
|
235
226
|
|
|
236
|
-
```
|
|
237
|
-
|
|
227
|
+
```ts
|
|
228
|
+
import { createHostActionDispatcher } from "@ringg/react-native";
|
|
229
|
+
|
|
230
|
+
const ports = {
|
|
231
|
+
// ...
|
|
232
|
+
onDomAction: createHostActionDispatcher(({ name, payload }) => {
|
|
233
|
+
if (name === "open_checkout") navigation.navigate("Checkout", payload);
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
238
236
|
```
|
|
239
237
|
|
|
240
|
-
|
|
241
|
-
| -------------- | --------- | ------- | ---------------------------------------------------- |
|
|
242
|
-
| `enableCamera` | `boolean` | `false` | Request camera permission (for future video support) |
|
|
238
|
+
## Notification sound
|
|
243
239
|
|
|
244
|
-
|
|
240
|
+
React Native has no audio playback of its own, and every option is a native
|
|
241
|
+
module — so the widget ships **silent** rather than forcing a dependency on
|
|
242
|
+
every integrator. Wire whichever player your app already has:
|
|
245
243
|
|
|
246
|
-
|
|
244
|
+
```ts
|
|
245
|
+
import { createAudioPlayer } from "expo-audio";
|
|
246
|
+
import { createNotificationPlayer } from "@ringg/react-native";
|
|
247
|
+
import { DEFAULT_CONFIG } from "@ringg/core";
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
const notification = createNotificationPlayer(DEFAULT_CONFIG.notificationTuneUrl, (url) => createAudioPlayer(url).play());
|
|
250
|
+
```
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
Pass it as `ports.notification`.
|
|
253
|
+
|
|
254
|
+
## Testing hooks
|
|
255
|
+
|
|
256
|
+
Every meaningful node carries a `testID` mirroring the web widget's
|
|
257
|
+
`data-ringg` name, prefixed with `ringg-` — `ringg-trigger-button`,
|
|
258
|
+
`ringg-widget-root`, `ringg-header-title`, `ringg-message-input`,
|
|
259
|
+
`ringg-end-call-confirm`, and so on. These are a contract: they will not be
|
|
260
|
+
renamed without a major version.
|
|
261
|
+
|
|
262
|
+
Two web names have no RN counterpart, because they mark screen-reader-only
|
|
263
|
+
nodes and RN has no visually-hidden text: `ringg-header-status` and
|
|
264
|
+
`ringg-connecting-label`'s `sr-only` sibling. That copy lives in
|
|
265
|
+
`accessibilityLabel` on the surrounding node instead.
|
|
266
|
+
|
|
267
|
+
## Example app
|
|
268
|
+
|
|
269
|
+
A runnable Expo harness lives in [`example/`](./example) — it is the reference
|
|
270
|
+
integration and runs fully offline (mock transport + in-process backend) when
|
|
271
|
+
no credentials are configured.
|
|
272
|
+
|
|
273
|
+
## Known issues
|
|
274
|
+
|
|
275
|
+
- **Breaking changes between releases.** APIs may shift while pre-1.0.
|
|
276
|
+
- **No frosted-glass blur.** The web widget's header and composer blur what
|
|
277
|
+
scrolls under them; RN has no blur primitive without a native dependency, so
|
|
278
|
+
those surfaces are near-opaque instead.
|
|
279
|
+
- **No gradient-filled text.** The typing indicator's shimmer sweeps a gradient
|
|
280
|
+
through the glyphs on web. RN cannot fill text with a gradient without a
|
|
281
|
+
masking dependency, so the label takes the colour that sweep averages to and
|
|
282
|
+
the motion moves into the animated ellipsis beside it.
|
|
283
|
+
- **Markdown is a subset.** Agent replies render bold, italic, inline code,
|
|
284
|
+
links, lists, headings, code blocks, blockquotes and rules. Tables are not
|
|
285
|
+
supported and render as plain text.
|
|
286
|
+
- **Voice on emulators is unreliable.** Android emulator networking often
|
|
287
|
+
cannot establish the media connection, and iOS simulators expose no
|
|
288
|
+
microphone or playout device. Test voice on a real device.
|
|
289
|
+
- **The policy-finder pack is web-only.** That integrator-specific flow is not
|
|
290
|
+
part of this package.
|