UNPKG

insite-ws-transfers

Version:

File transfers over WebSockets for inSite

3 lines (2 loc) 13 kB
import{Readable as L}from"node:stream";var r={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,k=1024*256,v=250;var h=class{constructor(e,s,t,{type:i,collect:n,encoding:o,size:c,metadata:l},p,g){this.ws=e,this.#e=e.isWebSocket?[e]:[e.wss,e],this.kind=s,this.id=t,this.type=i,this.collect=!!n,this.encoding=o,this.size=c,this.metadata=l,this.#s=p,this.#t=g,this.#l=this.#d()}ws;#e;kind;id;type;collect;encoding;size;metadata;#s;#t;#l;data;#i;isAborted=!1;isAbortedBySender=!1;isAbortedByReceiver=!1;isTransfered=!1;confirmResponse;beginAt=null;#a=null;#o=[];#n=!1;duration=null;transferedSize=0;processedSize=0;bytesPerMs=null;progress=0;endAt=null;error=null;#r;#h=0;async#d(){if(this.#i=this.constructor.types[this.type],this.#i)await this.#i.setup.call(this);else throw new Error(`Unknown type of transfer "${String(this.type)}"`);await this.confirm()}whenSetUp(){return this.#l}async confirm(){for(let{begin:e}of this.#t)if(await e?.call(...this.#e,this)===!1)return this.throw("Transfer was rejected by receiver");this.beginAt=this.#a=Date.now(),this.ws.sendMessage(r.confirm,this.id,this.confirmResponse),this.#r=setInterval(()=>{this.#h!==this.progress&&(this.#h=this.progress,this.ws.sendMessage(r.progress,this.id,this.progress))},v)}async handleChunk(e,s=e.length){let t=Date.now();this.bytesPerMs=s/(t-this.#a),this.#a=t,this.transferedSize+=s,this.#o.push([e,s]),this.#n||(this.#n=!0,await this.#f())}async#f(){let[e,s]=this.#o.shift();this.#i.transformChunk&&(e=await this.#i.transformChunk.call(this,e)),this.collect&&await this.#i.collect.call(this,e);for(let{chunk:t,progress:i}of this.#t)await(t??i)?.call(...this.#e,this,e);this.processedSize+=s,this.size&&(this.progress=this.processedSize/this.size),this.#o.length?this.#f():(this.#n=!1,this.isTransfered&&this.#c())}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.#c()}async#c(){this.progress=1,await this.#i.done?.call(this);for(let e of this.#t)e.once&&this.#s.removeListener(this.kind,e),await e.end?.call(...this.#e,this);clearInterval(this.#r),this.#s.delete(this.id),this.ws.sendMessage(r.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,s=!0){this.#s.delete(this.id),clearInterval(this.#r),this.error=new Error(e);for(let t of this.#t)t.once&&this.#s.removeListener(this.kind,t),t.error?.call(...this.#e,this,this.error);s&&this.ws.sendMessage(r.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 S=class{constructor(e,s={}){let{sizeLimit:t=z}=s;this.sizeLimit=t,e.isWebSocketServer?(e.on(`client-message:${r.request}`,(i,...n)=>this.handleRequest(i,...n)),e.on(`client-message:${r.chunk}`,(i,...n)=>this.handleChunk(i,...n)),e.on(`client-message:${r.sent}`,(i,...n)=>this.handleSent(i,...n)),e.on(`client-message:${r.abort}`,(i,...n)=>this.handleAbort(i,...n))):(e.on(`message:${r.request}`,(...i)=>this.handleRequest(e,...i)),e.on(`message:${r.chunk}`,(...i)=>this.handleChunk(e,...i)),e.on(`message:${r.sent}`,(...i)=>this.handleSent(e,...i)),e.on(`message:${r.abort}`,(...i)=>this.handleAbort(e,...i))),Object.assign(e,{onTransfer:(i,n,o)=>this.on(i,n,o),onceTransfer:(i,n,o)=>this.once(i,n,o)})}sizeLimit;#e=new Map;addTransferListener(e,s,t){return t?.once&&(s.once=!0),this.#e.get(e)?.add(s)??this.#e.set(e,new Set([s])),this}on=this.addTransferListener;once(e,s,t){return this.addTransferListener(e,s,{...t,once:!0})}removeTransferListener(e,s){if(s){let t=this.#e.get(e);t&&(t.delete(s),t.size||this.#e.delete(e))}else this.#e.delete(e);return this}off=this.removeTransferListener;#s=new Map;#t={delete:e=>this.#s.delete(e),removeListener:(e,s)=>this.removeTransferListener(e,s)};handleRequest(e,s,t,{type:i,size:n,metadata:o,...c}){let{Transfer:l}=this.constructor;this.#e.has(s)?this.#s.has(t)?e.sendMessage(r.error,t,"Transfer already exists"):i in l.types?n>this.sizeLimit?e.sendMessage(r.error,t,`Transfer size (${n} bytes) exeeds limit of ${this.sizeLimit} bytes`):this.#s.set(t,new l(e,s,t,{type:i,size:n,metadata:o,...c},this.#t,this.#e.get(s))):e.sendMessage(r.error,t,"Unknown type of transfer"):e.sendMessage(r.error,t,`Unknown kind of file "${s}"`)}async handleChunk(e,s,t,i=t.length){await this.#s.get(s)?.handleChunk(t,i)}async handleSent(e,s){await this.#s.get(s)?.handleSent()}handleAbort(e,s){this.#s.get(s)?.abort(!0)}static Transfer=h};var u=class extends L{constructor(e){super(),this.writable=e,this.promise=new Promise((s,t)=>{e.on("close",s),e.on("error",t)}),this.pipe(e)}writable;promise;_read(){}};function M(a){let e=new u(a);return this.streams?.push(e)??(this.streams=[e]),a}var P={setup(){this.encoding||(this.encoding="buffer"),this.encoding==="buffer"&&(this.isBuffer=!0),this.collect&&(this.data=this.isBuffer?Buffer.from(""):""),this.pipeTo=M},transformChunk(a){if(a=this.isBuffer?Buffer.from(a,"base64"):Buffer.from(a,"base64").toString(this.encoding),this.streams)for(let e of this.streams)e.push(a);return a},collect(a){this.isBuffer?this.data=Buffer.concat([this.data,a]):this.data+=a},async done(){if(this.streams){let a=[];for(let e of this.streams)e.push(null),a.push(e.promise);await Promise.all(a)}}},W=class extends h{streams;isBuffer=this.isBuffer||!1;pipeTo=this.pipeTo;static types={stream:P,file:P,...h.types}},w=class extends S{static Transfer=W};import{createReadStream as E,existsSync as $}from"node:fs";import{stat as F}from"node:fs/promises";import{basename as N,extname as q}from"node:path";import{Readable as H}from"node:stream";import j from"mime";import{uid as B}from"@nesvet/n";var d=class{constructor(e,s={}){this.string=e,this.size=this.string.length;let{chunkSize:t=1024*256}=s;this.chunkSize=t}string;size;chunkSize;listener;#e=0;#s=0;isAborted=!1;start(e){this.listener=e,this.#t()}async#t(){if(!this.isAborted){this.#s=Math.min(this.#e+this.chunkSize,this.size);let e=await new Promise(s=>{s(this.string.slice(this.#e,this.#s))});this.isAborted||(await this.listener(e),this.#s<this.size&&(this.#e=this.#s,this.#t()))}}abort(){this.isAborted=!0}};var f=class{constructor(e,s,{data:t,type:i,incomingType:n,collect:o,metadata:c,size:l,chunkSize:p,encoding:g,incomingEncoding:x,onBegin:T,onSenderProgress:C,onProgress:b,onEnd:O,onError:R},A){this.ws=e,this.#e=e.isWebSocket?[e]:[e.wss,e],this.kind=s,this.data=t,this.type=i,this.collect=o??!1,this.metadata=c,this.size=l??null,this.encoding=g,this.chunkSize=p??this.constructor.chunkSize,this.#s=A,T&&(this.#t=T),C&&(this.#l=C),b&&(this.#i=b),O&&(this.#a=O),R&&(this.#o=R),this.#n=this.#c(n,x)}ws;#e;kind;data;type;collect;metadata;size;encoding;chunkSize;#s;#t;#l;#i;#a;#o;#n;id=B();#r;isAborted=!1;isTransfered=!1;confirmResponse;beginAt=null;#h=null;#d=[];#f=!1;duration=null;transferedSize=0;bytesPerMs=null;senderProgress=0;progress=0;endAt=null;error=null;stringStreamer;fileStreamer;async#c(e,s){for(let[t,i,n]of this.constructor.types)if(this.type===t||i(this.data)){this.type=t,this.#r=n;break}if(this.type)await this.#r.setup.call(this),delete this.data;else throw new Error("Unknown type of transfer");this.ws.sendMessage(r.request,this.kind,this.id,{type:e??this.type,collect:this.collect,encoding:s??this.encoding,size:this.size,metadata:this.metadata})}whenSetUp(){return this.#n}async handleConfirm(e){this.confirmResponse=e,this.beginAt=this.#h=Date.now(),await this.#t?.call(...this.#e,this),await this.#r.confirm.call(this)}handleChunk=e=>{this.#d.push(e),this.#f||(this.#f=!0,this.#S())};async#S(){let e=this.#d.shift(),s=e.length;this.#r.transformChunk&&(e=await this.#r.transformChunk.call(this,e)),this.ws.sendMessage(r.chunk,this.id,e,s);let t=Date.now();this.bytesPerMs=s/(t-this.#h),this.#h=t,this.transferedSize+=s,this.size&&(this.senderProgress=this.transferedSize/this.size),await this.#l?.call(...this.#e,this),this.#d.length?this.#S():(this.#f=!1,this.senderProgress===1&&this.sent())}sent=()=>this.ws.sendMessage(r.sent,this.id);handleProgress(e){this.progress=e,this.#i?.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.#s.delete(this.id),this.progress!==1&&(this.progress=1,await this.#i?.call(...this.#e,this)),this.#a?.call(...this.#e,this)}throw(e){this.#s.delete(this.id),this.error=new Error(e),this.#o?.call(...this.#e,this,this.error)}abort(){return this.isAborted=!0,this.ws.sendMessage(r.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 d(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,s]=this.data.split(/^data:|;base64,|,/).slice(1);this.stringStreamer=new d(s,{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 d(this.data,{chunkSize:this.chunkSize}),this.size=this.stringStreamer.size,this.encoding||(this.encoding="utf8")},confirm(){this.stringStreamer.start(this.handleChunk)}}]];static chunkSize=k};var m=class{constructor(e){e.isWebSocketServer?(e.on(`client-message:${r.confirm}`,(s,...t)=>this.handleConfirm(s,...t)),e.on(`client-message:${r.progress}`,(s,...t)=>this.handleProgress(s,...t)),e.on(`client-message:${r.completed}`,(s,...t)=>this.handleCompleted(s,...t)),e.on(`client-message:${r.error}`,(s,...t)=>this.handleError(s,...t)),Object.assign(e,{transfer:(s,t,i)=>this.transfer(s,t,i)})):(e.on(`message:${r.confirm}`,(...s)=>this.handleConfirm(e,...s)),e.on(`message:${r.progress}`,(...s)=>this.handleProgress(e,...s)),e.on(`message:${r.completed}`,(...s)=>this.handleCompleted(e,...s)),e.on(`message:${r.error}`,(...s)=>this.handleError(e,...s)),Object.assign(e,{transfer:(s,t)=>this.transfer(e,s,t)}))}#e=new Map;#s={delete:e=>this.#e.delete(e)};transfer(e,s,t){let{Transfer:i}=this.constructor,n=new i(e,s,t,this.#s);return this.#e.set(n.id,n),n}async handleConfirm(e,s,t){await this.#e.get(s)?.handleConfirm(t)}handleProgress(e,s,t){this.#e.get(s)?.handleProgress(t)}async handleCompleted(e,s){await this.#e.get(s)?.handleCompleted()}handleError(e,s,t){this.#e.get(s)?.throw(t)}static Transfer=f};var y=class extends f{stream=this.stream;isBuffer=this.isBuffer||!1;static types=[["stream",e=>e instanceof H,{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"&&$(e),{async setup(){let e=this.data,s=N(e),{size:t,mtimeMs:i}=await F(e),n={name:s,type:j.getType(q(s).slice(1)),size:t,modifiedAt:i};this.stream=E(e),this.size=t,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")}}],...f.types]},I=class extends m{static Transfer=y};export{W as IncomingTransfer,w as IncomingTransport,W as NodeIncomingTransfer,y as OutgoingTransfer,I as OutgoingTransport}; //# sourceMappingURL=index.js.map