/// /** * onFileDownloadRequest hook is triggered before each API File download request. Could be used to validate or modify the file response before returning it to the client. * @see https://pocketbase.io/docs/js-event-hooks/#onfiledownloadrequest * * We use this to return a 302 to the CDN asset instead of having the asset proxied via Pocketbase * @see https://github.com/pocketbase/pocketbase/discussions/5995 * */ onFileDownloadRequest((event) => { // console.log('event', JSON.stringify(event)) // e.app // e.collection // e.record // e.fileField // e.servedPath // e.servedName // and all RequestEvent fields... const securityKey = process.env?.BUNNY_TOKEN_KEY; const baseUrl = process.env?.BUNNY_ZONE_URL; // console.log(`securityKey=${securityKey}, baseUrl=${baseUrl}`) if (!securityKey) { console.error('BUNNY_TOKEN_KEY was missing from env'); return event.next(); } if (!baseUrl) { console.error('BUNNY_ZONE_URL was missing from env'); return event.next(); } /** * Generate a signed BunnyCDN URL. * @param {string} securityKey - Your BunnyCDN security token * @param {string} baseUrl - The base URL (protocol + host) * @param {string} path - Path to the file (starting with /) * @param {string} rawQuery - Raw query string, e.g., "width=500&quality=5" * @param {number} expires - Unix timestamp for expiration */ function signUrlCool(securityKey, baseUrl, path, rawQuery = "", expires) { if (!path.startsWith('/')) path = '/' + path; if (baseUrl.endsWith('/')) throw new Error(`baseUrl must not end with a slash. got baseUrl=${baseUrl}`); // Build parameter string (sort keys alphabetically) let parameterData = ""; if (rawQuery) { const params = rawQuery .split("&") .map(p => p.split("=")) .filter(([key]) => key && key !== "token" && key !== "expires") .sort(([a], [b]) => a.localeCompare(b)); if (params.length) { parameterData = params.map(([k, v]) => `${k}=${v}`).join("&"); } } // Build hashable base const hashableBase = securityKey + path + expires + parameterData; // console.log(`hashableBase`, hashableBase) // Compute token using your $security.sha256 workflow const tokenH = $security.sha256(hashableBase); const token = Buffer.from(tokenH, "hex") .toString("base64") .replace(/\n/g, "") .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=/g, ""); // Build final signed URL let tokenUrl = baseUrl + path + "?token=" + token; if (parameterData) tokenUrl += "&" + parameterData; tokenUrl += "&expires=" + expires; return tokenUrl; } const rawQuery = event.requestEvent.request.url.rawQuery; // console.log(`record: ${JSON.stringify(event.record)}`) // // console.log(`collection: ${JSON.stringify(event.collection)}`) // console.log(`app: ${JSON.stringify(event.app)}`) // console.log(`fileField: ${JSON.stringify(event.fileField)}`) // console.log(`servedPath: ${JSON.stringify(event.servedPath)}`) // console.log(`servedName: ${JSON.stringify(event.servedName)}`) // Our job here is to take the servedPath, and sign it using bunnycdn method // Then serve a 302 redirect instead of serving the file proxied thru PB const path = event.servedPath; const expires = Math.round(Date.now() / 1000) + 7 * 24 * 3600; // 7 days const signedUrl = signUrlCool(securityKey, baseUrl, path, rawQuery, expires); // console.log(`rawQUery`, rawQuery, 'path', path); // console.log(`signedUrl=${signedUrl}`); // This redirect is a tricky thing. We do this to avoid proxying file requests via our pocketbase origin server. // The idea is to reduce load. // HOWEVER, this redirect slows down image loading because it now takes 2 requests per image. event.redirect(302, signedUrl); event.next() })