@graineai/inapp-react-native 0.30.0 → 0.32.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/dist/client.d.ts CHANGED
@@ -63,6 +63,9 @@ export declare class GraineInAppClient {
63
63
  configureFriction(override: FrictionConfig | null | undefined): void;
64
64
  get friction(): Required<FrictionConfig>;
65
65
  on(event: string, fn: Listener): () => void;
66
+ private lastWidget;
67
+ get pendingWidget(): any;
68
+ clearPendingWidget(): void;
66
69
  emit(event: string, payload?: any): void;
67
70
  private get fetch();
68
71
  session(): Promise<SessionConfig>;
package/dist/client.js CHANGED
@@ -28,6 +28,7 @@ export class GraineInAppClient {
28
28
  this.identity = {};
29
29
  this.appVersion = null;
30
30
  this.recentEvents = [];
31
+ this.lastWidget = null;
31
32
  if (!opts?.publishableKey)
32
33
  throw new Error("[Graine] publishableKey is required.");
33
34
  if (!opts?.baseUrl)
@@ -57,7 +58,15 @@ export class GraineInAppClient {
57
58
  this.listeners.get(event).add(fn);
58
59
  return () => this.listeners.get(event)?.delete(fn);
59
60
  }
61
+ get pendingWidget() {
62
+ return this.lastWidget;
63
+ }
64
+ clearPendingWidget() {
65
+ this.lastWidget = null;
66
+ }
60
67
  emit(event, payload) {
68
+ if (event === "widget")
69
+ this.lastWidget = payload ?? null;
61
70
  for (const fn of this.listeners.get(event) ?? []) {
62
71
  try {
63
72
  fn(payload);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export type { ActionHandler, AppActionRequest, AppActionResult, AppEvent, ScreenContext, ScreenField, } from "./protocol.js";
1
+ export type { ActionHandler, AppActionRequest, AppActionResult, AppEvent, ScreenContext, ScreenField, WidgetSurface, } from "./protocol.js";
2
+ export { widgetSurface } from "./protocol.js";
2
3
  export { GraineInAppClient, type GraineInAppOptions, type SessionConfig, type ActionDiagnosis, } from "./client.js";
3
4
  export { FrictionDetector, resolveFriction, rulesFor, DEFAULT_FRICTION, type FrictionConfig, type FrictionScreenRules, type FrictionNudge, type FrictionSignal, type FrictionKind, } from "./friction.js";
4
5
  export { VoiceSession, liveAudioStreamAdapter, base64ToPcm16, CAPTURE_SAMPLE_RATE, type AudioAdapter, type VoiceSessionOptions, } from "./voice.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { widgetSurface } from "./protocol.js";
1
2
  export { GraineInAppClient, } from "./client.js";
2
3
  export { FrictionDetector, resolveFriction, rulesFor, DEFAULT_FRICTION, } from "./friction.js";
3
4
  export { VoiceSession, liveAudioStreamAdapter, base64ToPcm16, CAPTURE_SAMPLE_RATE, } from "./voice.js";
@@ -62,3 +62,5 @@ export declare const ACTION_DEADLINE_MS = 6000;
62
62
  export declare const CONTEXT_THROTTLE_MS = 400;
63
63
  export declare const UNNAMED_TAP = "tap:screen";
64
64
  export declare function resolveWidgetImages(data: Record<string, unknown>, baseUrl: string): Record<string, unknown>;
65
+ export type WidgetSurface = "screen" | "sheet" | "card";
66
+ export declare function widgetSurface(kind: string | null | undefined): WidgetSurface;
package/dist/protocol.js CHANGED
@@ -29,3 +29,15 @@ export function resolveWidgetImages(data, baseUrl) {
29
29
  }
30
30
  return out;
31
31
  }
32
+ const SURFACE = {
33
+ product_list: "screen",
34
+ products: "screen",
35
+ plan_comparison: "sheet",
36
+ choices: "sheet",
37
+ quick_actions: "sheet",
38
+ calendar_slots: "sheet",
39
+ info_card: "card",
40
+ };
41
+ export function widgetSurface(kind) {
42
+ return SURFACE[String(kind ?? "").trim().toLowerCase()] ?? "card";
43
+ }
@@ -4,6 +4,8 @@ import { type AudioAdapter, type VoiceSessionOptions } from "../voice.js";
4
4
  import type { ActionHandler, ScreenContext, ScreenField } from "../protocol.js";
5
5
  export { GraineFab, GraineTouchCapture } from "./fab.js";
6
6
  export type { GraineFabProps, GraineTouchCaptureProps } from "./fab.js";
7
+ export { widgetSurface } from "../protocol.js";
8
+ export type { WidgetSurface } from "../protocol.js";
7
9
  import type { FrictionConfig } from "../friction.js";
8
10
  import { type LauncherInset, type LauncherVisibility } from "./navigation.js";
9
11
  interface GraineContextValue {
@@ -123,6 +125,11 @@ export declare function useGraineHighlight(name: string): {
123
125
  };
124
126
  export declare function useGraineWidget(): {
125
127
  widget: GraineWidget | null;
128
+ answered: {
129
+ widget: GraineWidget;
130
+ data: Record<string, unknown>;
131
+ summary: string;
132
+ } | null;
126
133
  submit: (data: Record<string, unknown>, summary: string) => void;
127
134
  dismiss: () => void;
128
135
  };
@@ -6,6 +6,7 @@ import { VoiceSession } from "../voice.js";
6
6
  import { resolveWidgetImages } from "../protocol.js";
7
7
  import { GraineFab, GraineTouchCapture } from "./fab.js";
8
8
  export { GraineFab, GraineTouchCapture } from "./fab.js";
9
+ export { widgetSurface } from "../protocol.js";
9
10
  import { LauncherVisibilityTracker, activeRouteName, } from "./navigation.js";
10
11
  export const Ctx = createContext(null);
11
12
  let defaultClient = null;
@@ -389,25 +390,42 @@ export function useGraineHighlight(name) {
389
390
  }
390
391
  export function useGraineWidget() {
391
392
  const client = useGraineClient();
392
- const [widget, setWidget] = useState(null);
393
- useEffect(() => client.on("widget", (w) => {
394
- if (!w?.widget_id)
395
- return;
396
- setWidget({
393
+ const adopt = useCallback((w) => w?.widget_id
394
+ ? {
397
395
  id: String(w.widget_id),
398
396
  kind: String(w.kind || ""),
399
397
  data: resolveWidgetImages((w.data || {}), client.opts.baseUrl),
400
398
  fallbackText: String(w.fallback_text || ""),
399
+ }
400
+ : null, [client]);
401
+ const [widget, setWidget] = useState(() => adopt(client.pendingWidget));
402
+ const [answered, setAnswered] = useState(null);
403
+ useEffect(() => {
404
+ const pending = adopt(client.pendingWidget);
405
+ if (pending)
406
+ setWidget((cur) => (cur && cur.id === pending.id ? cur : pending));
407
+ return client.on("widget", (w) => {
408
+ const next = adopt(w);
409
+ if (!next)
410
+ return;
411
+ setWidget(next);
412
+ setAnswered(null);
401
413
  });
402
- }), [client]);
414
+ }, [client, adopt]);
403
415
  const submit = useCallback((data, summary) => {
404
416
  if (!widget)
405
417
  return;
406
418
  client.submitWidget(widget.id, data, summary);
419
+ client.clearPendingWidget();
407
420
  setWidget(null);
421
+ setAnswered({ widget, data, summary });
408
422
  }, [client, widget]);
409
- const dismiss = useCallback(() => setWidget(null), []);
410
- return { widget, submit, dismiss };
423
+ const dismiss = useCallback(() => {
424
+ client.clearPendingWidget();
425
+ setWidget(null);
426
+ setAnswered(null);
427
+ }, [client]);
428
+ return { widget, answered, submit, dismiss };
411
429
  }
412
430
  export function useGraineAction(name, handler) {
413
431
  const client = useGraineClient();
@@ -138,6 +138,9 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
138
138
  startedRef.current = true;
139
139
  void beginCall();
140
140
  }
141
+ else if (!activeRef.current && !startedRef.current) {
142
+ post({ type: "graine:warm" });
143
+ }
141
144
  return;
142
145
  case "graine:call":
143
146
  setConnected(!!msg.connected);
@@ -200,6 +203,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
200
203
  start: useCallback(() => {
201
204
  setConnecting(true);
202
205
  setCaptions([]);
206
+ client.clearPendingWidget();
203
207
  if (!ready) {
204
208
  pendingStartRef.current = true;
205
209
  return;
@@ -243,6 +247,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
243
247
  setConnected(false);
244
248
  setConnecting(false);
245
249
  setCaptions([]);
250
+ client.clearPendingWidget();
246
251
  onCallState?.({ connected: false, connecting: false });
247
252
  }, [active]);
248
253
  useEffect(() => {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@graineai/inapp-react-native",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "type": "module",
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.",
5
+ "description": "Graine in-app agent for React Native — 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",