insite-ws-transfers
Version:
inSite file transfers over WebSockets
3 lines (2 loc) • 15.5 kB
JavaScript
import{Readable as D}from"node:stream";var o={request:"~t-request",confirm:"~t-confirm",chunk:"~t-c",progress:"~t-p",sent:"~t-sent",completed:"~t-completed",abort:"~t-abort",error:"~t-error"},z=10*1024*1024*1024,A=1024*256,E=250;var S=class{constructor(e,t,r,{type:s,collect:n,encoding:a,size:f,metadata:h},x,W){this.ws=e,this.#e=e.isWebSocket?[e]:[e.wss,e],this.kind=t,this.id=r,this.type=s,this.collect=!!n,this.encoding=a,this.size=f,this.metadata=h,this.#t=x,this.#r=W,this.#c=this.#l()}ws;#e;kind;id;type;collect;encoding;size;metadata;#t;#r;#c;data;#s;isAborted=!1;isAbortedBySender=!1;isAbortedByReceiver=!1;isTransfered=!1;confirmResponse;beginAt=null;#o=null;#a=[];#n=!1;duration=null;transferedSize=0;processedSize=0;bytesPerMs=null;progress=0;endAt=null;error=null;#i;#f=0;async#l(){if(this.#s=this.constructor.types[this.type],this.#s)await this.#s.setup.call(this);else throw new Error(`Unknown type of transfer "${String(this.type)}"`);await this.confirm()}whenSetUp(){return this.#c}async confirm(){for(let{begin:e}of this.#r)if(await e?.call(...this.#e,this)===!1)return this.throw("Transfer was rejected by receiver");this.beginAt=this.#o=Date.now(),this.ws.sendMessage(o.confirm,this.id,this.confirmResponse),this.#i=setInterval(()=>{this.#f!==this.progress&&(this.#f=this.progress,this.ws.sendMessage(o.progress,this.id,this.progress))},E)}async handleChunk(e,t=e.length){let r=Date.now();this.bytesPerMs=t/(r-this.#o),this.#o=r,this.transferedSize+=t,this.#a.push([e,t]),this.#n||(this.#n=!0,await this.#h())}async#h(){let[e,t]=this.#a.shift();this.#s.transformChunk&&(e=await this.#s.transformChunk.call(this,e)),this.collect&&await this.#s.collect.call(this,e);for(let{chunk:r,progress:s}of this.#r)await(r??s)?.call(...this.#e,this,e);this.processedSize+=t,this.size&&(this.progress=this.processedSize/this.size),this.#a.length?this.#h():(this.#n=!1,this.isTransfered&&this.#u())}async handleSent(){this.isTransfered=!0,this.size||(this.size=this.transferedSize),this.endAt=Date.now(),this.duration=this.endAt-this.beginAt,this.bytesPerMs=this.size/this.duration,this.#n||await this.#u()}async#u(){this.progress=1,await this.#s.done?.call(this);for(let e of this.#r)e.once&&this.#t.removeListener(this.kind,e),await e.end?.call(...this.#e,this);clearInterval(this.#i),this.#t.delete(this.id),this.ws.sendMessage(o.completed,this.id)}abort(e=!1){return this.isAborted=!0,this.isAbortedBySender=e,this.isAbortedByReceiver=!e,this.throw(`Transfer is aborted by ${e?"sender":"receiver"}`,!e)}throw(e,t=!0){this.#t.delete(this.id),clearInterval(this.#i),this.error=new Error(e);for(let r of this.#r)r.once&&this.#t.removeListener(this.kind,r),r.error?.call(...this.#e,this,this.error);t&&this.ws.sendMessage(o.error,this.id,e)}serialize(){return{id:this.id,kind:this.kind,type:this.type,collect:this.collect,encoding:this.encoding,size:this.size,metadata:this.metadata,isAborted:this.isAborted,isAbortedBySender:this.isAbortedBySender,isAbortedByReceiver:this.isAbortedByReceiver,isTransfered:this.isTransfered,confirmResponse:this.confirmResponse,beginAt:this.beginAt,duration:this.duration,transferedSize:this.transferedSize,processedSize:this.processedSize,bytesPerMs:this.bytesPerMs,progress:this.progress,endAt:this.endAt,error:this.error}}static types={object:{setup(){this.data=""},collect(e){this.data+=e},done(){this.data=JSON.parse(this.data)}},datauri:{setup(){this.collect&&(this.data="")},collect(e){this.data+=e},done(){this.collect&&(this.data=`data:${this.metadata.type};base64,${this.data}`)}},string:{setup(){this.collect&&(this.data="")},collect(e){this.data+=e}}}};var k=class{constructor(e,t={}){let{sizeLimit:r=z}=t;this.sizeLimit=r,e.isWebSocketServer?(e.on(`client-message:${o.request}`,(s,...n)=>this.handleRequest(s,...n)),e.on(`client-message:${o.chunk}`,(s,...n)=>this.handleChunk(s,...n)),e.on(`client-message:${o.sent}`,(s,...n)=>this.handleSent(s,...n)),e.on(`client-message:${o.abort}`,(s,...n)=>this.handleAbort(s,...n))):(e.on(`message:${o.request}`,(...s)=>this.handleRequest(e,...s)),e.on(`message:${o.chunk}`,(...s)=>this.handleChunk(e,...s)),e.on(`message:${o.sent}`,(...s)=>this.handleSent(e,...s)),e.on(`message:${o.abort}`,(...s)=>this.handleAbort(e,...s))),Object.assign(e,{onTransfer:(s,n,a)=>this.on(s,n,a),onceTransfer:(s,n,a)=>this.once(s,n,a)})}sizeLimit;#e=new Map;addTransferListener(e,t,r){return r?.once&&(t.once=!0),this.#e.get(e)?.add(t)??this.#e.set(e,new Set([t])),this}on=this.addTransferListener;once(e,t,r){return this.addTransferListener(e,t,{...r,once:!0})}removeTransferListener(e,t){if(t){let r=this.#e.get(e);r&&(r.delete(t),r.size||this.#e.delete(e))}else this.#e.delete(e);return this}off=this.removeTransferListener;#t=new Map;#r={delete:e=>this.#t.delete(e),removeListener:(e,t)=>this.removeTransferListener(e,t)};handleRequest(e,t,r,{type:s,size:n,metadata:a,...f}){let{Transfer:h}=this.constructor;this.#e.has(t)?this.#t.has(r)?e.sendMessage(o.error,r,"Transfer already exists"):s in h.types?n>this.sizeLimit?e.sendMessage(o.error,r,`Transfer size (${n} bytes) exeeds limit of ${this.sizeLimit} bytes`):this.#t.set(r,new h(e,t,r,{type:s,size:n,metadata:a,...f},this.#r,this.#e.get(t))):e.sendMessage(o.error,r,"Unknown type of transfer"):e.sendMessage(o.error,r,`Unknown kind of file "${t}"`)}async handleChunk(e,t,r,s=r.length){await this.#t.get(t)?.handleChunk(r,s)}async handleSent(e,t){await this.#t.get(t)?.handleSent()}handleAbort(e,t){this.#t.get(t)?.abort(!0)}static Transfer=S};var R=class extends D{constructor(e){super(),this.writable=e,this.promise=new Promise((t,r)=>{e.on("close",t),e.on("error",r)}),this.pipe(e)}writable;promise;_read(){}};function q(i){let e=new R(i);return this.streams?.push(e)??(this.streams=[e]),i}var $={setup(){this.encoding||(this.encoding="buffer"),this.encoding==="buffer"&&(this.isBuffer=!0),this.collect&&(this.data=this.isBuffer?Buffer.from(""):""),this.pipeTo=q},transformChunk(i){if(i=this.isBuffer?Buffer.from(i,"base64"):Buffer.from(i,"base64").toString(this.encoding),this.streams)for(let e of this.streams)e.push(i);return i},collect(i){this.isBuffer?this.data=Buffer.concat([this.data,i]):this.data+=i},async done(){if(this.streams){let i=[];for(let e of this.streams)e.push(null),i.push(e.promise);await Promise.all(i)}}},P=class extends S{streams;isBuffer=this.isBuffer||!1;pipeTo=this.pipeTo;static types={stream:$,file:$,...S.types}},M=class extends k{static Transfer=P};import{createReadStream as H,existsSync as J}from"node:fs";import{stat as U}from"node:fs/promises";import{basename as V,extname as K}from"node:path";import{Readable as Q}from"node:stream";import G from"mime";function p(){}var w=new WeakMap,b=class i extends Promise{constructor(e,t){let r,s;if(super((n,a)=>{r=n,s=a}),w.set(this,{resolve:r,reject:s}),t?.signal)if(t.signal.aborted)this.reject(new DOMException("Aborted","AbortError"));else{let n=()=>this.reject(new DOMException("Aborted","AbortError"));t.signal.addEventListener("abort",n),this.finally(()=>t.signal?.removeEventListener("abort",n))}if(typeof t?.timeout=="number"&&Number.isFinite(t.timeout)){let n=setTimeout(()=>this.reject(new DOMException("Timeout exceeded","TimeoutError")),t.timeout);this.finally(()=>clearTimeout(n))}e?.(this.resolve,this.reject)}isPending=!0;isFulfilled=!1;isRejected=!1;state="pending";result;resolve=e=>{if(this.isPending){this.isPending=!1,this.isFulfilled=!0,this.state="fulfilled",this.result=e;let t=w.get(this);t&&(t.resolve.call(this,e),w.delete(this))}};reject=e=>{if(this.isPending){this.isPending=!1,this.isRejected=!0,this.state="rejected",this.result=e;let t=w.get(this);t&&(t.reject.call(this,e),w.delete(this))}};static resolved(e){return new i(t=>t(e))}static rejected(e){return new i((t,r)=>r(e))}};function _(i,e=0,t={}){let{leading:r=!1,trailing:s=!0}=t,{maxWait:n=1/0}=t,a,f,h,x,W,m,c,g,C=async()=>{let d=Date.now();d>=h?(a=void 0,h=void 0,clearTimeout(f),f=void 0,typeof c=="function"&&await c(),l.resolve(g),m=r):(clearTimeout(a),a=setTimeout(C,h-d))},T=Number.isFinite(n)?()=>{c===u&&(c=!1),u(),f=setTimeout(T,n)}:null;async function u(){try{return g=await i.apply(x,W),g}catch(d){throw l.reject(d),d}}r&&typeof r=="boolean"&&(r=u),m=r,s&&typeof s=="boolean"&&(s=u),c=s;let l=Object.assign(function(...d){return W=d,x=this,h=Date.now()+e,c=s,l.promise.isPending||(l.promise=new b((F,N)=>{l.resolve=F,l.reject=N,typeof m=="function"&&(m(),m=!1,c===u&&(c=!1)),a||(a=setTimeout(C,e),T&&(f=setTimeout(T,n)))})),l.promise},{callback:i,promise:b.resolved(void 0),resolve:p,reject:p,run:u,clear:async d=>{clearTimeout(a),a=void 0,clearTimeout(f),f=void 0,l.resolve(d?await u():g)}});return l}_.noop=Object.assign(()=>{},{callback:p,promise:b.resolved(void 0),resolve:p,reject:p,run:p,clear:p});function j(i,e,t,r,s=!0){return Array.isArray(i)&&(i=i.length),typeof r!="string"&&(s=r??!0,r=t),(s?`${i}\xA0`:"")+j.raw(i,e,t,r)}j.raw=(i,e,t,r=t)=>{i=Math.abs(i);let s=i%10,n=i%100;return s===1&&n!==11?e:s>=2&&s<=4&&!(n>=12&&n<=15)?t:r};function L(i="",e=""){return`${i}${Date.now().toString(36)}${Math.round(Math.random()*Number.MAX_SAFE_INTEGER).toString(36)}${e}`}var O=class{constructor(e,t={}){this.string=e,this.size=this.string.length;let{chunkSize:r=1024*256}=t;this.chunkSize=r}string;size;chunkSize;listener;#e=0;#t=0;isAborted=!1;start(e){this.listener=e,this.#r()}async#r(){if(!this.isAborted){this.#t=Math.min(this.#e+this.chunkSize,this.size);let e=await new Promise(t=>{t(this.string.slice(this.#e,this.#t))});this.isAborted||(await this.listener(e),this.#t<this.size&&(this.#e=this.#t,this.#r()))}}abort(){this.isAborted=!0}};var y=class{constructor(e,t,{data:r,type:s,incomingType:n,collect:a,metadata:f,size:h,chunkSize:x,encoding:W,incomingEncoding:m,onBegin:c,onSenderProgress:g,onProgress:C,onEnd:T,onError:u},l){this.ws=e,this.#e=e.isWebSocket?[e]:[e.wss,e],this.kind=t,this.data=r,this.type=s,this.collect=a??!1,this.metadata=f,this.size=h??null,this.encoding=W,this.chunkSize=x??this.constructor.chunkSize,this.#t=l,c&&(this.#r=c),g&&(this.#c=g),C&&(this.#s=C),T&&(this.#o=T),u&&(this.#a=u),this.#n=this.#u(n,m)}ws;#e;kind;data;type;collect;metadata;size;encoding;chunkSize;#t;#r;#c;#s;#o;#a;#n;id=L();#i;isAborted=!1;isTransfered=!1;confirmResponse;beginAt=null;#f=null;#l=[];#h=!1;duration=null;transferedSize=0;bytesPerMs=null;senderProgress=0;progress=0;endAt=null;error=null;stringStreamer;fileStreamer;async#u(e,t){for(let[r,s,n]of this.constructor.types)if(this.type===r||s(this.data)){this.type=r,this.#i=n;break}if(this.type)await this.#i.setup.call(this),delete this.data;else throw new Error("Unknown type of transfer");this.ws.sendMessage(o.request,this.kind,this.id,{type:e??this.type,collect:this.collect,encoding:t??this.encoding,size:this.size,metadata:this.metadata})}whenSetUp(){return this.#n}async handleConfirm(e){this.confirmResponse=e,this.beginAt=this.#f=Date.now(),await this.#r?.call(...this.#e,this),await this.#i.confirm.call(this)}handleChunk=e=>{this.#l.push(e),this.#h||(this.#h=!0,this.#d())};async#d(){let e=this.#l.shift(),t=e.length;this.#i.transformChunk&&(e=await this.#i.transformChunk.call(this,e)),this.ws.sendMessage(o.chunk,this.id,e,t);let r=Date.now();this.bytesPerMs=t/(r-this.#f),this.#f=r,this.transferedSize+=t,this.size&&(this.senderProgress=this.transferedSize/this.size),await this.#c?.call(...this.#e,this),this.#l.length?this.#d():(this.#h=!1,this.senderProgress===1&&this.sent())}sent=()=>this.ws.sendMessage(o.sent,this.id);handleProgress(e){this.progress=e,this.#s?.call(...this.#e,this)}async handleCompleted(){this.isTransfered=!0,this.size||(this.size=this.transferedSize,this.senderProgress=1),this.endAt=Date.now(),this.duration=this.endAt-this.beginAt,this.bytesPerMs=this.size/this.duration,this.#t.delete(this.id),this.progress!==1&&(this.progress=1,await this.#s?.call(...this.#e,this)),this.#o?.call(...this.#e,this)}throw(e){this.#t.delete(this.id),this.error=new Error(e),this.#a?.call(...this.#e,this,this.error)}abort(){return this.isAborted=!0,this.ws.sendMessage(o.abort,this.id),this.throw("Transfer is aborted by sender")}serialize(){return{id:this.id,kind:this.kind,type:this.type,collect:this.collect,encoding:this.encoding,size:this.size,metadata:this.metadata,isAborted:this.isAborted,isTransfered:this.isTransfered,confirmResponse:this.confirmResponse,beginAt:this.beginAt,duration:this.duration,transferedSize:this.transferedSize,bytesPerMs:this.bytesPerMs,senderProgress:this.senderProgress,progress:this.progress,endAt:this.endAt,error:this.error}}static types=[["object",e=>typeof e=="object",{setup(){this.stringStreamer=new O(JSON.stringify(this.data),{chunkSize:this.chunkSize}),this.collect=!0,this.size=this.stringStreamer.size,this.encoding="utf8"},confirm(){this.stringStreamer.start(this.handleChunk)}}],["datauri",e=>typeof e=="string"&&/^data:[\w-.]+\/[\w-.+,]+(?:;base64)?,/.test(e),{setup(){let[e,t]=this.data.split(/^data:|;base64,|,/).slice(1);this.stringStreamer=new O(t,{chunkSize:this.chunkSize}),this.size=this.stringStreamer.size,this.encoding="base64",this.metadata={...this.metadata,type:e}},confirm(){this.stringStreamer.start(this.handleChunk)}}],["string",e=>typeof e=="string",{setup(){this.stringStreamer=new O(this.data,{chunkSize:this.chunkSize}),this.size=this.stringStreamer.size,this.encoding||(this.encoding="utf8")},confirm(){this.stringStreamer.start(this.handleChunk)}}]];static chunkSize=A};var v=class{constructor(e){e.isWebSocketServer?(e.on(`client-message:${o.confirm}`,(t,...r)=>this.handleConfirm(t,...r)),e.on(`client-message:${o.progress}`,(t,...r)=>this.handleProgress(t,...r)),e.on(`client-message:${o.completed}`,(t,...r)=>this.handleCompleted(t,...r)),e.on(`client-message:${o.error}`,(t,...r)=>this.handleError(t,...r)),Object.assign(e,{transfer:(t,r,s)=>this.transfer(t,r,s)})):(e.on(`message:${o.confirm}`,(...t)=>this.handleConfirm(e,...t)),e.on(`message:${o.progress}`,(...t)=>this.handleProgress(e,...t)),e.on(`message:${o.completed}`,(...t)=>this.handleCompleted(e,...t)),e.on(`message:${o.error}`,(...t)=>this.handleError(e,...t)),Object.assign(e,{transfer:(t,r)=>this.transfer(e,t,r)}))}#e=new Map;#t={delete:e=>this.#e.delete(e)};transfer(e,t,r){let{Transfer:s}=this.constructor,n=new s(e,t,r,this.#t);return this.#e.set(n.id,n),n}async handleConfirm(e,t,r){await this.#e.get(t)?.handleConfirm(r)}handleProgress(e,t,r){this.#e.get(t)?.handleProgress(r)}async handleCompleted(e,t){await this.#e.get(t)?.handleCompleted()}handleError(e,t,r){this.#e.get(t)?.throw(r)}static Transfer=y};var I=class extends y{stream=this.stream;isBuffer=this.isBuffer||!1;static types=[["stream",e=>e instanceof Q,{setup(){this.stream=this.data,this.encoding||(this.encoding="buffer"),this.encoding==="buffer"&&(this.isBuffer=!0)},confirm(){this.stream.on("data",this.handleChunk),this.size||this.stream.on("end",this.sent)},transformChunk(e){return(this.isBuffer?e:Buffer.from(e,this.encoding)).toString("base64")}}],["file",e=>typeof e=="string"&&J(e),{async setup(){let e=this.data,t=V(e),{size:r,mtimeMs:s}=await U(e),n={name:t,type:G.getType(K(t).slice(1)),size:r,modifiedAt:s};this.stream=H(e),this.size=r,this.encoding="buffer",this.metadata?Object.assign(this.metadata,n):this.metadata=n},confirm(){this.stream.on("data",this.handleChunk)},transformChunk(e){return e.toString("base64")}}],...y.types]},B=class extends v{static Transfer=I};export{P as IncomingTransfer,M as IncomingTransport,P as NodeIncomingTransfer,I as OutgoingTransfer,B as OutgoingTransport};
//# sourceMappingURL=index.js.map