1+ import Cookies from 'js-cookie' ;
2+
13export interface StoredCallData {
24 webCallUrl : string ;
35 id ?: string ;
@@ -11,50 +13,142 @@ export interface StoredCallData {
1113 } ;
1214 callOptions ?: any ;
1315 timestamp : number ;
16+ tabId ?: string ;
17+ }
18+
19+ export type StorageType = 'session' | 'cookies' ;
20+
21+ // Generate or retrieve tab-specific ID (stored in sessionStorage)
22+ function getTabId ( ) : string {
23+ const TAB_ID_KEY = '_vapi_tab_id' ;
24+ let tabId = sessionStorage . getItem ( TAB_ID_KEY ) ;
25+
26+ if ( ! tabId ) {
27+ tabId = `tab_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ;
28+ sessionStorage . setItem ( TAB_ID_KEY , tabId ) ;
29+ }
30+
31+ return tabId ;
32+ }
33+
34+ // Get root domain for cookie (e.g., ".domain.com")
35+ function getRootDomain ( ) : string {
36+ const hostname = window . location . hostname ;
37+
38+ // Handle localhost/IP
39+ if (
40+ hostname === 'localhost' ||
41+ hostname === '127.0.0.1' ||
42+ / ^ \d + \. \d + \. \d + \. \d + $ / . test ( hostname )
43+ ) {
44+ return hostname ;
45+ }
46+
47+ const parts = hostname . split ( '.' ) ;
48+
49+ // Already root domain
50+ if ( parts . length <= 2 ) {
51+ return hostname ;
52+ }
53+
54+ // Return root domain with leading dot (e.g., ".domain.com")
55+ return '.' + parts . slice ( - 2 ) . join ( '.' ) ;
1456}
1557
1658export const storeCallData = (
1759 reconnectStorageKey : string ,
1860 call : any ,
19- callOptions ?: any
61+ callOptions ?: any ,
62+ storageType : StorageType = 'session'
2063) => {
2164 const webCallUrl =
2265 ( call as any ) . webCallUrl || ( call . transport as any ) ?. callUrl ;
2366
24- if ( webCallUrl ) {
25- const webCallToStore = {
26- webCallUrl,
27- id : call . id ,
28- artifactPlan : call . artifactPlan ,
29- assistant : call . assistant ,
30- callOptions,
31- timestamp : Date . now ( ) ,
32- } ;
33- sessionStorage . setItem ( reconnectStorageKey , JSON . stringify ( webCallToStore ) ) ;
34- console . log ( 'Stored call data for reconnection:' , webCallToStore ) ;
35- } else {
67+ if ( ! webCallUrl ) {
3668 console . warn (
3769 'No webCallUrl found in call object, cannot store for reconnection'
3870 ) ;
71+ return ;
72+ }
73+
74+ const webCallToStore : StoredCallData = {
75+ webCallUrl,
76+ id : call . id ,
77+ artifactPlan : call . artifactPlan ,
78+ assistant : call . assistant ,
79+ callOptions,
80+ timestamp : Date . now ( ) ,
81+ } ;
82+
83+ if ( storageType === 'session' ) {
84+ sessionStorage . setItem ( reconnectStorageKey , JSON . stringify ( webCallToStore ) ) ;
85+ } else if ( storageType === 'cookies' ) {
86+ const tabId = getTabId ( ) ;
87+ const webCallToStoreWithTab = { ...webCallToStore , tabId } ;
88+
89+ try {
90+ const rootDomain = getRootDomain ( ) ;
91+
92+ Cookies . set ( reconnectStorageKey , JSON . stringify ( webCallToStoreWithTab ) , {
93+ domain : rootDomain ,
94+ path : '/' ,
95+ secure : true ,
96+ sameSite : 'lax' ,
97+ expires : 1 / 24 , // 1 hour (expires takes days, so 1/24 = 1 hour)
98+ } ) ;
99+ } catch ( error ) {
100+ console . error ( 'Failed to store call data in cookie:' , error ) ;
101+ }
39102 }
40103} ;
41104
42105export const getStoredCallData = (
43- reconnectStorageKey : string
106+ reconnectStorageKey : string ,
107+ storageType : StorageType = 'session'
44108) : StoredCallData | null => {
45109 try {
46- const stored = sessionStorage . getItem ( reconnectStorageKey ) ;
47- if ( ! stored ) return null ;
110+ if ( storageType === 'session' ) {
111+ const sessionData = sessionStorage . getItem ( reconnectStorageKey ) ;
112+ if ( ! sessionData ) return null ;
48113
49- return JSON . parse ( stored ) ;
50- } catch {
51- sessionStorage . removeItem ( reconnectStorageKey ) ;
114+ return JSON . parse ( sessionData ) ;
115+ } else if ( storageType === 'cookies' ) {
116+ const currentTabId = getTabId ( ) ;
117+ const cookieValue = Cookies . get ( reconnectStorageKey ) ;
118+
119+ if ( ! cookieValue ) return null ;
120+
121+ const data = JSON . parse ( cookieValue ) ;
122+
123+ // Verify tab ID matches (prevents multi-tab reconnection)
124+ if ( data . tabId !== currentTabId ) {
125+ console . warn ( 'Tab ID mismatch - ignoring call data from different tab' ) ;
126+ return null ;
127+ }
128+
129+ return data ;
130+ }
131+
132+ return null ;
133+ } catch ( error ) {
134+ console . error ( 'Error reading stored call data:' , error ) ;
52135 return null ;
53136 }
54137} ;
55138
56- export const clearStoredCall = ( reconnectStorageKey : string ) => {
57- sessionStorage . removeItem ( reconnectStorageKey ) ;
139+ export const clearStoredCall = (
140+ reconnectStorageKey : string ,
141+ storageType : StorageType = 'session'
142+ ) => {
143+ if ( storageType === 'session' ) {
144+ sessionStorage . removeItem ( reconnectStorageKey ) ;
145+ } else if ( storageType === 'cookies' ) {
146+ const rootDomain = getRootDomain ( ) ;
147+ Cookies . remove ( reconnectStorageKey , {
148+ domain : rootDomain ,
149+ path : '/' ,
150+ } ) ;
151+ }
58152} ;
59153
60154export const areCallOptionsEqual = ( options1 : any , options2 : any ) : boolean => {
0 commit comments