11import 'webextension-polyfill' ;
22
33import { currentTabAutoDetector } from '@root/src/pages/background/currentTabAutoDetector' ;
4+ import { createNetworkTrafficHandler } from '@root/src/pages/background/lyticsNetworkHandler' ;
45import { EmitLog } from '@root/src/shared/components/EmitLog' ;
5- import { EventModel } from '@src/shared/models/eventModel' ;
66import entityStore from '@src/shared/storages/entityStorage' ;
77import { domainStore } from '@src/shared/storages/extensionDomainStorage' ;
88import extensionStateStorage from '@src/shared/storages/extensionStateStorage' ;
99import tagActivityStore from '@src/shared/storages/tagActivityStorage' ;
1010import tagConfigStore from '@src/shared/storages/tagConfigStorage' ;
1111import { autoDetectedDomainsStore } from '@src/shared/storages/autoDetectedDomainsStorage' ;
12+ import domainStateStore from '@src/shared/storages/tabStateStorage' ;
1213
1314chrome . sidePanel . setPanelBehavior ( { openPanelOnActionClick : true } ) . catch ( error => {
1415 EmitLog ( { name : 'background' , payload : { msg : 'Failed to set side panel behavior' , error : error . message } } ) ;
@@ -152,154 +153,9 @@ domainStore.subscribe(() => {
152153// --------------------------------------------------------
153154// Handle Listen for Requests to lytics.io
154155// --------------------------------------------------------
155- const parseURL = ( url : string , body : any , type : string ) : EventModel => {
156- // Parse URL
157- const parsedURL = new URL ( url ) ;
158- const parsedBody = new URLSearchParams ( body ) ;
159-
160- // Get URL components
161- const protocol = parsedURL . protocol ; // e.g., 'https:'
162- const host = parsedURL . host ; // e.g., 'example.com'
163- const pathname = parsedURL . pathname ; // e.g., '/path/to/page'
164- const search = parsedURL . search ; // e.g., '?param1=value1¶m2=value2'
165-
166- // Parse query parameters into an object
167- const queryParams = new URLSearchParams ( search ) ;
168- const queryParamObj : Record < string , string > = { } ;
169- queryParams . forEach ( ( value , key ) => {
170- queryParamObj [ key ] = value ;
171- } ) ;
172-
173- const parsedBodyDataObj = { } ;
174- if ( parsedBody ) {
175- parsedBody . forEach ( ( value , key ) => {
176- parsedBodyDataObj [ key ] = decodeURIComponent ( value ) ;
177- } ) ;
178- }
179-
180- let ts : string ;
181- if ( queryParamObj . ts ) {
182- ts = queryParamObj . ts ;
183- } else if ( ( parsedBodyDataObj as any ) . ts ) {
184- ts = ( parsedBodyDataObj as any ) . ts ;
185- } else {
186- ts = Date . now ( ) . toString ( ) ;
187- }
188-
189- // Assign description based on type
190- let description : string ;
191- switch ( type ) {
192- case 'load-js-tag' :
193- description =
194- 'Loaded the core Lytics JavaScript SDK. Used for collecting data and surfacing the active visitors profile back to the browser.' ;
195- break ;
196- case 'load-profile' :
197- description = 'Loaded the active visitors full profile from Lytics.' ;
198- break ;
199- case 'collect-data' :
200- description = 'Collected data via jstag.send based on visitor activity.' ;
201- break ;
202- case 'load-pathfora-tag' :
203- description = 'Loaded the Pathfora JavaScript SDK for advanced onsite personalization.' ;
204- break ;
205- case 'load-pathfora-css' :
206- description =
207- 'Loaded the default Pathfora JavaScript SDK styles to ensure modals and inline widgets are styled correctly.' ;
208- break ;
209- case 'load-experience-config' :
210- description = 'Loaded the experience configurations from the Lytics Experience Engine.' ;
211- break ;
212- case 'load-campaign-config' :
213- description =
214- 'Loaded the legacy campaign configurations from Lytics. This feature is being deprecated. Reach out to support for additional guidance.' ;
215- break ;
216- default :
217- description = `Communicated with Lytics via a generic or unhandled request (${ type } ).` ;
218- }
219-
220- return {
221- protocol,
222- host,
223- pathname,
224- queryParamObj,
225- parsedBodyDataObj,
226- ts : parseInt ( ts ) ,
227- type,
228- description,
229- } as EventModel ;
230- } ;
231-
232- const handleNetworkTraffic = details => {
233- try {
234- // get the active tab url that generated the request
235- const activeTabUrl = details . initiator ;
236- if ( ! activeTabUrl ) {
237- return ;
238- }
239-
240- // parse the active tab url
241- const activeDomain = new URL ( activeTabUrl ) . hostname ;
242-
243- // if the active domain matches the sticky domain, then we can proceed with existing logic
244- if ( activeDomain !== stickyDomain ) {
245- EmitLog ( { name : 'background' , payload : { msg : `Request from non-sticky domain <${ activeDomain } >` } } ) ;
246- return ;
247- }
248-
249- const url = details . url ;
250- let postData ;
251- let result : EventModel ;
252-
253- switch ( true ) {
254- case url . includes ( '/api/tag/' ) :
255- EmitLog ( { name : 'background' , payload : { msg : 'Called Core Tag' , url : url } } ) ;
256- result = parseURL ( url , { } , 'load-js-tag' ) ;
257- break ;
258-
259- case url . includes ( '/api/personalize/' ) :
260- EmitLog ( { name : 'background' , payload : { msg : 'Called Entity API' , url : url } } ) ;
261- result = parseURL ( url , { } , 'load-profile' ) ;
262- break ;
263-
264- case url . includes ( '/c/' ) :
265- EmitLog ( { name : 'background' , payload : { msg : 'Collection API' , url : url } } ) ;
266- if ( details . requestBody ) {
267- postData = details ?. requestBody ?. formData ?. _js [ 0 ] ;
268- }
269- result = parseURL ( url , postData || { } , 'collect-data' ) ;
270- break ;
271-
272- case url . includes ( '/static/pathfora.min.js' ) :
273- EmitLog ( { name : 'background' , payload : { msg : 'Called Pathfora Tag' , url : url } } ) ;
274- result = parseURL ( url , { } , 'load-pathfora-tag' ) ;
275- break ;
276-
277- case url . includes ( 'static/pathfora.min.css' ) :
278- EmitLog ( { name : 'background' , payload : { msg : 'Called Pathfora Styles' , url : url } } ) ;
279- result = parseURL ( url , { } , 'load-pathfora-css' ) ;
280- break ;
281-
282- case url . includes ( '/experience/candidate' ) :
283- EmitLog ( { name : 'background' , payload : { msg : 'Called Experience Config API' , url : url } } ) ;
284- result = parseURL ( url , { } , 'load-experience-config' ) ;
285- break ;
286-
287- case url . includes ( '/api/program/campaign/config' ) :
288- EmitLog ( { name : 'background' , payload : { msg : 'Called Legacy Campaign Config API' , url : url } } ) ;
289- result = parseURL ( url , { } , 'load-campaign-config' ) ;
290- break ;
291-
292- default :
293- EmitLog ( { name : 'background' , payload : { msg : 'Unhandled API Requset' , url : url } } ) ;
294- }
295- tagActivityStore . add ( JSON . stringify ( result ) ) ;
296- } catch ( error ) {
297- EmitLog ( {
298- name : 'background' ,
299- payload : { msg : 'Error in handleNetworkTraffic' , error : error . message , url : details ?. url } ,
300- } ) ;
301- }
302- } ;
156+ const handleNetworkTraffic = createNetworkTrafficHandler ( {
157+ getStickyDomain : ( ) => stickyDomain ,
158+ } ) ;
303159
304160// --------------------------------------------------------
305161// Handle Auto-Detection Messages
@@ -469,6 +325,78 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
469325 }
470326 sendResponse ( { success : true } ) ;
471327 }
328+
329+ // Handle tagConfig save from content script
330+ if ( message . action === 'saveTagConfig' ) {
331+ const tabId = sender . tab ?. id ?. toString ( ) ;
332+ const tabUrl = sender . tab ?. url ;
333+ if ( tabId && tabUrl && message . payload ) {
334+ const domain = new URL ( tabUrl ) . hostname ;
335+
336+ // Parse the payload if it's a string (tagLink sends stringified data)
337+ let parsedConfig = message . payload ;
338+ if ( typeof message . payload === 'string' ) {
339+ try {
340+ parsedConfig = JSON . parse ( message . payload ) ;
341+ } catch ( error ) {
342+ EmitLog ( {
343+ name : 'background' ,
344+ payload : { msg : `Error parsing tag config` , error : error . message } ,
345+ } ) ;
346+ sendResponse ( { success : false , error : 'Invalid JSON' } ) ;
347+ return ;
348+ }
349+ }
350+
351+ // Save to domain state (shared across all tabs on this domain)
352+ await domainStateStore . updateDomainState ( domain , {
353+ tagConfig : parsedConfig ,
354+ } ) ;
355+ EmitLog ( {
356+ name : 'background' ,
357+ payload : {
358+ msg : `Tag config saved for domain ${ domain } ` ,
359+ hasData : Object . keys ( parsedConfig ) . length > 0 ,
360+ tabId,
361+ } ,
362+ } ) ;
363+ }
364+ sendResponse ( { success : true } ) ;
365+ }
366+
367+ // Handle entity save from content script
368+ if ( message . action === 'saveEntity' ) {
369+ const tabId = sender . tab ?. id ?. toString ( ) ;
370+ const tabUrl = sender . tab ?. url ;
371+ if ( tabId && tabUrl && message . payload ) {
372+ const domain = new URL ( tabUrl ) . hostname ;
373+
374+ // Parse the payload if it's a string (tagLink sends stringified data)
375+ let parsedProfile = message . payload ;
376+ if ( typeof message . payload === 'string' ) {
377+ try {
378+ parsedProfile = JSON . parse ( message . payload ) ;
379+ } catch ( error ) {
380+ EmitLog ( {
381+ name : 'background' ,
382+ payload : { msg : `Error parsing entity profile` , error : error . message } ,
383+ } ) ;
384+ sendResponse ( { success : false , error : 'Invalid JSON' } ) ;
385+ return ;
386+ }
387+ }
388+
389+ // Save to domain state (shared across all tabs on this domain)
390+ await domainStateStore . updateDomainState ( domain , {
391+ profile : parsedProfile ,
392+ } ) ;
393+ EmitLog ( {
394+ name : 'background' ,
395+ payload : { msg : `Entity profile saved for domain ${ domain } ` , tabId } ,
396+ } ) ;
397+ }
398+ sendResponse ( { success : true } ) ;
399+ }
472400 } catch ( error ) {
473401 EmitLog ( {
474402 name : 'background' ,
@@ -480,3 +408,45 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
480408 // Return true for async operations
481409 return true ;
482410} ) ;
411+
412+ // --------------------------------------------------------
413+ // Per-Domain Storage Cleanup
414+ // --------------------------------------------------------
415+
416+ // Unregister tab from domain when tab is closed
417+ chrome . tabs . onRemoved . addListener ( async ( tabId : number ) => {
418+ try {
419+ const tabIdStr = tabId . toString ( ) ;
420+
421+ // Get all domains and unregister this tab from each
422+ const allDomains = await domainStateStore . getAllDomains ( ) ;
423+ for ( const domain of allDomains ) {
424+ await domainStateStore . unregisterTab ( domain , tabIdStr ) ;
425+ }
426+
427+ // Immediately cleanup domains with no active tabs
428+ await domainStateStore . cleanupInactiveDomains ( ) ;
429+
430+ EmitLog ( {
431+ name : 'background' ,
432+ payload : { msg : `Tab ${ tabIdStr } closed, unregistered and cleaned up inactive domains` } ,
433+ } ) ;
434+ } catch ( error ) {
435+ EmitLog ( {
436+ name : 'background' ,
437+ payload : { msg : 'Error cleaning up closed tab' , error : error . message , tabId } ,
438+ } ) ;
439+ }
440+ } ) ;
441+
442+ // Periodic cleanup of inactive domains (every 60 seconds)
443+ setInterval ( async ( ) => {
444+ try {
445+ await domainStateStore . cleanupInactiveDomains ( ) ;
446+ } catch ( error ) {
447+ EmitLog ( {
448+ name : 'background' ,
449+ payload : { msg : 'Error in periodic domain cleanup' , error : error . message } ,
450+ } ) ;
451+ }
452+ } , 60000 ) ;
0 commit comments