@@ -20,7 +20,7 @@ interface FreshchatUser {
2020}
2121
2222interface FreshchatWidget {
23- init ( opts : { token : string ; host : string ; externalId ?: string ; restoreId ?: string } ) : void
23+ init ( opts : Record < string , unknown > ) : void
2424 user : {
2525 setProperties ( props : Record < string , unknown > ) : void
2626 update ( user : FreshchatUser ) : void
@@ -29,9 +29,20 @@ interface FreshchatWidget {
2929 setFirstName ( name : string ) : void
3030 setLastName ( name : string ) : void
3131 setPhone ( phone : string ) : void
32+ setLocale ?( locale : string ) : void
3233 }
3334 setExternalId ( id : string ) : void
3435 setJWTAuthToken ( token : string ) : void
36+ setConfig ?( config : Record < string , unknown > ) : void
37+ setTags ?( tags : string [ ] ) : void
38+ setFaqTags ?( payload : { tags : string [ ] ; filterType ?: string } ) : void
39+ trackPage ?( url : string , title ?: string ) : void
40+ isOpen ?( ) : boolean
41+ isLoaded ?( ) : boolean
42+ conversation ?: {
43+ setBotVariables ?( vars : Record < string , unknown > ) : void
44+ setConversationProperties ?( props : Record < string , unknown > ) : void
45+ }
3546 track ?: ( event : string , props ?: Record < string , unknown > ) => void
3647 show ( ) : void
3748 hide ( ) : void
@@ -55,23 +66,87 @@ function w(): FreshchatWindow {
5566const queue = createQueue < FreshchatWidget > ( )
5667const store = createIdentityStore ( )
5768const lifecycle = createLifecycle ( )
69+
70+ export type FreshchatRegion = "us" | "eu" | "in" | "au"
71+ const REGION_HOSTS : Record < FreshchatRegion , string > = {
72+ us : "https://wchat.freshchat.com" ,
73+ eu : "https://wchat.eu.freshchat.com" ,
74+ in : "https://wchat.in.freshchat.com" ,
75+ au : "https://wchat.au.freshchat.com" ,
76+ }
77+
78+ export type FreshchatEventName =
79+ | "widgetLoaded"
80+ | "widgetOpened"
81+ | "widgetClosed"
82+ | "widgetDestroyed"
83+ | "userCreated"
84+ | "userCleared"
85+ | "userStateChange"
86+ | "messageSent"
87+ | "messageReceived"
88+ | "unreadCountNotify"
89+ | "dialogOpened"
90+ | "dialogClosed"
91+ | "csatReceived"
92+ | "csatUpdated"
93+ | "conversationResolved"
94+ | "frameStateChange"
95+
96+ const FRESHCHAT_EVENT_MAP : Record < string , FreshchatEventName > = {
97+ "widget:loaded" : "widgetLoaded" ,
98+ "widget:opened" : "widgetOpened" ,
99+ "widget:closed" : "widgetClosed" ,
100+ "widget:destroyed" : "widgetDestroyed" ,
101+ "user:created" : "userCreated" ,
102+ "user:cleared" : "userCleared" ,
103+ "user:statechange" : "userStateChange" ,
104+ "message:sent" : "messageSent" ,
105+ "message:received" : "messageReceived" ,
106+ "unreadCount:notify" : "unreadCountNotify" ,
107+ "dialog:opened" : "dialogOpened" ,
108+ "dialog:closed" : "dialogClosed" ,
109+ "csat:received" : "csatReceived" ,
110+ "csat:updated" : "csatUpdated" ,
111+ "conversation:resolved" : "conversationResolved" ,
112+ "frame:statechange" : "frameStateChange" ,
113+ }
114+
115+ const eventListeners = new Map < FreshchatEventName , Set < ( payload ?: unknown ) => void > > ( )
116+ const unreadListeners = new Set < ( count : number ) => void > ( )
58117let readyPromise : Promise < void > | undefined
59118let readyResolve : ( ( ) => void ) | undefined
60119let currentToken : string | undefined
61120let currentHost : string | undefined
62121
63122export interface FreshchatLoadOptions extends LoadOptions {
64123 token : string
65- /** e.g. wchat.freshchat.com (default) or wchat.eu.freshchat.com */
124+ /** Convenience for picking the regional host. */
125+ region ?: FreshchatRegion
126+ /** Explicit host (overrides region). e.g. https://wchat.freshchat.com */
66127 host ?: string
67128 externalId ?: string
68129 restoreId ?: string
130+ /** Multi-site separation under one Freshchat account. */
131+ siteId ?: string
132+ /** Initial UI locale (e.g. "tr-TR"). */
133+ locale ?: string
134+ /** Bot/topic tags applied at init. */
135+ tags ?: string [ ]
136+ /** FAQ topic filter at init. */
137+ faqTags ?: { tags : string [ ] ; filterType ?: string }
138+ /** Open a parallel conversation on the same topic. */
139+ conversationReferenceId ?: string
140+ /** Open the widget panel on load. */
141+ open ?: boolean
142+ /** Eagerly load the widget chrome before user interaction. */
143+ eagerLoad ?: boolean
69144}
70145
71146export async function load ( options : FreshchatLoadOptions ) : Promise < void > {
72147 if ( ! isBrowser ( ) ) return
73148 if ( options . consent === false ) return
74- const host = options . host ?? "https://wchat.freshchat.com"
149+ const host = options . host ?? ( options . region ? REGION_HOSTS [ options . region ] : REGION_HOSTS . us )
75150 const h = hashConfig ( { token : options . token , host } )
76151 if ( lifecycle . state ( ) === "ready" && lifecycle . configHash ( ) === h ) return
77152 if ( lifecycle . configHash ( ) && lifecycle . configHash ( ) !== h ) await destroy ( )
@@ -101,14 +176,42 @@ export async function load(options: FreshchatLoadOptions): Promise<void> {
101176 for ( let i = 0 ; i < 80 ; i ++ ) {
102177 const widget = w ( ) . fcWidget
103178 if ( widget ) {
104- widget . init ( {
179+ const initPayload : Record < string , unknown > = {
105180 token : options . token ,
106181 host,
107182 externalId : options . externalId ,
108183 restoreId : options . restoreId ,
109- } )
184+ }
185+ if ( options . siteId !== undefined ) initPayload [ "siteId" ] = options . siteId
186+ if ( options . locale !== undefined ) initPayload [ "locale" ] = options . locale
187+ if ( options . tags !== undefined ) initPayload [ "tags" ] = options . tags
188+ if ( options . faqTags !== undefined ) initPayload [ "faqTags" ] = options . faqTags
189+ if ( options . conversationReferenceId !== undefined ) {
190+ initPayload [ "conversationReferenceId" ] = options . conversationReferenceId
191+ }
192+ if ( options . open !== undefined ) initPayload [ "open" ] = options . open
193+ if ( options . eagerLoad !== undefined ) {
194+ initPayload [ "config" ] = {
195+ ...( initPayload [ "config" ] as object ) ,
196+ eagerLoad : options . eagerLoad ,
197+ }
198+ }
199+ widget . init ( initPayload )
110200 queue . ready ( widget )
111- widget . on ( "widget:loaded" , ( ) => readyResolve ?.( ) )
201+ // Wire all documented widget events to the typed emitter.
202+ for ( const [ vendorName , mapped ] of Object . entries ( FRESHCHAT_EVENT_MAP ) ) {
203+ widget . on ( vendorName , ( payload : unknown ) => {
204+ const set = eventListeners . get ( mapped )
205+ if ( set ) for ( const l of set ) l ( payload )
206+ if ( mapped === "widgetLoaded" ) readyResolve ?.( )
207+ if ( mapped === "unreadCountNotify" ) {
208+ const count =
209+ ( payload as { count ?: number } | undefined ) ?. count ??
210+ ( typeof payload === "number" ? payload : 0 )
211+ for ( const l of unreadListeners ) l ( count )
212+ }
213+ } )
214+ }
112215 break
113216 }
114217 await new Promise ( ( r ) => setTimeout ( r , 50 ) )
@@ -149,20 +252,94 @@ export function track<T extends EventMetadata = EventMetadata>(
149252 return queue . enqueue ( ( widget ) => widget . track ?.( event , metadata ) )
150253}
151254
152- export function pageView ( _info ?: { path ?: string ; locale ?: string } ) : Promise < void > {
153- return Promise . resolve ( )
255+ export function pageView ( info ?: { path ?: string ; locale ?: string } ) : Promise < void > {
256+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
257+ return queue . enqueue ( ( widget ) => {
258+ if ( info ?. path && widget . trackPage ) widget . trackPage ( info . path )
259+ if ( info ?. locale ) widget . user . setLocale ?.( info . locale )
260+ } )
154261}
155262
156263export function show ( ) : Promise < void > {
157264 if ( ! isBrowser ( ) ) return Promise . resolve ( )
158- return queue . enqueue ( ( widget ) => widget . open ( ) )
265+ return queue . enqueue ( ( widget ) => widget . show ( ) )
159266}
160267
161268export function hide ( ) : Promise < void > {
269+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
270+ return queue . enqueue ( ( widget ) => widget . hide ( ) )
271+ }
272+
273+ export function open ( opts ?: { name ?: string } ) : Promise < void > {
274+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
275+ return queue . enqueue ( ( widget ) => widget . open ( opts ) )
276+ }
277+
278+ export function close ( ) : Promise < void > {
162279 if ( ! isBrowser ( ) ) return Promise . resolve ( )
163280 return queue . enqueue ( ( widget ) => widget . close ( ) )
164281}
165282
283+ export function setLocale ( locale : string ) : Promise < void > {
284+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
285+ return queue . enqueue ( ( widget ) => widget . user . setLocale ?.( locale ) )
286+ }
287+
288+ export function setTags ( tags : string [ ] ) : Promise < void > {
289+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
290+ return queue . enqueue ( ( widget ) => widget . setTags ?.( tags ) )
291+ }
292+
293+ export function setFaqTags ( payload : { tags : string [ ] ; filterType ?: string } ) : Promise < void > {
294+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
295+ return queue . enqueue ( ( widget ) => widget . setFaqTags ?.( payload ) )
296+ }
297+
298+ export function setConfig ( config : Record < string , unknown > ) : Promise < void > {
299+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
300+ return queue . enqueue ( ( widget ) => widget . setConfig ?.( config ) )
301+ }
302+
303+ export function setBotVariables ( vars : Record < string , unknown > ) : Promise < void > {
304+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
305+ return queue . enqueue ( ( widget ) => widget . conversation ?. setBotVariables ?.( vars ) )
306+ }
307+
308+ export function setConversationProperties ( props : Record < string , unknown > ) : Promise < void > {
309+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
310+ return queue . enqueue ( ( widget ) => widget . conversation ?. setConversationProperties ?.( props ) )
311+ }
312+
313+ export function trackPage ( url : string , title ?: string ) : Promise < void > {
314+ if ( ! isBrowser ( ) ) return Promise . resolve ( )
315+ return queue . enqueue ( ( widget ) => widget . trackPage ?.( url , title ) )
316+ }
317+
318+ export function isOpen ( ) : boolean | undefined {
319+ if ( ! isBrowser ( ) ) return undefined
320+ return w ( ) . fcWidget ?. isOpen ?.( )
321+ }
322+
323+ export function isLoaded ( ) : boolean | undefined {
324+ if ( ! isBrowser ( ) ) return undefined
325+ return w ( ) . fcWidget ?. isLoaded ?.( )
326+ }
327+
328+ export function on ( event : FreshchatEventName , listener : ( payload ?: unknown ) => void ) : ( ) => void {
329+ let set = eventListeners . get ( event )
330+ if ( ! set ) {
331+ set = new Set ( )
332+ eventListeners . set ( event , set )
333+ }
334+ set . add ( listener )
335+ return ( ) => set ?. delete ( listener )
336+ }
337+
338+ export function onUnreadCountChange ( listener : ( count : number ) => void ) : ( ) => void {
339+ unreadListeners . add ( listener )
340+ return ( ) => unreadListeners . delete ( listener )
341+ }
342+
166343export function shutdown ( ) : Promise < void > {
167344 if ( ! isBrowser ( ) ) return Promise . resolve ( )
168345 return queue
@@ -185,6 +362,8 @@ export async function destroy(): Promise<void> {
185362 Reflect . deleteProperty ( g , "fcSettings" )
186363 queue . reset ( )
187364 store . reset ( )
365+ eventListeners . clear ( )
366+ unreadListeners . clear ( )
188367 currentToken = undefined
189368 currentHost = undefined
190369 readyPromise = undefined
0 commit comments