pushduck
Version:
The fastest way to add file uploads to any web application. Enterprise security, edge-ready. Works with 16+ frameworks and 5+ storage providers. No heavy AWS SDK required.
1 lines • 4.07 kB
JavaScript
"use client";import{useCallback as e,useRef as t,useState as n}from"react";function r(e){return e<60?`${Math.round(e)}s`:e<3600?`${Math.round(e/60)}m`:`${Math.round(e/3600)}h`}function i(e){let t=[`B/s`,`KB/s`,`MB/s`,`GB/s`],n=e,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r++;return`${n.toFixed(1)} ${t[r]}`}async function a(e,t,n){return new Promise((r,i)=>{let a=new XMLHttpRequest,o=Date.now();a.upload.onprogress=e=>{if(e.lengthComputable&&n){let t=Math.round(e.loaded/e.total*100),r=(Date.now()-o)/1e3,i=e.loaded/r,a=e.total-e.loaded,s=a/i;n(t,i,s)}},a.onload=()=>{a.status>=200&&a.status<300?r():i(Error(`Upload failed with status: ${a.status}`))},a.onerror=()=>i(Error(`Upload failed`)),a.onabort=()=>i(Error(`Upload aborted`)),a.open(`PUT`,t),a.setRequestHeader(`Content-Type`,e.type),a.send(e)})}function o(r,i={}){let[o,s]=n([]),[c,l]=n(!1),[u,d]=n([]),[f,p]=n(0),[m,h]=n(0),[g,_]=n(0),v=t(new Map),y=e(e=>{if(e.length===0){p(0),h(0),_(0);return}let t=e.filter(e=>e.status===`uploading`||e.status===`success`);if(t.length===0){p(0),h(0),_(0);return}let n=t.reduce((e,t)=>e+t.size,0),r=t.reduce((e,t)=>{let n=t.status===`success`?100:t.progress||0;return e+t.size*n/100},0),a=n>0?r/n*100:0,o=t.reduce((e,t)=>e+(t.uploadSpeed||0),0),s=n-r,c=o>0?s/o:0,l=Math.min(100,Math.max(0,a));p(l),h(o),_(c),i.onProgress?.(l)},[i.onProgress]),b=e(()=>{v.current.forEach(e=>e.abort()),v.current.clear(),s([]),d([]),l(!1),p(0),h(0),_(0)},[]),x=e((e,t,n,r)=>{s(i=>{let a=i.map(i=>i.id===e?{...i,progress:t,uploadSpeed:n,eta:r}:i);return y(a),a})},[y]),S=e((e,t,n={})=>{s(r=>{let i=r.map(r=>r.id===e?{...r,status:t,...n}:r);return y(i),i})},[y]),C=e(async e=>{if(e.length){if(i.disabled){console.warn(`Upload disabled for route: ${String(r)}`);return}try{l(!0),d([]);let t=e.map(e=>({name:e.name,size:e.size,type:e.type})),n=e.map((e,t)=>({id:`${Date.now()}-${t}`,name:e.name,size:e.size,type:e.type,status:`pending`,progress:0,file:e}));s(n);let o=i.endpoint||`/api/s3-upload`,c=await fetch(`${o}?route=${String(r)}&action=presign`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({files:t})});if(!c.ok){let e=await c.json(),t=e.error||`Server error: ${c.statusText}`;s(e=>e.map(e=>({...e,status:`error`,error:t}))),i.onError?.(Error(t));return}let u=await c.json();if(!u.success){let e=u.error||`Failed to get presigned URLs`;s(t=>t.map(t=>({...t,status:`error`,error:e}))),i.onError?.(Error(e));return}i.onStart&&await i.onStart(t),i.onProgress?.(0);let f=u.results.map(async(t,r)=>{let o=e[r],s=n[r];if(!t.success){let e=t.error||`Failed to get presigned URL`;return S(s.id,`error`,{error:e}),i.onError?.(Error(e)),null}try{return S(s.id,`uploading`,{uploadStartTime:Date.now()}),await a(o,t.presignedUrl,(e,t,n)=>x(s.id,e,t,n)),S(s.id,`success`,{progress:100,key:t.key}),{key:t.key,file:{name:o.name,size:o.size,type:o.type},metadata:t.metadata}}catch(e){let t=e instanceof Error?e.message:`Upload failed`;return S(s.id,`error`,{error:t}),i.onError?.(e instanceof Error?e:Error(t)),null}}),p=await Promise.all(f),m=p.filter(Boolean);if(m.length>0)try{let e=await fetch(`${o}?route=${String(r)}&action=complete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({completions:m})});if(e.ok){let t=await e.json();t.success&&t.results&&t.results.forEach(e=>{if(e.success&&e.key){let t=m.find(t=>t?.key===e.key);if(t){let r=n.find(e=>e.name===t.file.name&&e.size===t.file.size);r&&S(r.id,`success`,{url:e.url,key:e.key,progress:100})}}})}}catch(e){console.warn(`Failed to notify server of upload completion:`,e)}i.onSuccess&&s(e=>{let t=e.filter(e=>e.status===`success`);return t.length>0&&i.onSuccess?.(t),e})}catch(e){let t=e instanceof Error?e.message:`Upload failed`;d(e=>[...e,t]),s(e=>e.map(e=>e.status===`success`||e.status===`error`?e:{...e,status:`error`,error:t})),i.onError?.(e instanceof Error?e:Error(t))}finally{l(!1),v.current.clear()}}},[r,i,x,S]);return{files:o,uploadFiles:C,reset:b,isUploading:c,errors:u,progress:f,uploadSpeed:m,eta:g}}export{r as formatETA,i as formatUploadSpeed,o as useUploadRoute};