33 *
44 * Intercepts HTTP responses to extract CDN cache headers and updates
55 * the toolbar badge with the cache status (HIT/MISS/etc).
6- *
7- * Supports: Cloudflare, CloudFront, Fastly, Akamai, Bunny CDN, Varnish
86 */
97
8+ // Import shared constants (loaded via manifest.json)
9+ // Uses: TRACKED_HEADERS, STATUS_COLORS, detectCDN, parseCacheStatus
10+
1011// =============================================================================
1112// State Management
1213// =============================================================================
@@ -17,122 +18,12 @@ const tabData = new Map();
1718/** Tracks tabs with pending navigations to capture only the first main request */
1819const pendingNavigations = new Set ( ) ;
1920
20- // =============================================================================
21- // Configuration
22- // =============================================================================
23-
24- /** HTTP headers to capture from responses */
25- const TRACKED_HEADERS = [
26- // Cloudflare
27- 'cf-cache-status' , 'cf-ray' , 'cf-pop' ,
28- // CloudFront
29- 'x-amz-cf-id' , 'x-amz-cf-pop' ,
30- // Fastly
31- 'x-served-by' , 'x-cache-hits' , 'x-timer' ,
32- // Akamai
33- 'x-akamai-request-id' ,
34- // Bunny CDN
35- 'cdn-cache' , 'cdn-pullzone' , 'cdn-requestid' ,
36- // Generic (CloudFront, Fastly, Akamai, Varnish, KeyCDN, etc.)
37- 'x-cache' , 'x-cache-status' , 'x-varnish' , 'x-edge-location' , 'via' ,
38- // Standard cache headers
39- 'age' , 'cache-control' , 'expires' , 'etag' , 'last-modified' , 'vary' , 'pragma' ,
40- // Response metadata
41- 'server' , 'content-type'
42- ] ;
43-
44- /** Badge colors for each cache status */
45- const STATUS_COLORS = {
46- 'HIT' : { badge : '#22c55e' , text : '#fff' } , // Green
47- 'MISS' : { badge : '#ef4444' , text : '#fff' } , // Red
48- 'EXPIRED' : { badge : '#eab308' , text : '#000' } , // Yellow
49- 'STALE' : { badge : '#eab308' , text : '#000' } , // Yellow
50- 'REVALIDATED' : { badge : '#eab308' , text : '#000' } , // Yellow
51- 'REFRESH' : { badge : '#eab308' , text : '#000' } , // Yellow
52- 'BYPASS' : { badge : '#6b7280' , text : '#fff' } , // Gray
53- 'DYNAMIC' : { badge : '#6b7280' , text : '#fff' } , // Gray
54- 'ERROR' : { badge : '#ef4444' , text : '#fff' } , // Red
55- 'NONE' : { badge : '#6b7280' , text : '#fff' } // Gray (no CDN)
56- } ;
57-
58- // =============================================================================
59- // CDN Detection
60- // =============================================================================
61-
62- /**
63- * Detects the CDN provider based on response headers.
64- * @param {Object } headers - Lowercase header name to value mapping
65- * @returns {string|null } CDN identifier or null if not detected
66- */
67- function detectCDN ( headers ) {
68- if ( headers [ 'cf-cache-status' ] || headers [ 'cf-ray' ] ) return 'cloudflare' ;
69- if ( headers [ 'x-amz-cf-id' ] || headers [ 'x-amz-cf-pop' ] ) return 'cloudfront' ;
70- if ( headers [ 'x-served-by' ] || headers [ 'x-timer' ] ) return 'fastly' ;
71- if ( headers [ 'x-akamai-request-id' ] ) return 'akamai' ;
72- if ( headers [ 'cdn-cache' ] || headers [ 'cdn-pullzone' ] ) return 'bunny' ;
73- if ( headers [ 'x-varnish' ] ) return 'varnish' ;
74-
75- // Check server header for CDN hints
76- const server = ( headers [ 'server' ] || '' ) . toLowerCase ( ) ;
77- if ( server . includes ( 'akamai' ) ) return 'akamai' ;
78-
79- // Check x-cache with via header for additional hints
80- if ( headers [ 'x-cache' ] ) {
81- const via = ( headers [ 'via' ] || '' ) . toLowerCase ( ) ;
82- if ( via . includes ( 'cloudfront' ) ) return 'cloudfront' ;
83- if ( via . includes ( 'varnish' ) ) return 'varnish' ;
84- if ( via . includes ( 'akamai' ) ) return 'akamai' ;
85- return 'cdn' ; // Generic CDN
86- }
87-
88- return null ;
89- }
90-
91- /**
92- * Parses cache status from headers based on the detected CDN.
93- * @param {Object } headers - Lowercase header name to value mapping
94- * @param {string } cdn - CDN identifier from detectCDN()
95- * @returns {string|null } Normalized cache status (HIT, MISS, etc.) or null
96- */
97- function parseCacheStatus ( headers , cdn ) {
98- // Cloudflare uses its own header
99- if ( cdn === 'cloudflare' ) {
100- return headers [ 'cf-cache-status' ] ?. toUpperCase ( ) || null ;
101- }
102-
103- // Bunny CDN uses cdn-cache header
104- if ( cdn === 'bunny' ) {
105- const status = headers [ 'cdn-cache' ] ;
106- if ( status ) {
107- const lower = status . toLowerCase ( ) ;
108- if ( lower . includes ( 'hit' ) ) return 'HIT' ;
109- if ( lower . includes ( 'miss' ) ) return 'MISS' ;
110- }
111- return null ;
112- }
113-
114- // Most CDNs use x-cache header (CloudFront, Fastly, Akamai, Varnish, etc.)
115- const xCache = headers [ 'x-cache' ] || headers [ 'x-cache-status' ] ;
116- if ( xCache ) {
117- const lower = xCache . toLowerCase ( ) ;
118- if ( lower . includes ( 'hit' ) ) return 'HIT' ;
119- if ( lower . includes ( 'miss' ) ) return 'MISS' ;
120- if ( lower . includes ( 'refresh' ) ) return 'REFRESH' ;
121- if ( lower . includes ( 'error' ) ) return 'ERROR' ;
122- if ( lower . includes ( 'pass' ) ) return 'BYPASS' ;
123- if ( lower . includes ( 'expired' ) ) return 'EXPIRED' ;
124- }
125-
126- return null ;
127- }
128-
12921// =============================================================================
13022// Badge Management
13123// =============================================================================
13224
13325/**
13426 * Updates the toolbar badge text and color for a tab.
135- * Sets both globally and per-tab (Safari has inconsistent per-tab support).
13627 * @param {number } tabId - Browser tab ID
13728 * @param {string|null } status - Cache status (HIT, MISS, etc.)
13829 * @param {string|null } cdn - CDN identifier
@@ -141,19 +32,16 @@ function updateBadge(tabId, status, cdn) {
14132 const displayStatus = status || 'NONE' ;
14233 const colors = STATUS_COLORS [ displayStatus ] || STATUS_COLORS [ 'NONE' ] ;
14334
144- // Map status to shortened badge text
14535 const badgeTextMap = {
14636 'HIT' : 'HIT' , 'MISS' : 'MISS' , 'EXPIRED' : 'EXP' , 'STALE' : 'STL' ,
14737 'REVALIDATED' : 'REV' , 'BYPASS' : 'BYP' , 'DYNAMIC' : 'DYN' ,
14838 'REFRESH' : 'REF' , 'ERROR' : 'ERR'
14939 } ;
15040 const badgeText = status ? ( badgeTextMap [ status ] || status . substring ( 0 , 3 ) ) : '' ;
15141
152- // Set badge globally (Safari doesn't always support per-tab badges)
15342 browser . action . setBadgeText ( { text : badgeText } ) ;
15443 browser . action . setBadgeBackgroundColor ( { color : colors . badge } ) ;
15544
156- // Also try per-tab for browsers that support it
15745 try {
15846 browser . action . setBadgeText ( { text : badgeText , tabId } ) ;
15947 browser . action . setBadgeBackgroundColor ( { color : colors . badge , tabId } ) ;
@@ -163,7 +51,7 @@ function updateBadge(tabId, status, cdn) {
16351}
16452
16553/**
166- * Clears the badge, setting it globally and per- tab.
54+ * Clears the badge for a tab.
16755 * @param {number } tabId - Browser tab ID
16856 */
16957function clearBadge ( tabId ) {
@@ -181,26 +69,25 @@ function clearBadge(tabId) {
18169
18270// Track navigation start to capture only the first main document request
18371browser . webNavigation . onBeforeNavigate . addListener ( ( details ) => {
184- if ( details . frameId === 0 ) { // Main frame only
72+ if ( details . frameId === 0 ) {
18573 pendingNavigations . add ( details . tabId ) ;
186- tabData . delete ( details . tabId ) ; // Clear stale data
74+ tabData . delete ( details . tabId ) ;
18775 }
18876} ) ;
18977
19078// Update URL after navigation completes (handles redirects)
19179browser . webNavigation . onCompleted . addListener ( ( details ) => {
192- if ( details . frameId === 0 ) { // Main frame only
80+ if ( details . frameId === 0 ) {
19381 const data = tabData . get ( details . tabId ) ;
19482 if ( data && data . url !== details . url ) {
195- data . url = details . url ; // Update to final URL after redirects
83+ data . url = details . url ;
19684 }
19785 }
19886} ) ;
19987
20088// Capture response headers for main document requests
20189browser . webRequest . onHeadersReceived . addListener (
20290 ( details ) => {
203- // Only process main frame requests we're expecting
20491 if ( details . type !== 'main_frame' || details . frameId !== 0 ) return ;
20592 if ( ! pendingNavigations . has ( details . tabId ) ) return ;
20693
@@ -215,11 +102,10 @@ browser.webRequest.onHeadersReceived.addListener(
215102 }
216103 }
217104
218- // Detect CDN and parse status
105+ // Detect CDN and parse status using shared functions
219106 const cdn = detectCDN ( headers ) ;
220107 const status = parseCacheStatus ( headers , cdn ) ;
221108
222- // Store data for popup
223109 tabData . set ( details . tabId , {
224110 url : details . url ,
225111 headers,
@@ -275,13 +161,11 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
275161 sendResponse ( tabData . get ( message . tabId ) || null ) ;
276162 }
277163
278- // Handle performance data from content script
279164 if ( message . type === 'performanceData' && sender . tab ) {
280165 const existing = tabData . get ( sender . tab . id ) ;
281166 if ( existing ) {
282167 existing . performance = message . metrics ;
283168 } else {
284- // Store performance data even if no cache headers yet
285169 tabData . set ( sender . tab . id , {
286170 url : sender . tab . url ,
287171 headers : { } ,
0 commit comments