1
- 'use strict' ;
2
-
3
- importScripts ( '/utils.js' ) ;
4
-
5
- const { getFromStorage, setToStorage, getCurrentTabRootDomainFromStorage, getUrlProtocolPlusHostname } = Utils ;
6
-
7
- const Engine = {
8
- storageCache : { } ,
9
- /**
10
- * Initialize Engine
11
- */
12
- async init ( ) {
13
- // get all stored domains and their values from local storage
14
- Engine . storageCache = await Engine . getAllStorageSyncData ( ) ;
15
-
16
- // on tab change, get the current tab's root domain and check if it has Tailwind CSS
17
- this . performOnTabChange ( ) ;
18
-
19
- // create alarm to delete local storage and cache
20
- Engine . createAlarm ( ) ;
21
-
22
- // delete local storage and cache after a week
23
- Engine . reset ( 'reset' ) ;
24
-
25
- chrome . webRequest . onCompleted . addListener ( Engine . onStyleSheetRequestComplete , {
26
- urls : [ 'http://*/*' , 'https://*/*' ] ,
27
- types : [ 'stylesheet' ] ,
28
- } ) ;
29
- } ,
30
-
31
- disAllowedList : [ 'https://chrome.google.com' , 'https://chrome.google.com/' ] ,
32
-
33
- /**
34
- * Create an alarm to delete local storage and cache
35
- */
36
- createAlarm ( ) {
37
- chrome . alarms . create ( 'reset' , {
38
- // get week in minutes
39
- periodInMinutes : 60 * 24 * 7 ,
40
- } ) ;
41
-
42
- console . log ( 'Reset alarm set to occur every week' ) ;
43
- } ,
44
-
45
- /**
46
- * Listen for alarm and act accordingly
47
- */
48
- reset ( alarmName ) {
49
- chrome . alarms . onAlarm . addListener ( async alarm => {
50
- if ( alarm . name === alarmName ) {
51
- try {
52
- console . log ( alarmName , 'Resetting...' ) ;
53
- Engine . resetCacheAndLocalStorage ( )
54
- . then ( ( ) => {
55
- console . log ( 'storage after reset' , Engine . storageCache ) ;
56
- } )
57
- . catch ( error => console . error ( 'reset' , error ) ) ;
58
- } catch ( error ) {
59
- console . error ( 'reset' , error ) ;
60
- }
61
- }
62
- } ) ;
63
- } ,
64
-
65
- /**
66
- * Clear caches
67
- */
68
- async resetCacheAndLocalStorage ( ) {
69
- Engine . storageCache = { } ;
70
- await Utils . promisify ( chrome . storage . sync , 'clear' ) ;
71
- } ,
1
+ // background.js
2
+
3
+ // Cache for Tailwind detection results (domain -> { hasTailwindCSS, tailwindVersion })
4
+ const domainCache = { } ;
5
+
6
+ // Helper functions
7
+ const getDomain = ( url ) => {
8
+ try {
9
+ const { hostname } = new URL ( url ) ;
10
+ return hostname ;
11
+ } catch ( error ) {
12
+ console . error ( "Error parsing URL:" , error ) ;
13
+ return null ;
14
+ }
15
+ } ;
72
16
73
- /**
74
- * Get all storage data
75
- * @returns {Promise<any> }
76
- * @private
77
- * @see https://developer.chrome.com/extensions/storage#type-StorageArea
78
- * @see https://developer.chrome.com/extensions/storage#method-StorageArea.get
79
- * @see https://developer.chrome.com/extensions/storage#type-StorageArea.StorageArea
80
- * */
81
- async getAllStorageSyncData ( ) {
82
- // Immediately return a promise and start asynchronous work
83
- return new Promise ( ( resolve , reject ) => {
84
- // Asynchronously fetch all data from storage.sync.
85
- chrome . storage . sync . get ( null , items => {
86
- // Pass any observed errors down the promise chain.
87
- if ( chrome . runtime . lastError ) {
88
- return reject ( chrome . runtime . lastError ) ;
89
- }
90
- // Pass the data retrieved from storage down the promise chain.
91
- resolve ( items ) ;
92
- } ) ;
93
- } ) ;
94
- } ,
17
+ const updateCacheAndBadge = ( domain , hasTailwindCSS , tailwindVersion ) => {
18
+ domainCache [ domain ] = { hasTailwindCSS, tailwindVersion } ;
19
+ console . log ( `Updated cache for ${ domain } : ${ hasTailwindCSS } , version: ${ tailwindVersion } ` ) ;
20
+ updateBadge ( hasTailwindCSS , tailwindVersion ) ;
21
+ } ;
95
22
96
- /**
97
- * Perform action on tab change
98
- */
99
- performOnTabChange ( ) {
100
- chrome . tabs . onActivated . addListener ( async ( ) => {
101
- getCurrentTabRootDomainFromStorage ( ) . then ( ( { storage, rootDomain } ) => {
102
- // check cache for root domain
103
- if ( ! Engine . storageCache [ rootDomain ] && Engine . beginsWithHttpProtocols ( rootDomain ) && ! Engine . disAllowedList . includes ( rootDomain ) ) {
104
- fetch ( rootDomain )
105
- . then ( async function ( response ) {
106
- // regex get stylesheet
107
- const regex = / < l i n k [ ^ > ] * r e l = " s t y l e s h e e t " [ ^ > ] * h r e f = " ( [ ^ " ] * ) " [ ^ > ] * > / g;
23
+ const updateBadge = ( hasTailwindCSS , tailwindVersion = "unknown" ) => {
24
+ const badgeText = hasTailwindCSS
25
+ ? tailwindVersion === "unknown"
26
+ ? "UN"
27
+ : `T${ tailwindVersion . split ( "." ) [ 0 ] } `
28
+ : "" ;
29
+ chrome . action . setBadgeText ( { text : badgeText } ) ;
30
+ chrome . action . setTitle ( {
31
+ title : hasTailwindCSS
32
+ ? `Tailwind CSS v${ tailwindVersion } `
33
+ : "This website is not using Tailwind CSS" ,
34
+ } ) ;
35
+ } ;
108
36
109
- const links = [ ] ;
110
- let match ;
111
- const text = await response . text ( ) ;
37
+ const clearBadge = ( ) => {
38
+ chrome . action . setBadgeText ( { text : "" } ) ;
39
+ chrome . action . setTitle ( { title : "Tailwind CSS Detector" } ) ;
40
+ } ;
112
41
113
- while ( ( match = regex . exec ( text ) ) !== null ) {
114
- links . push ( match [ 1 ] ) ;
115
- }
42
+ const resetCache = ( ) => {
43
+ Object . keys ( domainCache ) . forEach ( ( domain ) => {
44
+ delete domainCache [ domain ] ;
45
+ } ) ;
46
+ console . log ( "Cache reset successfully via scheduled alarm" ) ;
47
+ clearBadge ( ) ;
48
+ } ;
116
49
117
- if ( links . length > 0 ) {
118
- for await ( const link of links ) {
119
- if ( Engine . beginsWithHttpProtocols ( link ) && ! Engine . disAllowedList . includes ( link ) ) {
120
- await Engine . fetchStyleSheet ( link , rootDomain ) ;
121
- } else {
122
- await Engine . fetchStyleSheet ( `${ rootDomain } ${ link } ` , rootDomain ) ;
123
- }
124
- }
50
+ const evaluateTab = ( tabId ) => {
51
+ chrome . tabs . get ( tabId , ( tab ) => {
52
+ if (
53
+ tab &&
54
+ tab . url &&
55
+ ! tab . url . startsWith ( "chrome://" ) &&
56
+ tab . url !== "about:blank" &&
57
+ tab . url !== "chrome://newtab/"
58
+ ) {
59
+ const domain = getDomain ( tab . url ) ;
60
+ if ( domain && domainCache [ domain ] ) {
61
+ console . log ( `Cache hit: ${ domain } ` ) ;
62
+ updateBadge ( domainCache [ domain ] . hasTailwindCSS , domainCache [ domain ] . tailwindVersion ) ;
63
+ } else {
64
+ console . log ( `Cache miss: ${ domain } ` ) ;
65
+ chrome . scripting . executeScript (
66
+ {
67
+ target : { tabId } ,
68
+ files : [ "content.js" ] ,
69
+ } ,
70
+ ( ) => {
71
+ chrome . tabs . sendMessage ( tabId , { action : "checkForTailwindCSS" } , ( response ) => {
72
+ if ( chrome . runtime . lastError ) {
73
+ console . error ( "Error sending message to tab:" , chrome . runtime . lastError . message ) ;
74
+ clearBadge ( ) ;
125
75
} else {
126
- // no stylesheet found
127
- console . log ( 'no stylesheet found for root domain -' , rootDomain ) ;
128
-
129
- await setToStorage ( rootDomain , { } ) ;
130
- Engine . storageCache [ rootDomain ] = { } ;
76
+ console . log ( `Response from content script for ${ domain } :` , response ) ;
77
+ if ( response && typeof response . hasTailwindCSS !== "undefined" ) {
78
+ updateCacheAndBadge ( domain , response . hasTailwindCSS , response . tailwindVersion ) ;
79
+ }
131
80
}
132
- } )
133
- . catch ( error => console . log ( 'fetch error' , error ) ) ;
134
- } else {
135
- console . log ( 'cache hit' , rootDomain ) ;
136
- }
137
- } ) ;
138
- } ) ;
139
- } ,
140
-
141
- /**
142
- * Check if string is starts with https:// or http:// or www.
143
- * @param {String } url
144
- * */
145
- beginsWithHttpProtocols ( url ) {
146
- if ( url ) {
147
- return url . startsWith ( 'https://' ) || url . startsWith ( 'http://' ) || url . startsWith ( 'www.' ) ;
148
- }
149
- } ,
150
-
151
- async fetchStyleSheet ( url , rootDomain ) {
152
- fetch ( url )
153
- . then ( response => response . text ( ) )
154
- . then ( async text => {
155
- await Engine . analyze ( text , rootDomain ) ;
156
- } )
157
- . catch ( error => console . log ( rootDomain , error ) ) ;
158
- } ,
159
-
160
- /**
161
- * analyze stylesheets
162
- * @param {Object } request
163
- */
164
- async onStyleSheetRequestComplete ( request ) {
165
- await Engine . fetchStyleSheet ( request . url , getUrlProtocolPlusHostname ( request . initiator ) ) ;
166
- } ,
167
-
168
- async analyze ( text , rootDomain ) {
169
- if ( rootDomain ) {
170
- // detect tailwindcss
171
- const regexHasTailwindcss = / (?< ! [ \w \d ] ) (?: t a i l w i n d | t a i l w i n d c s s | - - t w - b g - o p a c i t y | - - t w - h u e - r o t a t e | - - t w - t r a n s l a t e - x | - - t w - r i n g - o f f s e t - w i d t h | - - t w - r i n g - s h a d o w | - - t w - c o n t e n t ) (? ! [ \w \d ] ) / gi;
172
- const hasTailwindCss = regexHasTailwindcss . test ( text ) ;
173
-
174
- // detect tailwindcss version
175
- const regexHasVersion = / (?: ^ | \s ) t a i l w i n d c s s \s + ( [ ^ \s ] + ) / gi;
176
- const versions = [ ] ;
177
-
178
- let match ;
179
-
180
- while ( ( match = regexHasVersion . exec ( text ) ) ) {
181
- versions . push ( match [ 1 ] ) ;
81
+ } ) ;
82
+ }
83
+ ) ;
182
84
}
183
-
184
- await setToStorage ( rootDomain , { versions, hasTailwindCss } ) ;
185
-
186
- // Cache the data
187
- Engine . storageCache [ rootDomain ] = { versions, hasTailwindCss } ;
85
+ } else {
86
+ clearBadge ( ) ;
87
+ console . log ( `Skipping tab with URL: ${ tab ? tab . url : "unknown" } ` ) ;
188
88
}
189
- } ,
89
+ } ) ;
190
90
} ;
191
91
192
- Engine . init ( ) ;
92
+ // Event listeners
93
+ chrome . tabs . onActivated . addListener ( ( { tabId } ) => evaluateTab ( tabId ) ) ;
94
+ chrome . tabs . onUpdated . addListener ( ( tabId , changeInfo , tab ) => {
95
+ if ( changeInfo . status === "complete" ) {
96
+ evaluateTab ( tabId ) ;
97
+ }
98
+ } ) ;
99
+ chrome . tabs . onRemoved . addListener ( clearBadge ) ;
100
+
101
+ // Create an alarm to reset the cache every two weeks
102
+ chrome . alarms . create ( "resetCacheAlarm" , { periodInMinutes : 20160 } ) ;
103
+
104
+ // Listen for the cache reset alarm
105
+ chrome . alarms . onAlarm . addListener ( ( alarm ) => {
106
+ if ( alarm . name === "resetCacheAlarm" ) {
107
+ resetCache ( ) ;
108
+ }
109
+ } ) ;
110
+
111
+ // Listen for messages from the popup
112
+ chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
113
+ console . log ( "Message received in background script:" , message ) ;
114
+
115
+ if ( message . requestUpdate ) {
116
+ chrome . tabs . query ( { active : true , currentWindow : true } , ( tabs ) => {
117
+ if ( tabs . length > 0 ) {
118
+ const tabId = tabs [ 0 ] . id ;
119
+ chrome . tabs . get ( tabId , ( tab ) => {
120
+ if ( tab && tab . url ) {
121
+ const domain = getDomain ( tab . url ) ;
122
+ if ( domain && domainCache [ domain ] ) {
123
+ console . log ( `Popup Cache hit: ${ domain } ` ) ;
124
+ sendResponse ( domainCache [ domain ] ) ;
125
+ } else {
126
+ console . log ( `Popup Cache miss: ${ domain } ` ) ;
127
+ evaluateTab ( tabId ) ;
128
+ sendResponse ( { hasTailwindCSS : false , tailwindVersion : "unknown" } ) ;
129
+ }
130
+ }
131
+ } ) ;
132
+ } else {
133
+ console . log ( "No active tab available" ) ;
134
+ }
135
+ } ) ;
136
+ return true ; // To allow asynchronous `sendResponse`
137
+ }
138
+
139
+ if ( typeof message . hasTailwindCSS !== "undefined" ) {
140
+ const tabId = sender . tab ?. id ;
141
+ if ( tabId ) {
142
+ chrome . tabs . get ( tabId , ( tab ) => {
143
+ if ( tab && tab . url ) {
144
+ const domain = getDomain ( tab . url ) ;
145
+ if ( domain ) {
146
+ updateCacheAndBadge ( domain , message . hasTailwindCSS , message . tailwindVersion ) ;
147
+ }
148
+ }
149
+ } ) ;
150
+ }
151
+ } else {
152
+ console . log ( "Invalid message received" ) ;
153
+ }
154
+ sendResponse ( { status : "done" } ) ;
155
+ return true ; // Ensure the sendResponse is maintained
156
+ } ) ;
0 commit comments