/// /** * 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((e) => { // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // console.log('onFileDownloadRequest hook has been triggered ~~~'); // e.next() // 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 e.next(); } if (!baseUrl) { console.error('BUNNY_ZONE_URL was missing from env'); return e.next(); } /** * Generates a BunnyCDN-style signed URL using directory tokens. * * We sign URLs to make hotlinking difficult * @see https://support.bunny.net/hc/en-us/articles/360016055099-How-to-sign-URLs-for-BunnyCDN-Token-Authentication * @see https://github.com/pocketbase/pocketbase/discussions/5983#discussioncomment-11426659 // HMAC in pocketbase * @see https://github.com/pocketbase/pocketbase/discussions/6772 // base64 encode the hex */ function signUrl(securityKey, baseUrl, path, expires) { if (!path.startsWith('/')) path = '/' + path; if (baseUrl.endsWith('/')) throw new Error(`baseUrl must not end with a slash. got baseUrl=${baseUrl}`); const hashableBase = securityKey + path + expires; // Generate and encode the token const tokenH = $security.sha256(hashableBase); const token = Buffer.from(tokenH, "hex") .toString("base64") .replace(/\n/g, "") // Remove newlines .replace(/\+/g, "-") // Replace + with - .replace(/\//g, "_") // Replace / with _ .replace(/=/g, ""); // Remove = // Generate the URL const signedUrl = baseUrl + path + '?token=' + token + '&expires=' + expires; return signedUrl; } console.log(`record: ${JSON.stringify(e.record)}`) console.log(`collection: ${JSON.stringify(e.collection)}`) console.log(`app: ${JSON.stringify(e.app)}`) console.log(`fileField: ${JSON.stringify(e.fileField)}`) console.log(`servedPath: ${JSON.stringify(e.servedPath)}`) console.log(`servedName: ${JSON.stringify(e.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 = e.servedPath; const expires = Math.round(Date.now() / 1000) + 3600; const signedUrl = signUrl(securityKey, baseUrl, path, expires); 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. e.redirect(302, signedUrl); e.next() })