@live-assistant/react-native 0.3.0 → 0.3.1

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 CHANGED
@@ -15,8 +15,10 @@ same way in every app — so they are not yours to write. The one value it canno
15
15
  invent is the route on **your** server that mints a short-lived token, because
16
16
  your API key must never ship in an app bundle.
17
17
 
18
- This page is the whole of it: the four steps below, then a table for every value
19
- you can pass. If you would rather read code than prose,
18
+ This page is the whole of it: five steps, then a table for every value you can
19
+ pass. **Building for the web only?** Steps 1 and 2 are the native setup — there,
20
+ the install is the whole of it and nothing needs rebuilding; skip to
21
+ [step 3](#3-mint-tokens-on-your-server). If you would rather read code than prose,
20
22
  [`examples/expo-app`](https://github.com/Recipely-Team/live-assistant/tree/main/examples/expo-app)
21
23
  is a working app in one file, and CI builds it on every change.
22
24
 
@@ -39,6 +41,8 @@ On the web nothing native is needed, but `getUserMedia` only exists in a
39
41
 
40
42
  ## 2. Configure the microphone — one line
41
43
 
44
+ *Native only. A web build needs nothing from this step.*
45
+
42
46
  ```json
43
47
  {
44
48
  "expo": {
@@ -49,6 +49,10 @@ export type LiveAssistantProps = LiveAssistantEndpointProps | LiveAssistantConne
49
49
  * - **The controller is built once**, in a `useState` initialiser. It owns a
50
50
  * socket and two devices; rebuilding it on a render would start a session per
51
51
  * render. The session is stopped when this unmounts.
52
+ * - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
53
+ * through a ref, so passing them inline — which is how anyone passes them —
54
+ * cannot re-run the lifecycle effect. It once could, and its cleanup hung up
55
+ * on a live session whenever a parent re-rendered.
52
56
  * - **`headers` and `language` are read at connection time, not at mount.** An
53
57
  * app whose `Authorization` header is refreshed mid-session would otherwise
54
58
  * reconnect with the token it had when the component first rendered — which
@@ -1 +1 @@
1
- {"version":3,"file":"live-assistant.d.ts","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG1H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,mEAAmE;AACnE,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,YAAY,CAAC;IACzD,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9E,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;IACpF,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED,wCAAwC;AACxC,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,mFAAmF;AACnF,UAAU,4BAA6B,SAAQ,wBAAwB;IACrE,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,MAAM,MAAM,kBAAkB,GAAG,0BAA0B,GAAG,4BAA4B,CAAC;AAK3F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,aAAa,EACb,QAA2B,EAC3B,OAAO,EACP,KAAK,EACL,IAAI,EACJ,MAAM,EACN,OAAO,EACP,SAAS,EACT,GAAG,MAAM,EACV,EAAE,kBAAkB,+BAuCpB"}
1
+ {"version":3,"file":"live-assistant.d.ts","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG1H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,mEAAmE;AACnE,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,YAAY,CAAC;IACzD,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9E,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;IACpF,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED,wCAAwC;AACxC,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,mFAAmF;AACnF,UAAU,4BAA6B,SAAQ,wBAAwB;IACrE,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,MAAM,MAAM,kBAAkB,GAAG,0BAA0B,GAAG,4BAA4B,CAAC;AAK3F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,aAAa,EACb,QAA2B,EAC3B,OAAO,EACP,KAAK,EACL,IAAI,EACJ,MAAM,EACN,OAAO,EACP,SAAS,EACT,GAAG,MAAM,EACV,EAAE,kBAAkB,+BA2CpB"}
@@ -27,6 +27,10 @@ const JSON_TYPE = 'application/json';
27
27
  * - **The controller is built once**, in a `useState` initialiser. It owns a
28
28
  * socket and two devices; rebuilding it on a render would start a session per
29
29
  * render. The session is stopped when this unmounts.
30
+ * - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
31
+ * through a ref, so passing them inline — which is how anyone passes them —
32
+ * cannot re-run the lifecycle effect. It once could, and its cleanup hung up
33
+ * on a live session whenever a parent re-rendered.
30
34
  * - **`headers` and `language` are read at connection time, not at mount.** An
31
35
  * app whose `Authorization` header is refreshed mid-session would otherwise
32
36
  * reconnect with the token it had when the component first rendered — which
@@ -42,8 +46,8 @@ const JSON_TYPE = 'application/json';
42
46
  */
43
47
  function LiveAssistant({ tokenEndpoint, getConnection, language = DEFAULT_LANGUAGE, headers, tools, page, timing, onReady, onFailure, ...widget }) {
44
48
  // Read at connection time rather than captured at mount: see the doc block.
45
- const latest = (0, react_1.useRef)({ tokenEndpoint, getConnection, language, headers });
46
- latest.current = { tokenEndpoint, getConnection, language, headers };
49
+ const latest = (0, react_1.useRef)({ tokenEndpoint, getConnection, language, headers, onReady, onFailure });
50
+ latest.current = { tokenEndpoint, getConnection, language, headers, onReady, onFailure };
47
51
  const [controller] = (0, react_1.useState)(() => new core_1.AssistantController({
48
52
  session: new gemini_1.GeminiLiveSession(),
49
53
  microphone: new audio_1.Microphone(),
@@ -65,20 +69,32 @@ function LiveAssistant({ tokenEndpoint, getConnection, language = DEFAULT_LANGUA
65
69
  return (await response.json());
66
70
  },
67
71
  }));
72
+ // Only the controller is a dependency. `onReady` used to be one too, and an
73
+ // inline arrow — the ordinary way to pass it — is a new function on every
74
+ // render, so the effect re-ran and its cleanup hung up on a live session
75
+ // whenever anything above this re-rendered.
68
76
  (0, react_1.useEffect)(() => {
69
- onReady?.(controller);
77
+ latest.current.onReady?.(controller);
70
78
  return () => void controller.stop();
71
- }, [controller, onReady]);
72
- return ((0, jsx_runtime_1.jsxs)(react_2.AssistantProvider, { controller: controller, children: [(0, jsx_runtime_1.jsx)(FailureReporter, { onFailure: onFailure }), (0, jsx_runtime_1.jsx)(widget_1.AssistantWidget, { ...widget })] }));
79
+ }, [controller]);
80
+ return ((0, jsx_runtime_1.jsxs)(react_2.AssistantProvider, { controller: controller, children: [(0, jsx_runtime_1.jsx)(FailureReporter, { onFailure: latest.current.onFailure }), (0, jsx_runtime_1.jsx)(widget_1.AssistantWidget, { ...widget })] }));
73
81
  }
74
82
  const selectError = (state) => state.error;
75
- /** Reports failures to the app without re-rendering the widget for them. */
83
+ /**
84
+ * Reports failures to the app without re-rendering the widget for them.
85
+ *
86
+ * The handler is held in a ref for the same reason the lifecycle effect takes
87
+ * only the controller: an inline arrow changes identity every render, and a
88
+ * handler in the dependencies would report the same failure again each time.
89
+ */
76
90
  function FailureReporter({ onFailure }) {
77
91
  const error = (0, react_2.useAssistantState)(selectError);
92
+ const report = (0, react_1.useRef)(onFailure);
93
+ report.current = onFailure;
78
94
  (0, react_1.useEffect)(() => {
79
95
  if (error !== null)
80
- onFailure?.(error);
81
- }, [error, onFailure]);
96
+ report.current?.(error);
97
+ }, [error]);
82
98
  return null;
83
99
  }
84
100
  //# sourceMappingURL=live-assistant.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"live-assistant.js","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"live-assistant.js","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-assistant/react-native",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "One install for a voice assistant in a React Native or Expo app: the session, a Gemini Live connection, microphone and playback, React bindings and a ready-made widget, all re-exported from one place.",
5
5
  "license": "MIT",
6
6
  "author": "Recep Tayyip Ekşi",
@@ -41,11 +41,11 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@live-assistant/audio": "0.3.0",
45
- "@live-assistant/core": "0.3.0",
46
- "@live-assistant/gemini": "0.3.0",
47
- "@live-assistant/react": "0.3.0",
48
- "@live-assistant/widget": "0.3.0"
44
+ "@live-assistant/audio": "0.3.1",
45
+ "@live-assistant/core": "0.3.1",
46
+ "@live-assistant/gemini": "0.3.1",
47
+ "@live-assistant/react": "0.3.1",
48
+ "@live-assistant/widget": "0.3.1"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": ">=18",
@@ -60,6 +60,10 @@ const JSON_TYPE = 'application/json';
60
60
  * - **The controller is built once**, in a `useState` initialiser. It owns a
61
61
  * socket and two devices; rebuilding it on a render would start a session per
62
62
  * render. The session is stopped when this unmounts.
63
+ * - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
64
+ * through a ref, so passing them inline — which is how anyone passes them —
65
+ * cannot re-run the lifecycle effect. It once could, and its cleanup hung up
66
+ * on a live session whenever a parent re-rendered.
63
67
  * - **`headers` and `language` are read at connection time, not at mount.** An
64
68
  * app whose `Authorization` header is refreshed mid-session would otherwise
65
69
  * reconnect with the token it had when the component first rendered — which
@@ -86,8 +90,8 @@ export function LiveAssistant({
86
90
  ...widget
87
91
  }: LiveAssistantProps) {
88
92
  // Read at connection time rather than captured at mount: see the doc block.
89
- const latest = useRef({ tokenEndpoint, getConnection, language, headers });
90
- latest.current = { tokenEndpoint, getConnection, language, headers };
93
+ const latest = useRef({ tokenEndpoint, getConnection, language, headers, onReady, onFailure });
94
+ latest.current = { tokenEndpoint, getConnection, language, headers, onReady, onFailure };
91
95
 
92
96
  const [controller] = useState(
93
97
  () =>
@@ -112,14 +116,18 @@ export function LiveAssistant({
112
116
  }),
113
117
  );
114
118
 
119
+ // Only the controller is a dependency. `onReady` used to be one too, and an
120
+ // inline arrow — the ordinary way to pass it — is a new function on every
121
+ // render, so the effect re-ran and its cleanup hung up on a live session
122
+ // whenever anything above this re-rendered.
115
123
  useEffect(() => {
116
- onReady?.(controller);
124
+ latest.current.onReady?.(controller);
117
125
  return () => void controller.stop();
118
- }, [controller, onReady]);
126
+ }, [controller]);
119
127
 
120
128
  return (
121
129
  <AssistantProvider controller={controller}>
122
- <FailureReporter onFailure={onFailure} />
130
+ <FailureReporter onFailure={latest.current.onFailure} />
123
131
  <AssistantWidget {...widget} />
124
132
  </AssistantProvider>
125
133
  );
@@ -127,11 +135,19 @@ export function LiveAssistant({
127
135
 
128
136
  const selectError = (state: { readonly error: AssistantFailure | null }): AssistantFailure | null => state.error;
129
137
 
130
- /** Reports failures to the app without re-rendering the widget for them. */
138
+ /**
139
+ * Reports failures to the app without re-rendering the widget for them.
140
+ *
141
+ * The handler is held in a ref for the same reason the lifecycle effect takes
142
+ * only the controller: an inline arrow changes identity every render, and a
143
+ * handler in the dependencies would report the same failure again each time.
144
+ */
131
145
  function FailureReporter({ onFailure }: { readonly onFailure?: (failure: AssistantFailure) => void }) {
132
146
  const error = useAssistantState(selectError);
147
+ const report = useRef(onFailure);
148
+ report.current = onFailure;
133
149
  useEffect(() => {
134
- if (error !== null) onFailure?.(error);
135
- }, [error, onFailure]);
150
+ if (error !== null) report.current?.(error);
151
+ }, [error]);
136
152
  return null;
137
153
  }