|
| 1 | +/** |
| 2 | + * Patterns for resources that should be excluded from capture |
| 3 | + * These are typically analytics, tracking, source maps, and other |
| 4 | + * resources that are not needed for offline viewing |
| 5 | + */ |
| 6 | + |
| 7 | +/** |
| 8 | + * URL patterns to exclude from asset collection |
| 9 | + * These patterns are matched against the full URL |
| 10 | + */ |
| 11 | +export const EXCLUDE_URL_PATTERNS = [ |
| 12 | + // Source maps (development files, not needed for viewing) |
| 13 | + /\.map$/i, |
| 14 | + /\.js\.map$/i, |
| 15 | + /\.css\.map$/i, |
| 16 | + |
| 17 | + // Well-known paths (service discovery, not needed offline) |
| 18 | + /\/\.well-known\//i, |
| 19 | + |
| 20 | + // Cloudflare |
| 21 | + /\/cdn-cgi\/rum/i, // Real User Monitoring |
| 22 | + /\/cdn-cgi\/beacon/i, |
| 23 | + /\/cdn-cgi\/trace/i, |
| 24 | + /\/cdn-cgi\/challenge-platform/i, |
| 25 | + /cloudflareinsights\.com/i, |
| 26 | + |
| 27 | + // Google Analytics / Tag Manager |
| 28 | + /google-analytics\.com/i, |
| 29 | + /googletagmanager\.com/i, |
| 30 | + /googletagservices\.com/i, |
| 31 | + /googlesyndication\.com/i, |
| 32 | + /googleadservices\.com/i, |
| 33 | + /doubleclick\.net/i, |
| 34 | + /\/gtag\/js/i, |
| 35 | + /\/ga\.js/i, |
| 36 | + /\/analytics\.js/i, |
| 37 | + /\/gtm\.js/i, |
| 38 | + |
| 39 | + // Facebook |
| 40 | + /connect\.facebook\.net/i, |
| 41 | + /facebook\.com\/tr/i, |
| 42 | + /fbevents\.js/i, |
| 43 | + /pixel\.facebook\.com/i, |
| 44 | + |
| 45 | + // Twitter/X |
| 46 | + /platform\.twitter\.com\/widgets/i, |
| 47 | + /analytics\.twitter\.com/i, |
| 48 | + /t\.co\/i\/adsct/i, |
| 49 | + |
| 50 | + // LinkedIn |
| 51 | + /snap\.licdn\.com/i, |
| 52 | + /linkedin\.com\/px/i, |
| 53 | + |
| 54 | + // Microsoft/Bing |
| 55 | + /clarity\.ms/i, |
| 56 | + /bat\.bing\.com/i, |
| 57 | + |
| 58 | + // Hotjar |
| 59 | + /hotjar\.com/i, |
| 60 | + /static\.hotjar\.com/i, |
| 61 | + |
| 62 | + // Mixpanel |
| 63 | + /mixpanel\.com/i, |
| 64 | + /cdn\.mxpnl\.com/i, |
| 65 | + |
| 66 | + // Segment |
| 67 | + /cdn\.segment\.com/i, |
| 68 | + /api\.segment\.io/i, |
| 69 | + |
| 70 | + // Amplitude |
| 71 | + /amplitude\.com/i, |
| 72 | + /cdn\.amplitude\.com/i, |
| 73 | + |
| 74 | + // Heap |
| 75 | + /heap-analytics\.com/i, |
| 76 | + /heapanalytics\.com/i, |
| 77 | + |
| 78 | + // Intercom |
| 79 | + /widget\.intercom\.io/i, |
| 80 | + /api-iam\.intercom\.io/i, |
| 81 | + /intercom\.io\/widget/i, |
| 82 | + |
| 83 | + // Drift |
| 84 | + /js\.driftt\.com/i, |
| 85 | + /drift\.com/i, |
| 86 | + |
| 87 | + // HubSpot |
| 88 | + /js\.hs-scripts\.com/i, |
| 89 | + /js\.hsforms\.net/i, |
| 90 | + /js\.hs-analytics\.net/i, |
| 91 | + /forms\.hubspot\.com/i, |
| 92 | + /track\.hubspot\.com/i, |
| 93 | + |
| 94 | + // Zendesk |
| 95 | + /static\.zdassets\.com/i, |
| 96 | + /ekr\.zdassets\.com/i, |
| 97 | + |
| 98 | + // Crisp |
| 99 | + /client\.crisp\.chat/i, |
| 100 | + |
| 101 | + // Tawk.to |
| 102 | + /embed\.tawk\.to/i, |
| 103 | + |
| 104 | + // LiveChat |
| 105 | + /cdn\.livechatinc\.com/i, |
| 106 | + |
| 107 | + // Sentry (error tracking) |
| 108 | + /browser\.sentry-cdn\.com/i, |
| 109 | + /sentry\.io\/api/i, |
| 110 | + /ingest\.sentry\.io/i, |
| 111 | + |
| 112 | + // LogRocket |
| 113 | + /cdn\.logrocket\.io/i, |
| 114 | + /r\.logrocket\.io/i, |
| 115 | + |
| 116 | + // FullStory |
| 117 | + /fullstory\.com/i, |
| 118 | + /rs\.fullstory\.com/i, |
| 119 | + |
| 120 | + // Crazy Egg |
| 121 | + /script\.crazyegg\.com/i, |
| 122 | + |
| 123 | + // Optimizely |
| 124 | + /cdn\.optimizely\.com/i, |
| 125 | + /logx\.optimizely\.com/i, |
| 126 | + |
| 127 | + // VWO |
| 128 | + /dev\.visualwebsiteoptimizer\.com/i, |
| 129 | + |
| 130 | + // New Relic |
| 131 | + /js-agent\.newrelic\.com/i, |
| 132 | + /bam\.nr-data\.net/i, |
| 133 | + |
| 134 | + // Datadog |
| 135 | + /datadoghq\.com/i, |
| 136 | + /browser-intake/i, |
| 137 | + |
| 138 | + // Beacon APIs (generic) |
| 139 | + /\/beacon\//i, |
| 140 | + /\/collect\?/i, |
| 141 | + /\/pixel\?/i, |
| 142 | + /\/track\?/i, |
| 143 | + /\/event\?/i, |
| 144 | + /\/log\?/i, |
| 145 | + |
| 146 | + // Common API patterns that won't work offline |
| 147 | + /\/api\/v\d+\//i, // versioned APIs |
| 148 | + /graphql/i, |
| 149 | + /\/webhook/i, |
| 150 | + /\/socket\.io/i, |
| 151 | + /\/ws\//i, // WebSocket |
| 152 | + |
| 153 | + // Social widgets (load external content) |
| 154 | + /platform\.instagram\.com/i, |
| 155 | + /apis\.google\.com\/js\/plusone/i, |
| 156 | + /widgets\.pinterest\.com/i, |
| 157 | + |
| 158 | + // Ad networks |
| 159 | + /ad\.doubleclick\.net/i, |
| 160 | + /pagead2\.googlesyndication\.com/i, |
| 161 | + /adservice\.google\.com/i, |
| 162 | + /amazon-adsystem\.com/i, |
| 163 | + /advertising\.com/i, |
| 164 | + |
| 165 | + // Cookie consent / GDPR |
| 166 | + /cookiebot\.com/i, |
| 167 | + /cdn\.cookielaw\.org/i, |
| 168 | + /consent\.cookiebot\.com/i, |
| 169 | + /onetrust\.com/i, |
| 170 | + /quantcast\.com/i, |
| 171 | + |
| 172 | + // Push notifications |
| 173 | + /onesignal\.com/i, |
| 174 | + /pushwoosh\.com/i, |
| 175 | + /pusher\.com/i, |
| 176 | + |
| 177 | + // A/B testing |
| 178 | + /cdn\.split\.io/i, |
| 179 | + /launchdarkly\.com/i, |
| 180 | + |
| 181 | + // Performance monitoring |
| 182 | + /speedcurve\.com/i, |
| 183 | + /rum\.speedcurve\.com/i, |
| 184 | + |
| 185 | + // Generic patterns |
| 186 | + /\/analytics/i, |
| 187 | + /\/telemetry/i, |
| 188 | + /\/metrics/i, |
| 189 | + /\/stats\//i, |
| 190 | +]; |
| 191 | + |
| 192 | +/** |
| 193 | + * Script src patterns to remove from HTML |
| 194 | + * These scripts should be removed entirely from the captured HTML |
| 195 | + */ |
| 196 | +export const STRIP_SCRIPT_PATTERNS = [ |
| 197 | + // Google |
| 198 | + /google-analytics\.com/i, |
| 199 | + /googletagmanager\.com/i, |
| 200 | + /gtag\/js/i, |
| 201 | + /ga\.js/i, |
| 202 | + /analytics\.js/i, |
| 203 | + |
| 204 | + // Facebook |
| 205 | + /connect\.facebook\.net/i, |
| 206 | + /fbevents\.js/i, |
| 207 | + |
| 208 | + // Cloudflare |
| 209 | + /cloudflareinsights\.com/i, |
| 210 | + /cdn-cgi\/scripts/i, |
| 211 | + |
| 212 | + // Generic analytics |
| 213 | + /hotjar\.com/i, |
| 214 | + /mixpanel\.com/i, |
| 215 | + /segment\.com/i, |
| 216 | + /amplitude\.com/i, |
| 217 | + /heap-analytics/i, |
| 218 | + /intercom\.io/i, |
| 219 | + /drift/i, |
| 220 | + /hs-scripts\.com/i, |
| 221 | + /tawk\.to/i, |
| 222 | + /livechatinc\.com/i, |
| 223 | + /sentry/i, |
| 224 | + /logrocket/i, |
| 225 | + /fullstory/i, |
| 226 | + /crazyegg/i, |
| 227 | + /optimizely/i, |
| 228 | + /newrelic/i, |
| 229 | + /datadoghq/i, |
| 230 | +]; |
| 231 | + |
| 232 | +/** |
| 233 | + * Inline script content patterns to remove |
| 234 | + * These patterns match content inside <script> tags that should be removed |
| 235 | + */ |
| 236 | +export const STRIP_INLINE_SCRIPT_PATTERNS = [ |
| 237 | + // Google Analytics initialization |
| 238 | + /gtag\s*\(\s*['"]config['"]/i, |
| 239 | + /ga\s*\(\s*['"]create['"]/i, |
| 240 | + /ga\s*\(\s*['"]send['"]/i, |
| 241 | + /_gaq\.push/i, |
| 242 | + /GoogleAnalyticsObject/i, |
| 243 | + |
| 244 | + // Facebook Pixel |
| 245 | + /fbq\s*\(\s*['"]init['"]/i, |
| 246 | + /fbq\s*\(\s*['"]track['"]/i, |
| 247 | + |
| 248 | + // Generic tracking |
| 249 | + /window\._analytics/i, |
| 250 | + /window\.analytics\.track/i, |
| 251 | + /window\.dataLayer/i, |
| 252 | + /dataLayer\.push/i, |
| 253 | + |
| 254 | + // Hotjar |
| 255 | + /hjid/i, |
| 256 | + /hotjar/i, |
| 257 | + |
| 258 | + // Intercom |
| 259 | + /window\.intercomSettings/i, |
| 260 | + /Intercom\s*\(/i, |
| 261 | + |
| 262 | + // Drift |
| 263 | + /drift\.load/i, |
| 264 | + |
| 265 | + // HubSpot |
| 266 | + /_hsq\.push/i, |
| 267 | +]; |
| 268 | + |
| 269 | +/** |
| 270 | + * Image/pixel patterns to remove (tracking pixels) |
| 271 | + */ |
| 272 | +export const STRIP_PIXEL_PATTERNS = [ |
| 273 | + /facebook\.com\/tr/i, |
| 274 | + /pixel\.facebook\.com/i, |
| 275 | + /google-analytics\.com\/__utm/i, |
| 276 | + /bat\.bing\.com/i, |
| 277 | + /analytics\.twitter\.com/i, |
| 278 | + /linkedin\.com\/px/i, |
| 279 | + /t\.co\/i\/adsct/i, |
| 280 | +]; |
| 281 | + |
| 282 | +/** |
| 283 | + * Check if a URL should be excluded from capture |
| 284 | + */ |
| 285 | +export function shouldExcludeUrl(url) { |
| 286 | + if (!url) return false; |
| 287 | + |
| 288 | + return EXCLUDE_URL_PATTERNS.some(pattern => pattern.test(url)); |
| 289 | +} |
| 290 | + |
| 291 | +/** |
| 292 | + * Check if a script src should be stripped from HTML |
| 293 | + */ |
| 294 | +export function shouldStripScript(src) { |
| 295 | + if (!src) return false; |
| 296 | + |
| 297 | + return STRIP_SCRIPT_PATTERNS.some(pattern => pattern.test(src)); |
| 298 | +} |
| 299 | + |
| 300 | +/** |
| 301 | + * Check if inline script content should be removed |
| 302 | + */ |
| 303 | +export function shouldStripInlineScript(content) { |
| 304 | + if (!content) return false; |
| 305 | + |
| 306 | + return STRIP_INLINE_SCRIPT_PATTERNS.some(pattern => pattern.test(content)); |
| 307 | +} |
| 308 | + |
| 309 | +/** |
| 310 | + * Check if an image is a tracking pixel |
| 311 | + */ |
| 312 | +export function isTrackingPixel(src) { |
| 313 | + if (!src) return false; |
| 314 | + |
| 315 | + return STRIP_PIXEL_PATTERNS.some(pattern => pattern.test(src)); |
| 316 | +} |
| 317 | + |
| 318 | +/** |
| 319 | + * Get a human-readable reason for why a URL was excluded |
| 320 | + */ |
| 321 | +export function getExcludeReason(url) { |
| 322 | + if (!url) return null; |
| 323 | + |
| 324 | + if (/\.map$/i.test(url)) return 'source-map'; |
| 325 | + if (/\/\.well-known\//i.test(url)) return 'well-known'; |
| 326 | + if (/cdn-cgi/i.test(url)) return 'cloudflare-service'; |
| 327 | + if (/google.*analytics|gtag|ga\.js/i.test(url)) return 'google-analytics'; |
| 328 | + if (/facebook|fbevents/i.test(url)) return 'facebook-tracking'; |
| 329 | + if (/twitter|t\.co/i.test(url)) return 'twitter-tracking'; |
| 330 | + if (/hotjar|mixpanel|segment|amplitude|heap/i.test(url)) return 'analytics'; |
| 331 | + if (/intercom|drift|hubspot|zendesk|crisp|tawk|livechat/i.test(url)) { |
| 332 | + return 'chat-widget'; |
| 333 | + } |
| 334 | + if (/sentry|logrocket|fullstory|newrelic|datadog/i.test(url)) { |
| 335 | + return 'error-monitoring'; |
| 336 | + } |
| 337 | + if (/beacon|collect\?|pixel\?|track\?/i.test(url)) return 'tracking-beacon'; |
| 338 | + if (/\/api\/|graphql|webhook/i.test(url)) return 'api-endpoint'; |
| 339 | + if (/ad\.|adsystem|advertising/i.test(url)) return 'advertising'; |
| 340 | + if (/cookiebot|cookielaw|onetrust|quantcast/i.test(url)) |
| 341 | + return 'cookie-consent'; |
| 342 | + |
| 343 | + return 'excluded-pattern'; |
| 344 | +} |
0 commit comments