Skip to content

Commit bb9897c

Browse files
committed
feat: add cookie option for voice reconnect
1 parent 8b626c7 commit bb9897c

File tree

9 files changed

+195
-33
lines changed

9 files changed

+195
-33
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ The simplest way to add the widget to your website:
100100
| `chatPlaceholder` | `string` | `'Type your message...'` | Chat input placeholder text |
101101
| **Voice Configuration** | | | |
102102
| `voiceShowTranscript` | `boolean` | `false` | Show/hide voice transcript |
103-
| `voiceAutoReconnect` | `boolean` | `false` | Auto-reconnect to an active web call within the same browser tab (uses session storage) |
104-
| `reconnectStorageKey` | `string` | `'vapi_widget_web_call'` | Key for storing reconnection data (uses session storage) |
103+
| `voiceAutoReconnect` | `boolean` | `false` | Auto-reconnect to an active web call (see `voiceReconnectStorage` for scope) |
104+
| `voiceReconnectStorage` | `'session' \| 'cookies'` | `'session'` | Storage type: 'session' (same tab only) or 'cookies' (same tab across subdomains) |
105+
| `reconnectStorageKey` | `string` | `'vapi_widget_web_call'` | Key for storing reconnection data |
105106
| **Consent Configuration** | | | |
106107
| `consentRequired` | `boolean` | `false` | Show consent form before first use |
107108
| `consentTitle` | `string` | `"Terms and conditions"` | Consent form title |
@@ -286,6 +287,19 @@ Use this approach if your environment doesn't support custom elements or for bet
286287
mode="voice"
287288
voiceAutoReconnect={true}
288289
/>
290+
````
291+
292+
### Voice with Cross-Subdomain Reconnection
293+
294+
```tsx
295+
<VapiWidget
296+
publicKey="pk_123"
297+
assistantId="asst_456"
298+
mode="voice"
299+
voiceAutoReconnect={true}
300+
voiceReconnectStorage="cookies"
301+
/>
302+
```
289303

290304
## Development
291305

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
},
7575
"devDependencies": {
7676
"@playwright/test": "^1.53.1",
77+
"@types/js-cookie": "^3.0.6",
7778
"@types/node": "^24.0.6",
7879
"@types/react": "^18.2.66",
7980
"@types/react-dom": "^18.2.22",
@@ -102,6 +103,7 @@
102103
"dependencies": {
103104
"@microsoft/fetch-event-source": "^2.0.1",
104105
"@phosphor-icons/react": "^2.1.10",
106+
"js-cookie": "^3.0.5",
105107
"react-colorful": "^5.6.1",
106108
"react-markdown": "^10.1.0"
107109
}

src/components/VapiWidget.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const VapiWidget: React.FC<VapiWidgetProps> = ({
6262
voiceShowTranscript,
6363
showTranscript = false, // deprecated
6464
voiceAutoReconnect = false,
65+
voiceReconnectStorage = 'session',
6566
reconnectStorageKey = 'vapi_widget_web_call',
6667
// Consent configuration
6768
consentRequired,
@@ -150,6 +151,7 @@ const VapiWidget: React.FC<VapiWidgetProps> = ({
150151
apiUrl,
151152
firstChatMessage: effectiveChatFirstMessage,
152153
voiceAutoReconnect,
154+
voiceReconnectStorage,
153155
reconnectStorageKey,
154156
onCallStart: effectiveOnVoiceStart,
155157
onCallEnd: effectiveOnVoiceEnd,

src/components/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface VapiWidgetProps {
4949
// Voice Configuration
5050
voiceShowTranscript?: boolean;
5151
voiceAutoReconnect?: boolean;
52+
voiceReconnectStorage?: 'session' | 'cookies';
5253
reconnectStorageKey?: string;
5354

5455
// Consent Configuration

src/hooks/useVapiCall.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useEffect, useRef, useCallback } from 'react';
22
import Vapi from '@vapi-ai/web';
33
import * as vapiCallStorage from '../utils/vapiCallStorage';
4+
import type { StorageType } from '../utils/vapiCallStorage';
45

56
export interface VapiCallState {
67
isCallActive: boolean;
@@ -25,6 +26,7 @@ export interface UseVapiCallOptions {
2526
apiUrl?: string;
2627
enabled?: boolean;
2728
voiceAutoReconnect?: boolean;
29+
voiceReconnectStorage?: StorageType;
2830
reconnectStorageKey?: string;
2931
onCallStart?: () => void;
3032
onCallEnd?: () => void;
@@ -43,6 +45,7 @@ export const useVapiCall = ({
4345
apiUrl,
4446
enabled = true,
4547
voiceAutoReconnect = false,
48+
voiceReconnectStorage = 'session',
4649
reconnectStorageKey = 'vapi_widget_web_call',
4750
onCallStart,
4851
onCallEnd,
@@ -98,7 +101,10 @@ export const useVapiCall = ({
98101
setIsSpeaking(false);
99102
setIsMuted(false);
100103
// Clear stored call data on successful call end
101-
vapiCallStorage.clearStoredCall(reconnectStorageKey);
104+
vapiCallStorage.clearStoredCall(
105+
reconnectStorageKey,
106+
voiceReconnectStorage
107+
);
102108
callbacksRef.current.onCallEnd?.();
103109
};
104110

@@ -153,7 +159,7 @@ export const useVapiCall = ({
153159
vapi.removeListener('message', handleMessage);
154160
vapi.removeListener('error', handleError);
155161
};
156-
}, [vapi, reconnectStorageKey]);
162+
}, [vapi, reconnectStorageKey, voiceReconnectStorage]);
157163

158164
useEffect(() => {
159165
return () => {
@@ -194,14 +200,26 @@ export const useVapiCall = ({
194200

195201
// Store call data for reconnection if call was successful and auto-reconnect is enabled
196202
if (call && voiceAutoReconnect) {
197-
vapiCallStorage.storeCallData(reconnectStorageKey, call, callOptions);
203+
vapiCallStorage.storeCallData(
204+
reconnectStorageKey,
205+
call,
206+
callOptions,
207+
voiceReconnectStorage
208+
);
198209
}
199210
} catch (error) {
200211
console.error('Error starting call:', error);
201212
setConnectionStatus('disconnected');
202213
callbacksRef.current.onError?.(error as Error);
203214
}
204-
}, [vapi, callOptions, enabled, voiceAutoReconnect, reconnectStorageKey]);
215+
}, [
216+
vapi,
217+
callOptions,
218+
enabled,
219+
voiceAutoReconnect,
220+
voiceReconnectStorage,
221+
reconnectStorageKey,
222+
]);
205223

206224
const endCall = useCallback(
207225
async ({ force = false }: { force?: boolean } = {}) => {
@@ -250,7 +268,10 @@ export const useVapiCall = ({
250268
return;
251269
}
252270

253-
const storedData = vapiCallStorage.getStoredCallData(reconnectStorageKey);
271+
const storedData = vapiCallStorage.getStoredCallData(
272+
reconnectStorageKey,
273+
voiceReconnectStorage
274+
);
254275

255276
if (!storedData) {
256277
console.warn('No stored call data found for reconnection');
@@ -264,7 +285,10 @@ export const useVapiCall = ({
264285
console.warn(
265286
'CallOptions have changed since last call, clearing stored data and skipping reconnection'
266287
);
267-
vapiCallStorage.clearStoredCall(reconnectStorageKey);
288+
vapiCallStorage.clearStoredCall(
289+
reconnectStorageKey,
290+
voiceReconnectStorage
291+
);
268292
return;
269293
}
270294

@@ -281,14 +305,17 @@ export const useVapiCall = ({
281305
} catch (error) {
282306
setConnectionStatus('disconnected');
283307
console.error('Reconnection failed:', error);
284-
vapiCallStorage.clearStoredCall(reconnectStorageKey);
308+
vapiCallStorage.clearStoredCall(
309+
reconnectStorageKey,
310+
voiceReconnectStorage
311+
);
285312
callbacksRef.current.onError?.(error as Error);
286313
}
287-
}, [vapi, enabled, reconnectStorageKey, callOptions]);
314+
}, [vapi, enabled, reconnectStorageKey, voiceReconnectStorage, callOptions]);
288315

289316
const clearStoredCall = useCallback(() => {
290-
vapiCallStorage.clearStoredCall(reconnectStorageKey);
291-
}, [reconnectStorageKey]);
317+
vapiCallStorage.clearStoredCall(reconnectStorageKey, voiceReconnectStorage);
318+
}, [reconnectStorageKey, voiceReconnectStorage]);
292319

293320
useEffect(() => {
294321
if (!vapi || !enabled || !voiceAutoReconnect) {

src/hooks/useVapiWidget.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface UseVapiWidgetOptions {
1616
apiUrl?: string;
1717
firstChatMessage?: string;
1818
voiceAutoReconnect?: boolean;
19+
voiceReconnectStorage?: 'session' | 'cookies';
1920
reconnectStorageKey?: string;
2021
onCallStart?: () => void;
2122
onCallEnd?: () => void;
@@ -32,6 +33,7 @@ export const useVapiWidget = ({
3233
apiUrl,
3334
firstChatMessage,
3435
voiceAutoReconnect = false,
36+
voiceReconnectStorage = 'session',
3537
reconnectStorageKey,
3638
onCallStart,
3739
onCallEnd,
@@ -68,6 +70,7 @@ export const useVapiWidget = ({
6870
apiUrl,
6971
enabled: voiceEnabled,
7072
voiceAutoReconnect,
73+
voiceReconnectStorage,
7174
reconnectStorageKey,
7275
onCallStart: () => {
7376
// In hybrid mode, clear all conversations when starting voice

0 commit comments

Comments
 (0)