image cache on cloudflare r2

feat: just redirect to image

dunkirk.sh e64ef501 33060f4d

verified
Changed files
+11 -90
src
+4 -4
manifest.yaml
··· 1 1 display_information: 2 - name: Tacy 2 + name: tacy 3 3 description: Automatically uploads files to R2 and provides public URLs 4 - background_color: "#16302b" 4 + background_color: "#0e2d52" 5 5 features: 6 6 bot_user: 7 7 display_name: Tacy ··· 12 12 - files:read 13 13 - reactions:write 14 14 - chat:write 15 + - im:history 16 + - channels:history 15 17 settings: 16 18 event_subscriptions: 17 19 request_url: https://l4.dunkirk.sh/slack/events 18 20 bot_events: 19 21 - message.channels 20 - - message.groups 21 22 - message.im 22 - - message.mpim 23 23 org_deploy_enabled: false 24 24 socket_mode_enabled: false 25 25 token_rotation_enabled: false
+6 -86
src/index.ts
··· 153 153 return new Response("Not found", { status: 404 }); 154 154 } 155 155 156 - // Prevent infinite loops 157 - if (/image-resizing/.test(request.headers.get("via") || "")) { 158 - const object = await env.IMAGES.get(imageKey); 159 - if (!object) { 160 - return new Response("Not found", { status: 404 }); 161 - } 162 - return new Response(object.body, { 163 - headers: { 164 - "Content-Type": 165 - object.httpMetadata?.contentType || "application/octet-stream", 166 - "Cache-Control": "public, max-age=31536000", 167 - }, 168 - }); 169 - } 170 - 171 - // Parse transformation params 172 - const width = url.searchParams.get("w"); 173 - const height = url.searchParams.get("h"); 174 - const quality = url.searchParams.get("q") || "85"; 175 - const format = url.searchParams.get("f") || "auto"; 176 - const fit = url.searchParams.get("fit") || "scale-down"; 177 - 178 - // Check cache first 179 - const cacheKey = new Request(url.toString(), request); 180 - const cache = caches.default; 181 - let response = await cache.match(cacheKey); 182 - if (response) { 183 - return response; 184 - } 185 - 186 - // Fetch from R2 187 - const object = await env.IMAGES.get(imageKey); 156 + // Verify object exists before redirecting 157 + const object = await env.IMAGES.head(imageKey); 188 158 if (!object) { 189 159 return new Response("Not found", { status: 404 }); 190 160 } 191 161 192 - // In local dev, Cloudflare image transformations don't work 193 - // So we just serve the original image 194 - const isLocalDev = 195 - url.hostname === "localhost" || url.hostname.includes("127.0.0.1"); 196 - 197 - if (isLocalDev) { 198 - const headers = new Headers(); 199 - headers.set( 200 - "Content-Type", 201 - object.httpMetadata?.contentType || "image/png", 202 - ); 203 - headers.set("Cache-Control", "public, max-age=31536000"); 204 - return new Response(object.body, { headers }); 205 - } 206 - 207 - // Build image transformation options 208 - const imageOptions: any = { 209 - quality: parseInt(quality), 210 - format, 211 - fit, 212 - }; 213 - 214 - if (width) imageOptions.width = parseInt(width); 215 - if (height) imageOptions.height = parseInt(height); 216 - 217 - // Determine format based on Accept header if auto 218 - if (format === "auto") { 219 - const accept = request.headers.get("accept") || ""; 220 - if (/image\/avif/.test(accept)) { 221 - imageOptions.format = "avif"; 222 - } else if (/image\/webp/.test(accept)) { 223 - imageOptions.format = "webp"; 224 - } 225 - } 226 - 227 - // Fetch and transform 228 - const imageResponse = await fetch(request.url, { 229 - cf: { 230 - image: imageOptions, 231 - }, 232 - }); 233 - 234 - // Clone response with cache headers 235 - response = new Response(imageResponse.body, imageResponse); 236 - response.headers.set( 237 - "Cache-Control", 238 - "public, max-age=31536000, s-maxage=86400", 239 - ); 240 - response.headers.set("Vary", "Accept"); 241 - 242 - // Cache asynchronously 243 - ctx.waitUntil(cache.put(cacheKey, response.clone())); 244 - 245 - return response; 162 + // Redirect to R2 public URL - much more efficient than proxying 163 + const r2PublicUrl = `${env.R2_PUBLIC_URL}/${imageKey}`; 164 + return Response.redirect(r2PublicUrl, 307); 246 165 } 247 166 248 167 async function handleImageUpload( ··· 308 227 IMAGES: R2Bucket; 309 228 AUTH_TOKEN: string; 310 229 PUBLIC_URL: string; 230 + R2_PUBLIC_URL: string; 311 231 ALLOWED_CHANNELS?: string; 312 232 }
+1
wrangler.toml
··· 12 12 13 13 [vars] 14 14 PUBLIC_URL = "https://l4.dunkirk.sh" 15 + R2_PUBLIC_URL = "https://pub-3a6e58e5ca3b4847b3247131f8e7b413.r2.dev" 15 16 ALLOWED_CHANNELS = "C08EKUY7QVA" # Comma-separated channel IDs 16 17 17 18 # Set secrets: