Advertisement
WalshyDev

B2 Upload with Cloudflare Workers

Sep 10th, 2021
3,420
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. addEventListener('fetch', event => {
  2.   event.respondWith(handleRequest(event.request))
  3. })
  4.  
  5. /////////////////////////
  6. // Configure these
  7. /////////////////////////
  8. const key = 'KEY_ID';
  9. const appKey = 'APPLICATION_KEY';
  10. const bucketId = 'BUCKET_ID';
  11. const bucketName = 'BUCKET_NAME';
  12.  
  13. const apiVer = 'b2api/v2';
  14. const downloadBase = `https://f002.backblazeb2.com/file/${bucketName}`;
  15.  
  16. // 22 hours, it lasts for 24 hours but we expire early to just avoid any auth errors
  17. const detailsExpiration = 79200;
  18.  
  19. const initialAuth = btoa(key + ':' + appKey);
  20.  
  21. async function handleRequest(request) {
  22.   // Only accept a POST
  23.   if (request.method !== 'POST') {
  24.     return new Response('{"error": "Bad request!"}');
  25.   }
  26.  
  27.   // Parse the request to FormData
  28.   const formData = await request.formData();
  29.   // Get the File from the form. Key for the file is 'image' for me
  30.   const image = formData.get('image');
  31.  
  32.   const details = await setup();
  33.   if (!details) {
  34.     return new Response('{"error": "Failed to upload!"}');
  35.   }
  36.  
  37.   const fileName = await upload(details, image);
  38.   if (!fileName) {
  39.     return new Response('{"error": "Failed to upload!"}');
  40.   }
  41.  
  42.   return new Response(`{"message": "Uploaded!", "file": "${fileName}", "b2Url": "${downloadBase}/${fileName}"}`);
  43. }
  44.  
  45. // returns: { "apiUrl", "authToken", "bucketAuth", "uploadUrl" }
  46. async function setup() {
  47.   // We will try and fetch the auth token and upload URL from KV.
  48.   // They are valid for 24 hours so no need to request it every time
  49.   // { "apiUrl", "authToken", "bucketAuth", "uploadUrl" }
  50.   const storedDetails = await KV.get('details', 'json');
  51.  
  52.   if (storedDetails) {
  53.     return storedDetails;
  54.   }
  55.  
  56.   // If we are not authorized then let's do that!
  57.   const details = {};
  58.  
  59.   const authRes = await fetch(`http://api.backblazeb2.com/${apiVer}/b2_authorize_account`, {
  60.     headers: {
  61.       Authorization: 'Basic ' + initialAuth
  62.     }
  63.   });
  64.   const authJson = await authRes.json();
  65.  
  66.   if (!authRes.ok) {
  67.     console.error('Failed to authenticate, got json:', authJson);
  68.     return false;
  69.   }
  70.  
  71.   // Grab the auth token from the responses
  72.   details.apiUrl = authJson.apiUrl;
  73.   details.authToken = authJson.authorizationToken;
  74.  
  75.   // Grab the upload URL
  76.   const uploadRes = await fetch(`${authJson.apiUrl}/${apiVer}/b2_get_upload_url`, {
  77.     method: 'POST',
  78.     headers: {
  79.       Authorization: authJson.authorizationToken
  80.     },
  81.     body: JSON.stringify({
  82.       bucketId
  83.     })
  84.   });
  85.   const uploadJson = await uploadRes.json();
  86.  
  87.   if (!uploadRes.ok) {
  88.     console.error('Failed to get upload URL, got json:', uploadJson);
  89.     return false;
  90.   }
  91.  
  92.   details.bucketAuth = uploadJson.authorizationToken;
  93.   details.uploadUrl = uploadJson.uploadUrl;
  94.  
  95.   // Write the details into KV so we can get them in future calls.
  96.   // Note this can take up to 60 seconds to propagate globally.
  97.   await KV.put('details', JSON.stringify(details), { expirationTtl: detailsExpiration });
  98.  
  99.   return details;
  100. }
  101.  
  102. async function upload(details, file) {
  103.   const extension = file.name.substring(file.name.lastIndexOf('.'));
  104.  
  105.   // I'm gonna use UUIDs for files here but you could use anything
  106.   const uploadedFileName = crypto.randomUUID() + extension;
  107.  
  108.   const hash = await sha1(file);
  109.  
  110.   const res = await fetch(details.uploadUrl, {
  111.     method: 'POST',
  112.     headers: {
  113.       'Authorization': details.bucketAuth,
  114.       'X-Bz-File-Name': uploadedFileName,
  115.       // We have the type and size of the image in File
  116.       'Content-Type': file.type,
  117.       'Content-Length': file.size,
  118.       // SHA-1 of the file
  119.       'X-Bz-Content-Sha1': hash,
  120.     },
  121.     body: file.stream()
  122.   });
  123.  
  124.   if (!res.ok) {
  125.     const json = await res.json();
  126.     console.error('Failed to upload, got json:', json);
  127.     return false;
  128.   }
  129.  
  130.   return uploadedFileName;
  131. }
  132.  
  133. async function sha1(file) {
  134.   const fileData = await file.arrayBuffer();
  135.   const digest = await crypto.subtle.digest('SHA-1', fileData);
  136.   const array = Array.from(new Uint8Array(digest));
  137.   const sha1 =  array.map(b => b.toString(16).padStart(2, '0')).join('')
  138.   return sha1;
  139. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement