Last active
January 5, 2025 00:31
-
-
Save hutt/62e9355afb0d4ff0eeecd39bc51652de to your computer and use it in GitHub Desktop.
This Cloudflare Worker proxies and caches requests for privacy-friendly light-yt.js YouTube video embeds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This Cloudflare Worker proxies and caches requests for privacy-friendly light-yt.js YouTube video embeds. | |
| // https://gist.github.com/hutt/62e9355afb0d4ff0eeecd39bc51652de | |
| // Base URLs for YouTube no-cookie and YouTube image CDN | |
| const YT_NOCOOKIE_URL = 'https://www.youtube-nocookie.com'; | |
| const YTIMG_URL = 'https://i.ytimg.com'; | |
| // Path prefix for our proxy | |
| const PROXY_PATH = '/yt-proxy'; | |
| // Default cache duration in seconds (1 day) | |
| const DEFAULT_CACHE_DURATION = 1 * 24 * 60 * 60; | |
| // Listen for incoming fetch events | |
| addEventListener('fetch', event => { | |
| event.respondWith(handleRequest(event)); | |
| }); | |
| async function handleRequest(event) { | |
| try { | |
| const request = event.request; | |
| const url = new URL(request.url); | |
| const path = url.pathname; | |
| // Check if the request path starts with our proxy path | |
| if (path.startsWith(PROXY_PATH)) { | |
| // Extract the sub-path after the proxy path | |
| const subPath = path.slice(PROXY_PATH.length + 1); | |
| // Split the sub-path to get the proxy type and target path | |
| const [proxyType, ...restPath] = subPath.split('/'); | |
| const targetPath = restPath.join('/'); | |
| let targetUrl; | |
| let isJson = false; | |
| // Determine the target URL based on the proxy type | |
| if (proxyType === 'data') { | |
| targetUrl = new URL(`${YT_NOCOOKIE_URL}/${targetPath}`); | |
| isJson = true; | |
| } else if (proxyType === 'thumbnail') { | |
| targetUrl = new URL(`${YTIMG_URL}/${targetPath}`); | |
| } else { | |
| // Return an error for invalid proxy types | |
| return new Response('Invalid proxy type', { status: 400 }); | |
| } | |
| // Copy all query parameters from the original request to the target URL | |
| for (const [key, value] of url.searchParams) { | |
| targetUrl.searchParams.append(key, value); | |
| } | |
| // Try to get the response from cache first | |
| const cache = caches.default; | |
| let response = await cache.match(request); | |
| if (!response) { | |
| // If not in cache, create a new headers object without sensitive headers | |
| const cleanHeaders = new Headers(request.headers); | |
| cleanHeaders.delete('cf-connecting-ip'); | |
| cleanHeaders.delete('x-real-ip'); | |
| // Fetch the resource from the target URL | |
| response = await fetch(targetUrl.toString(), { | |
| headers: cleanHeaders, | |
| method: 'GET', | |
| }); | |
| if (response.ok) { | |
| // Create a new response with custom headers | |
| const newResponse = new Response(response.body, response); | |
| // Set cache control headers | |
| newResponse.headers.set('Cache-Control', `public, max-age=${DEFAULT_CACHE_DURATION}, stale-while-revalidate=${DEFAULT_CACHE_DURATION}`); | |
| // Set ETag header | |
| newResponse.headers.set('ETag', response.headers.get('ETag') || crypto.randomUUID()); | |
| if (isJson) { | |
| // For JSON data, set the content type to application/json | |
| newResponse.headers.set('Content-Type', 'application/json'); | |
| } else { | |
| // For thumbnails, keep the original content type if it's an image | |
| const contentType = response.headers.get('Content-Type'); | |
| if (contentType && contentType.startsWith('image/')) { | |
| newResponse.headers.set('Content-Type', contentType); | |
| } | |
| // Remove Content-Encoding header for images as they're already compressed | |
| newResponse.headers.delete('Content-Encoding'); | |
| } | |
| // Put the response in cache | |
| event.waitUntil(cache.put(request, newResponse.clone())); | |
| return newResponse; | |
| } | |
| } | |
| return response; | |
| } | |
| // If the path doesn't start with our proxy path, return a 404 | |
| return new Response('Not Found', { status: 404 }); | |
| } catch (error) { | |
| // Return a 500 error if something goes wrong | |
| return new Response(`Error: ${error.message}`, { status: 500 }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment