From 9827100d1960db5e66dd7af233579343f2b3f0f8 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Fri, 13 Jun 2025 13:57:43 +0300 Subject: [PATCH] fix: Use images --- infra/cdn.ts | 22 +++++++++ infra/storage.ts | 6 ++- infra/www.ts | 2 + infra/zero.ts | 8 ++-- packages/www/src/assets/service-worker.js | 54 +++++++++++++++++++++++ packages/www/src/index.tsx | 13 ++++++ packages/www/src/sst-env.d.ts | 1 + sst-env.d.ts | 8 ++++ 8 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 infra/cdn.ts create mode 100644 packages/www/src/assets/service-worker.js diff --git a/infra/cdn.ts b/infra/cdn.ts new file mode 100644 index 00000000..81f657de --- /dev/null +++ b/infra/cdn.ts @@ -0,0 +1,22 @@ +import { domain } from "./dns"; +import { storage } from "./storage"; + +export const cdn = new sst.aws.Router("CDNRouter", { + routes: { + "/public": { + bucket: storage, + rewrite: { + regex: "^/public/([a-zA-Z0-9_-]+)$", + to: "/images/$1" + }, + }, + }, + domain: { + name: "cdn." + domain, + dns: sst.cloudflare.dns() + } +}); + +export const outputs = { + cdn: cdn.url +} \ No newline at end of file diff --git a/infra/storage.ts b/infra/storage.ts index ef5af0c6..115e6a5d 100644 --- a/infra/storage.ts +++ b/infra/storage.ts @@ -1 +1,5 @@ -export const storage = new sst.aws.Bucket("Storage"); \ No newline at end of file +export const storage = new sst.aws.Bucket("Storage", { + access: "cloudfront" +}); + +export const zeroStorage = new sst.aws.Bucket("ZeroStorage"); \ No newline at end of file diff --git a/infra/www.ts b/infra/www.ts index 360c373f..56c24168 100644 --- a/infra/www.ts +++ b/infra/www.ts @@ -1,5 +1,6 @@ // This is the website part where people play and connect import { api } from "./api"; +import { cdn } from "./cdn"; import { auth } from "./auth"; import { zero } from "./zero"; import { domain } from "./dns"; @@ -16,6 +17,7 @@ new sst.aws.StaticSite("Web", { }, environment: { VITE_API_URL: api.url, + VITE_CDN_URL: cdn.url, VITE_STAGE: $app.stage, VITE_AUTH_URL: auth.url, VITE_ZERO_URL: zero.url, diff --git a/infra/zero.ts b/infra/zero.ts index ff8755a8..54c30a0f 100644 --- a/infra/zero.ts +++ b/infra/zero.ts @@ -2,8 +2,8 @@ import { auth } from "./auth"; import { domain } from "./dns"; import { readFileSync } from "fs"; import { cluster } from "./cluster"; -import { storage } from "./storage"; import { postgres } from "./postgres"; +import { zeroStorage } from "./storage"; const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`; @@ -32,7 +32,7 @@ const zeroEnv = { ? { } : { - ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero/0`, + ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${zeroStorage.name}/zero/0`, }), }; @@ -46,7 +46,7 @@ const replicationManager = !$dev capacity: "spot", architecture: "arm64", image: zeroEnv.ZERO_IMAGE_URL, - link: [storage, postgres], + link: [zeroStorage, postgres], health: { command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"], interval: "5 seconds", @@ -123,7 +123,7 @@ const replicationManager = !$dev export const zero = new sst.aws.Service("Zero", { cluster, image: zeroEnv.ZERO_IMAGE_URL, - link: [storage, postgres], + link: [zeroStorage, postgres], architecture: "arm64", cpu: "0.5 vCPU", memory: "1 GB", diff --git a/packages/www/src/assets/service-worker.js b/packages/www/src/assets/service-worker.js new file mode 100644 index 00000000..275e8142 --- /dev/null +++ b/packages/www/src/assets/service-worker.js @@ -0,0 +1,54 @@ +const CACHE_NAME = 'image-cache-v1'; +const AUTH_TOKEN = 'Bearer YOUR_DYNAMIC_AUTH_TOKEN'; // Replace at runtime if needed + +self.addEventListener('install', (event) => { + self.skipWaiting(); // Activate immediately +}); + +self.addEventListener('activate', (event) => { + clients.claim(); // Take control of all clients +}); + +self.addEventListener('fetch', (event) => { + const req = event.request; + + // Only intercept image requests + if (req.destination !== 'image') return; + + // Only intercept our image requests + const url = new URL(req.url); + if (import.meta.env.VITE_CDN_URL !== url.origin) return; + + event.respondWith(handleImageRequest(req)); +}); + +async function handleImageRequest(request) { + const cache = await caches.open(CACHE_NAME); + + const cachedResponse = await cache.match(request); + if (cachedResponse) return cachedResponse; + + // Clone and modify the request with Authorization header + const modifiedRequest = new Request(request.url, { + method: request.method, + headers: { + ...Object.fromEntries(request.headers.entries()), + Authorization: AUTH_TOKEN, + }, + cache: 'no-store', + mode: 'same-origin', + credentials: 'same-origin', + }); + + try { + const response = await fetch(modifiedRequest); + + if (response.ok) { + await cache.put(request, response.clone()); + } + + return response; + } catch (err) { + return new Response('Image load failed', { status: 503 }); + } +} diff --git a/packages/www/src/index.tsx b/packages/www/src/index.tsx index 608b9165..03a16245 100644 --- a/packages/www/src/index.tsx +++ b/packages/www/src/index.tsx @@ -17,6 +17,19 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { ); } +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker + .register('/src/assets/service-worker.js') + .then((reg) => { + console.log('[SW] Registered:', reg.scope); + }) + .catch((err) => { + console.error('[SW] Registration failed:', err); + }); + }); +} + render( () => ( diff --git a/packages/www/src/sst-env.d.ts b/packages/www/src/sst-env.d.ts index 884d4914..83e132c5 100644 --- a/packages/www/src/sst-env.d.ts +++ b/packages/www/src/sst-env.d.ts @@ -4,6 +4,7 @@ /// interface ImportMetaEnv { readonly VITE_API_URL: string + readonly VITE_CDN_URL: string readonly VITE_STAGE: string readonly VITE_AUTH_URL: string readonly VITE_ZERO_URL: string diff --git a/sst-env.d.ts b/sst-env.d.ts index 40b884bc..65e99aa6 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -24,6 +24,10 @@ declare module "sst" { "name": string "type": "sst.aws.Bus" } + "CDNRouter": { + "type": "sst.aws.Router" + "url": string + } "Database": { "clusterArn": string "database": string @@ -124,6 +128,10 @@ declare module "sst" { "type": "sst.aws.Service" "url": string } + "ZeroStorage": { + "name": string + "type": "sst.aws.Bucket" + } } }