1/** 2 * @license 3 * Copyright 2025 Google LLC 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6// service-worker.js 7 8// Define the target URL that we want to intercept and proxy. 9const TARGET_URL_PREFIX = 'https://generativelanguage.googleapis.com'; 10 11const BYPASS_PATHS = 12 ['generateContentPerUserQuota', 'streamGenerateContentPerUserQuota']; 13 14// Installation event: 15self.addEventListener('install', (event) => { 16 try { 17 console.log('Service Worker: Installing...'); 18 event.waitUntil(self.skipWaiting()); 19 } catch (error) { 20 console.error('Service Worker: Error during install event:', error); 21 // If skipWaiting fails, the new SW might get stuck in a waiting state. 22 } 23}); 24 25// Activation event: 26self.addEventListener('activate', (event) => { 27 try { 28 console.log('Service Worker: Activating...'); 29 event.waitUntil(self.clients.claim()); 30 } catch (error) { 31 console.error('Service Worker: Error during activate event:', error); 32 // If clients.claim() fails, the SW might not control existing pages until 33 // next nav. 34 } 35}); 36 37// Fetch event: 38self.addEventListener('fetch', (event) => { 39 try { 40 const requestUrl = event.request.url; 41 42 if (requestUrl.startsWith(TARGET_URL_PREFIX) && 43 !BYPASS_PATHS.some(path => requestUrl.includes(path))) { 44 console.log(`Service Worker: Intercepting request to ${requestUrl}`); 45 46 const remainingPathAndQuery = 47 requestUrl.substring(TARGET_URL_PREFIX.length); 48 49 const separator = remainingPathAndQuery.includes('?') ? '&' : '?'; 50 const proxyUrl = 51 `${self.location.origin}/gemini-api-proxy${remainingPathAndQuery}${separator}__applet_proxy=true`; 52 53 console.log(`Service Worker: Proxying to ${proxyUrl}`); 54 55 // Construct headers for the request to the proxy 56 const newHeaders = new Headers(); 57 // Copy essential headers from the original request 58 // For OPTIONS (preflight) requests, Access-Control-Request-* are 59 // critical. For actual requests (POST, GET), Content-Type, Accept etc. 60 const headersToCopy = [ 61 'Content-Type', 62 'Accept', 63 'Access-Control-Request-Method', 64 'Access-Control-Request-Headers', 65 ]; 66 67 for (const headerName of headersToCopy) { 68 if (event.request.headers.has(headerName)) { 69 newHeaders.set(headerName, event.request.headers.get(headerName)); 70 } 71 } 72 73 if (event.request.method === 'POST') { 74 // Ensure Content-Type is set for POST requests to the proxy, defaulting 75 // to application/json 76 if (!newHeaders.has('Content-Type')) { 77 console.warn( 78 'Service Worker: POST request to proxy was missing Content-Type in newHeaders. Defaulting to application/json.'); 79 newHeaders.set('Content-Type', 'application/json'); 80 } else { 81 console.log( 82 `Service Worker: POST request to proxy has Content-Type: ${ 83 newHeaders.get('Content-Type')}`); 84 } 85 } 86 87 const createProxyRequest = async () => { 88 const body = 89 event.request.method !== 'GET' && event.request.method !== 'HEAD' ? 90 await event.request.arrayBuffer() : 91 undefined; 92 93 const requestOptions = { 94 method: event.request.method, 95 headers: newHeaders, 96 body: body, 97 mode: event.request.mode, 98 credentials: event.request.credentials, 99 cache: event.request.cache, 100 redirect: event.request.redirect, 101 referrer: event.request.referrer, 102 integrity: event.request.integrity, 103 }; 104 105 // Only set duplex if there's a body and it's a relevant method 106 if (body) { 107 requestOptions.duplex = 'half'; 108 } 109 110 return new Request(proxyUrl, requestOptions); 111 }; 112 113 const promise = 114 createProxyRequest() 115 .then(proxyRequest => fetch(proxyRequest)) 116 .then((response) => { 117 console.log(`Service Worker: Successfully proxied request to ${ 118 proxyUrl}, Status: ${response.status}`); 119 return response; 120 }) 121 .catch((error) => { 122 // Log more error details 123 console.error(`Service Worker: Error proxying request to ${ 124 proxyUrl}. Message: ${error.message}, Name: ${
125 error.name}, Stack: ${error.stack}`); 126 return new Response( 127 JSON.stringify({ 128 error: 'Proxying failed', 129 details: error.message, 130 name: error.name, 131 proxiedUrl: proxyUrl 132 }), 133 { 134 status: 135 502, // Bad Gateway is appropriate for proxy errors 136 headers: {'Content-Type': 'application/json'} 137 }); 138 }); 139 140 event.respondWith(promise); 141 142 } else { 143 // If the request URL doesn't match our target, let it proceed as normal. 144 event.respondWith(fetch(event.request)); 145 } 146 } catch (error) { 147 // Log more error details for unhandled errors too 148 console.error( 149 'Service Worker: Unhandled error in fetch event handler. Message:', 150 error.message, 'Name:', error.name, 'Stack:', error.stack); 151 event.respondWith(new Response( 152 JSON.stringify({ 153 error: 'Service worker fetch handler failed', 154 details: error.message, 155 name: error.name 156 }), 157 {status: 500, headers: {'Content-Type': 'application/json'}})); 158 } 159});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.