@graineai/inapp-react-native 0.16.0 → 0.18.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/INTEGRATION.md CHANGED
@@ -652,11 +652,37 @@ agent not enabled for apps — leaves the launcher rendering nothing, silently.
652
652
  Pass it before you debug anything else.
653
653
 
654
654
  **On `rtc`: every call reports a fallback to `ws`.**
655
- `onTransport` gives a `reason`. `unreachable` means WebRTC could not be reached
656
- at all — usually no SIP realm on the org, or the SBC is not reachable from the
657
- customer's network. `screen_channel` means the audio connected but the control
658
- socket did not join, so the call was restarted on the WebSocket rather than run
659
- blind. Both are org configuration, not integration code.
655
+ `onTransport` gives a `reason`, and `message` carries the server's own sentence.
656
+
657
+ | `reason` | Means | Retry helps? |
658
+ |---|---|---|
659
+ | `not_configured` | A setting is wrong — see below. Also raised through `onError`. | Never |
660
+ | `unreachable` | No SIP realm on the org, or the SBC is not reachable from this network. | Sometimes |
661
+ | `screen_channel` | Audio connected, the screen channel did not, so the call restarted rather than run blind. | Sometimes |
662
+
663
+ **`not_configured`: which setting?** The call still works — it is carried on the
664
+ WebSocket — so nothing is on fire, but WebRTC will never be used until this is
665
+ fixed. Ask the mint endpoint directly, without rebuilding anything:
666
+
667
+ ```bash
668
+ curl -i -X POST https://www.graine.ai/api/embed/rtc-session \
669
+ -H 'Content-Type: application/json' \
670
+ -d '{"publishableKey":"pk_live_YOUR_KEY"}'
671
+ ```
672
+
673
+ | Response | `code` | Fix |
674
+ |---|---|---|
675
+ | 401 | `key_missing` | No key reached the endpoint. Update the SDK — before 0.17.0 it did not put `pk` in the page URL. |
676
+ | 401 | `key_malformed` | Not a publishable key. It starts with `pk_live_`, from Agent → Embed & Widgets. |
677
+ | 401 | `key_unknown` | No agent registered to that key. |
678
+ | 403 | `no_origin` | **The common one for apps.** A native app sends no `Origin`, so add the literal entry `app://` to that agent's Allowed domains. |
679
+ | 403 | `no_domains_configured` | The allowlist is empty, which denies everything. |
680
+ | 403 | `origin_not_allowed` | Add your site to Allowed domains. |
681
+ | 503 | `not_configured` | WebRTC is not enabled on that deployment. Not fixable from the app. |
682
+ | 200 | — | The mint works; look at `onTransport` for `unreachable` instead. |
683
+
684
+ That curl sends **no** `Origin` header, exactly as a native app does — so a 403
685
+ from it is precisely the 403 your app is getting.
660
686
 
661
687
  **On `rtc`: the greeting plays twice, occasionally.**
662
688
  That is the `screen_channel` fallback restarting the call. It only happens when
@@ -4,6 +4,8 @@ export interface GraineVoiceLauncherProps {
4
4
  onTransport?: (info: {
5
5
  transport: "ws" | "rtc";
6
6
  requested: "ws" | "rtc";
7
+ reason?: "not_configured" | "unreachable" | "screen_channel";
8
+ message?: string;
7
9
  }) => void;
8
10
  webView: React.ComponentType<any>;
9
11
  autoStart?: boolean;
@@ -25,6 +27,7 @@ export interface GraineVoiceApi {
25
27
  connected: boolean;
26
28
  connecting: boolean;
27
29
  muted: boolean;
30
+ ready: boolean;
28
31
  start: () => void;
29
32
  end: () => void;
30
33
  setMuted: (m: boolean) => void;
@@ -106,12 +106,18 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, trans
106
106
  onTransport?.({
107
107
  transport: msg.transport === "rtc" ? "rtc" : "ws",
108
108
  requested: msg.requested === "rtc" ? "rtc" : "ws",
109
+ reason: msg.reason,
110
+ message: msg.message,
109
111
  });
112
+ if (msg.reason === "not_configured" && msg.message) {
113
+ onError?.(String(msg.message));
114
+ }
110
115
  return;
111
116
  case "graine:ready":
112
117
  setReady(true);
113
118
  pushContext();
114
- if (autoStart && !startedRef.current) {
119
+ if ((autoStart || pendingStartRef.current) && !startedRef.current) {
120
+ pendingStartRef.current = false;
115
121
  startedRef.current = true;
116
122
  void beginCall();
117
123
  }
@@ -150,15 +156,21 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, trans
150
156
  return;
151
157
  }
152
158
  }
153
- }, [autoStart, beginCall, client, onCaption, onCallState, onMicDenied, onTransport, onEnded, post, pushContext]);
159
+ }, [autoStart, beginCall, client, onCaption, onCallState, onMicDenied, onTransport, onError, onEnded, post, pushContext]);
160
+ const pendingStartRef = useRef(false);
154
161
  const api = {
155
162
  connected,
156
163
  connecting,
157
164
  muted,
165
+ ready: !!voiceAgentId && ready,
158
166
  start: useCallback(() => {
159
167
  setConnecting(true);
168
+ if (!ready) {
169
+ pendingStartRef.current = true;
170
+ return;
171
+ }
160
172
  void beginCall();
161
- }, [beginCall]),
173
+ }, [beginCall, ready]),
162
174
  end: useCallback(() => post({ type: "graine:end-call" }), [post]),
163
175
  setMuted: useCallback((m) => {
164
176
  setMutedState(m);
@@ -196,6 +208,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, trans
196
208
  const effectiveTransport = transport ?? (appearance?.audioTransport === "rtc" ? "rtc" : "ws");
197
209
  const src = `${client.opts.baseUrl.replace(/\/$/, "")}/embed/${voiceAgentId}` +
198
210
  `?mode=voice&branding=0&embed=1&transport=${effectiveTransport}` +
211
+ `&pk=${encodeURIComponent(client.opts.publishableKey)}` +
199
212
  (appearance?.accent ? `&accent=${encodeURIComponent(String(appearance.accent).replace("#", ""))}` : "");
200
213
  return (_jsxs(_Fragment, { children: [_jsx(View, { style: { position: "absolute", width: 1, height: 1, opacity: 0, bottom: 0, left: 0 }, pointerEvents: "none", children: _jsx(WebView, { ref: ref, source: { uri: src }, originWhitelist: ["*"], javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, mediaCapturePermissionGrantType: "grant", onMessage: onMessage, style: { width: 1, height: 1, backgroundColor: "transparent" } }) }), children?.(api)] }));
201
214
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@graineai/inapp-react-native",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
- "description": "Graine in-app agent for React Native an agent that sees the screen your customer is on and can act on it.",
5
+ "description": "Graine in-app agent for React Native \u2014 an agent that sees the screen your customer is on and can act on it.",
6
6
  "license": "MIT",
7
7
  "private": false,
8
8
  "main": "dist/index.js",