perkoon 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import{EventEmitter as Ie}from"node:events";import Q from"node:crypto";import _e from"node-datachannel";import{EventEmitter as Se}from"node:events";var{PeerConnection:ve}=_e,B=class extends Se{constructor(){super(),this.pc=null,this.channels=new Map,this.remoteDescriptionSet=!1,this.pendingCandidates=[]}initialize(t,e){let s={iceServers:e.map(i=>(Array.isArray(i.urls)?i.urls:[i.urls]).map(n=>i.username&&i.credential?`${n}:${i.username}:${i.credential}`:n).join(",")),maxMessageSize:262144};this.pc=new ve(t,s),this.pc.onLocalCandidate((i,r)=>{this.emit("ice-candidate",{candidate:i,mid:r})}),this.pc.onStateChange(i=>{this.emit("connection-state",i)}),this.pc.onGatheringStateChange(i=>{this.emit("gathering-state",i)}),this.pc.onDataChannel(i=>{let r=this._wrapChannel(i),n=i.getLabel();this.channels.set(n,r),this.emit("data-channel",r,n)})}async createOffer(t){return new Promise(e=>{this.pc.onLocalDescription((n,o)=>{e({sdp:n,type:o})});let s=this.pc.createDataChannel("control",{ordered:!0});this.channels.set("control",this._wrapChannel(s));let i=`transfer-${t}`,r=this.pc.createDataChannel(i,{ordered:!0,maxRetransmits:3});this.channels.set(i,this._wrapChannel(r))})}async handleOffer(t){return new Promise(e=>{this.pc.onLocalDescription((s,i)=>{e({sdp:s,type:i})}),this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()})}handleAnswer(t){this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()}addIceCandidate(t){t.candidate&&(this.remoteDescriptionSet?this.pc.addRemoteCandidate(t.candidate,t.mid||"0"):this.pendingCandidates.push(t))}_flushPendingCandidates(){for(let t of this.pendingCandidates)this.pc.addRemoteCandidate(t.candidate,t.mid||"0");this.pendingCandidates=[]}getChannel(t){return this.channels.get(t)||null}async waitForChannel(t,e=3e4){let s=this.channels.get(t);return s?.readyState==="open"?s:new Promise((i,r)=>{let n=setTimeout(()=>{r(new Error(`Data channel "${t}" open timeout`))},e),o=c=>{c.readyState==="open"?(clearTimeout(n),i(c)):c.addEventListener("open",()=>{clearTimeout(n),i(c)})};if(s)o(s);else{let c=(l,f)=>{f===t&&(this.removeListener("data-channel",c),o(l))};this.on("data-channel",c)}})}async waitForChannelByPrefix(t,e=3e4){for(let[s,i]of this.channels)if(s.startsWith(t))return i.readyState==="open"?{channel:i,label:s}:new Promise((r,n)=>{let o=setTimeout(()=>n(new Error(`Channel "${s}" open timeout`)),e);i.addEventListener("open",()=>{clearTimeout(o),r({channel:i,label:s})})});return new Promise((s,i)=>{let r=setTimeout(()=>{this.removeListener("data-channel",n),i(new Error(`No channel with prefix "${t}" within ${e}ms`))},e),n=(o,c)=>{c.startsWith(t)&&(this.removeListener("data-channel",n),o.readyState==="open"?(clearTimeout(r),s({channel:o,label:c})):o.addEventListener("open",()=>{clearTimeout(r),s({channel:o,label:c})}))};this.on("data-channel",n)})}close(){for(let[,t]of this.channels)try{t.close()}catch{}if(this.channels.clear(),this.pc){try{this.pc.close()}catch{}this.pc=null}}_wrapChannel(t){let e=new Map,s="connecting";try{t.isOpen&&t.isOpen()&&(s="open")}catch{}let i={get readyState(){return s},get bufferedAmount(){try{return t.bufferedAmount()||0}catch{return 0}},send(n){n instanceof ArrayBuffer?t.sendMessageBinary(Buffer.from(n)):n instanceof Uint8Array?t.sendMessageBinary(Buffer.from(n.buffer,n.byteOffset,n.byteLength)):Buffer.isBuffer(n)?t.sendMessageBinary(n):t.sendMessage(String(n))},close(){s="closed";try{t.close()}catch{}},addEventListener(n,o){e.has(n)||e.set(n,new Set),e.get(n).add(o)},removeEventListener(n,o){let c=e.get(n);c&&c.delete(o)},set onmessage(n){i.addEventListener("message",n)},set onopen(n){i.addEventListener("open",n)},set onclose(n){i.addEventListener("close",n)},set onerror(n){i.addEventListener("error",n)}};t.onOpen(()=>{s="open",r("open",{})}),t.onClosed(()=>{s="closed",r("close",{})}),t.onError(n=>{r("error",{error:n})}),t.onMessage(n=>{let o;Buffer.isBuffer(n)?o=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):o=n,r("message",{data:o})});function r(n,o){let c=e.get(n);if(c)for(let l of c)try{l(o)}catch{}}return i}};import{Socket as Pe}from"phoenix";import Te from"ws";import{EventEmitter as be}from"node:events";var z=class extends be{constructor(t){super(),this.serverUrl=t.replace(/\/$/,""),this.socket=null,this.channel=null,this.connected=!1,this.heartbeatInterval=null,this.peers=[]}async connect(t,e,s,i){let r=this.serverUrl.replace(/^https:/,"wss:").replace(/^http:/,"ws:")+"/socket";return new Promise((n,o)=>{this.socket=new Pe(r,{params:{},transport:Te,timeout:15e3,reconnectAfterMs:()=>36e5}),this.socket.onError(c=>{this.connected||o(new Error(`Signaling connection failed: ${c?.message||"unknown error"}`))}),this.socket.connect(),this.channel=this.socket.channel(`p2p:${t}`,{token:e,peer_id:s,role:i}),this.channel.on("message",c=>{this.emit(c.type,c)}),this.channel.onError(()=>{this.connected=!1}),this.channel.onClose(()=>{this.connected=!1,this._stopHeartbeat(),this.emit("disconnected")}),this.channel.join().receive("ok",c=>{this.connected=!0,this.peers=c.peers||[],this._startHeartbeat(),n({peers:this.peers})}).receive("error",c=>{o(new Error(`Channel join failed: ${c.reason||"unknown"}`))}).receive("timeout",()=>{o(new Error("Channel join timed out"))})})}sendOffer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("offer",{to:t,offer:e})}sendAnswer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("answer",{to:t,answer:e})}sendIceCandidate(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("ice_candidate",{to:t,candidate:e})}sendFilesAdded(t,e,s){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("files_added",{batch_id:t,files:e,total_size:s})}disconnect(){if(this._stopHeartbeat(),this.channel){try{this.channel.leave()}catch{}this.channel=null}if(this.socket){try{this.socket.disconnect()}catch{}this.socket=null}this.connected=!1}_startHeartbeat(){this._stopHeartbeat(),this.heartbeatInterval=setInterval(()=>{this.connected&&this.channel&&this.channel.push("heartbeat",{})},1e4)}_stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}};import{open as Ce}from"node:fs/promises";import ke from"node:path";import{lookup as Ee}from"mime-types";async function ee(a){let t=await Ce(a,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${a}`);return{name:ke.basename(a),size:e.size,type:Ee(a)||"application/octet-stream",async readSlice(s,i){let r=i-s,n=Buffer.alloc(r),{bytesRead:o}=await t.read(n,0,r,s);return o===r?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n.buffer.slice(n.byteOffset,n.byteOffset+o)},async close(){await t.close()}}}import{open as Re,mkdir as Le,access as te}from"node:fs/promises";import se from"node:path";function ie(a,t={}){let e=new Map;return{async startStreamingReceive(s,i,r){await Le(a,{recursive:!0});let n=Fe(i.fileName),o=se.join(a,n);t.overwrite||(o=await Ae(o));let c=await Re(o,"w");i.size>0&&await c.truncate(i.size),e.set(s,{fh:c,filePath:o,pieceSize:i.pieceSize,size:i.size,fileName:i.fileName})},async writeChunk(s,i,r){let n=e.get(s);if(!n)return{success:!1,error:"NO_HANDLE"};let o=i*n.pieceSize,c=Buffer.isBuffer(r)?r:Buffer.from(r);try{return await n.fh.write(c,0,c.byteLength,o),{success:!0}}catch(l){return{success:!1,error:l.message}}},async completeStream(s){let i=e.get(s);i&&(await i.fh.datasync(),await i.fh.close(),e.delete(s))},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(s){return e.get(s)?.filePath},async abort(s){let i=e.get(s);if(i){try{await i.fh.close()}catch{}e.delete(s);try{let{unlink:r}=await import("node:fs/promises");await r(i.filePath)}catch{}}}}}async function Ae(a){try{await te(a)}catch{return a}let t=se.extname(a),e=a.slice(0,-t.length||void 0);for(let s=1;s<1e3;s++){let i=`${e}_${s}${t}`;try{await te(i)}catch{return i}}return`${e}_${Date.now()}${t}`}function Fe(a){return a.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function ne(){let a=new Map;return{async startStreamingReceive(t,e,s){a.set(t,{pieceSize:e.pieceSize,totalPieces:Math.ceil(e.size/e.pieceSize),size:e.size,nextPiece:0,buffer:new Map,bytesWritten:0})},async writeChunk(t,e,s){let i=a.get(t);if(!i)return{success:!1,error:"NO_HANDLE"};let r=Buffer.isBuffer(s)?s:Buffer.from(s);for(i.buffer.set(e,r);i.buffer.has(i.nextPiece);){let n=i.buffer.get(i.nextPiece);i.buffer.delete(i.nextPiece);let o=i.nextPiece*i.pieceSize,c=i.size-o,l=c<n.byteLength?n.subarray(0,c):n;process.stdout.write(l),i.bytesWritten+=l.byteLength,i.nextPiece++}return{success:!0}},async completeStream(t){a.delete(t)},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(t){return"stdout"},async abort(t){a.delete(t)}}}var U=class{constructor(){this._listeners=new Map,this._onceListeners=new Map}on(t,e){return this._listeners.has(t)||this._listeners.set(t,new Set),this._listeners.get(t).add(e),()=>this.off(t,e)}once(t,e){return this._onceListeners.has(t)||this._onceListeners.set(t,new Set),this._onceListeners.get(t).add(e),()=>{let s=this._onceListeners.get(t);s&&s.delete(e)}}off(t,e){let s=this._listeners.get(t);s&&s.delete(e);let i=this._onceListeners.get(t);i&&i.delete(e)}removeAllListeners(t){t?(this._listeners.delete(t),this._onceListeners.delete(t)):(this._listeners.clear(),this._onceListeners.clear())}emit(t,e){let s=this._listeners.get(t);s&&s.forEach(r=>{try{r(e)}catch{}});let i=this._onceListeners.get(t);if(i){let r=[...i];this._onceListeners.delete(t),r.forEach(n=>{try{n(e)}catch{}})}}listenerCount(t){let e=this._listeners.get(t)?.size||0,s=this._onceListeners.get(t)?.size||0;return e+s}hasListeners(t){return this.listenerCount(t)>0}eventNames(){return[...new Set([...this._listeners.keys(),...this._onceListeners.keys()])]}waitFor(t,e){return new Promise((s,i)=>{let r,n=o=>{r&&clearTimeout(r),s(o)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),i(new Error(`Timeout waiting for event: ${t}`))},e))})}};function Me(){let a=Be();return{browser:a.browser,version:a.version,platform:ze(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:$e(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:xe(a.browser),recommendedChunkSize:Ne(a.browser),maxFileSize:De(a.browser),maxFileSizeLabel:Ue(a.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:Oe(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function Be(){let a=navigator.userAgent,t="unknown",e="0";if(a.includes("Edg/")){t="edge";let s=a.match(/Edg\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Chrome/")){t="chrome";let s=a.match(/Chrome\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Firefox/")){t="firefox";let s=a.match(/Firefox\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Safari/")&&!a.includes("Chrome")){t="safari";let s=a.match(/Version\/(\d+)/);e=s?s[1]:"0"}return{browser:t,version:parseInt(e,10)}}function ze(){let a=navigator.userAgent;return a.includes("Windows")?"windows":a.includes("Mac")?"macos":a.includes("Linux")?"linux":a.includes("Android")?"android":a.includes("iPhone")||a.includes("iPad")?"ios":"unknown"}function $e(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function Oe(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function xe(a){switch(a){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function Ne(a){switch(a){case"chrome":case"edge":return 256*1024;case"firefox":return 64*1024;case"safari":return 128*1024;default:return 64*1024}}function De(a){switch(a){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return 1/0;case"safari":return 4*1024*1024*1024;default:return 2*1024*1024*1024}}function Ue(a){switch(a){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return"Unlimited";case"safari":return"4GB";default:return"2GB"}}var H=null;function re(){return H||(H=Me()),H}var oe={chrome:1/0,edge:1/0,brave:1/0,opera:1/0,vivaldi:1/0,samsung:1/0,ucbrowser:1/0,firefox:1/0,safari:4294967296,ios:4294967296,unknown:2147483648},ae={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function ce(a){return oe[a]||oe.unknown}function le(a){return ae[a]||ae.unknown}var We={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxPieceRetries:3,maxRetryQueueSize:1e4,maxInFlightMultiplier:10,highWaterMark:16*1024*1024,lowWaterMark:4*1024*1024,maxPiecesPerSecond:5e3,rateLimitWindowMs:1e3,channelRotationThreshold:512*1024*1024,channelRotationEnabled:!1,parallelChannels:1,parallelChannelsEnabled:!1,parallelChannelsThreshold:100*1024*1024,pacingEnabled:!0,pacingThreshold:0,pacingIntervalMs:10,targetBufferedAmount:8*1024*1024,minBufferedAmount:4*1024*1024,maxBufferedAmount:16*1024*1024,maxPiecesPerTick:32,burstPiecesPerTick:64,maxPendingReads:512,maxInFlightPieces:4e3},W=class extends U{constructor(t={}){super(),this.config={...We,...t},this.transfers=new Map,this.rateLimits=new Map,this._onBufferedAmountLow=this._onBufferedAmountLow.bind(this)}initSend(t,e,s){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let i=this._createManifest(e),r=this.config.parallelChannelsEnabled&&e.size>=this.config.parallelChannelsThreshold,n={id:t,type:"send",file:e,manifest:i,channel:s,channels:[s],channelIndex:0,useParallelChannels:r,state:"initialized",nextPieceToSend:0,piecesAckedCount:0,bytesTransferred:0,paused:!1,inFlightPieces:new Map,retryQueue:[],channelBytes:[0],channelThresholds:[this.config.channelRotationThreshold],channelRotationCounts:[0],pendingRotations:new Set,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,idleTimeoutId:null,pieceTimeouts:new Map,pieceRetries:new Map};return this.transfers.set(t,n),{transferId:t,manifest:i}}async startSend(t){let e=this.transfers.get(t);if(!e||e.type!=="send")throw new Error(`Send transfer not found: ${t}`);if(e.state==="transferring")return;e.state="transferring",e.startTime=e.startTime||Date.now();let s=this.config.pacingEnabled&&e.manifest.fileSize>=this.config.pacingThreshold;e.usePacing=s;for(let i of e.channels)i.addEventListener("bufferedamountlow",this._onBufferedAmountLow);e.useParallelChannels&&e.channels.length<this.config.parallelChannels&&this.emit("parallel-channels-needed",{transferId:t,currentCount:e.channels.length,targetCount:this.config.parallelChannels}),this._startIdleTimeout(t),s?this._startPacingLoop(t):await this._sendNextPieces(t)}addChannel(t,e){let s=this.transfers.get(t);if(!s||s.type!=="send")return;let i=s.channels.length;s.channels.push(e),e.addEventListener("bufferedamountlow",this._onBufferedAmountLow),s.channelBytes.push(0),s.channelRotationCounts.push(0),s.channelThresholds||(s.channelThresholds=[this.config.channelRotationThreshold]);let r=this.config.channelRotationThreshold*(1+i*.25);s.channelThresholds.push(r),!s.paused&&s.state==="transferring"&&this._sendNextPieces(t)}handlePieceAck(t,e){let s=this.transfers.get(t);if(!s||s.type!=="send")return;let i=s.inFlightPieces.get(e);if(!i)return;let r=i.channelIndex>=0?i.channelIndex:0;s.inFlightPieces.delete(e),s.lastActivityTime=Date.now(),s.piecesAckedCount++;let n=this._getPieceSize(s.manifest,e);s.bytesTransferred+=n,s.channelBytes[r]!==void 0&&(s.channelBytes[r]+=n),this._checkChannelRotation(t,s,r);let o=this._calculateProgress(s);if(this._checkProgressMilestones(t,s,o),this._emitProgress(t,s),s.piecesAckedCount===s.manifest.totalPieces){this._completeSend(t);return}!s.paused&&!s.usePacing&&this._sendNextPieces(t)}handlePieceAckBatch(t,e){if(Array.isArray(e))for(let s of e)this.handlePieceAck(t,s)}handleWaterLevelAck(t,e,s){let i=this.transfers.get(t);if(!(!i||i.type!=="send")){if(typeof e=="number"&&e>=0)for(let r=0;r<=e;r++)this.handlePieceAck(t,r);if(Array.isArray(s))for(let r of s)this.handlePieceAck(t,r)}}requeuePiece(t,e){let s=this.transfers.get(t);!s||s.type!=="send"||s.state==="completed"||s.state==="completing"||(s.inFlightPieces.delete(e),s.retryQueue.includes(e)||s.retryQueue.push(e),s.lastActivityTime=Date.now(),!s.paused&&!s.usePacing&&this._sendNextPieces(t))}markSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||this._completeSend(t)}_checkChannelRotation(t,e,s){if(!this.config.channelRotationEnabled||e.pendingRotations.has(s))return;let i=e.channelBytes[s]||0,r=e.channelThresholds?.[s]||this.config.channelRotationThreshold;i>=r&&(e.pendingRotations.add(s),this.emit("channel-rotation-needed",{transferId:t,channelIndex:s,bytesTransferred:e.bytesTransferred,channelBytes:i,threshold:r,rotationCount:e.channelRotationCounts[s]||0}))}rotateSingleChannel(t,e,s){let i=this.transfers.get(t);if(!i||i.type!=="send"||e>=i.channels.length)return;let r=i.channels[e],n=(i.channelRotationCounts[e]||0)+1;i.channels[e]=s,e===0&&(i.channel=s),i.channelBytes[e]=0,i.channelRotationCounts[e]=n,i.pendingRotations.delete(e),s.addEventListener("bufferedamountlow",this._onBufferedAmountLow),r&&r.readyState==="open"&&this._drainAndCloseChannel(r,e,n),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}_drainAndCloseChannel(t,e,s){let i=()=>{if(t.readyState==="open"){if(t.bufferedAmount===0){try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}return}setTimeout(i,50)}};i(),setTimeout(()=>{if(t.readyState==="open")try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}},5e3)}rotateChannel(t,e){this.rotateChannels(t,[e])}initReceive(t,e,s={}){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let i=re(),r=e.fileSize,n=ce(i.browser);if(r>n){let c=le(i.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${i.browser}'s ${c} limit. Use a Chromium browser (Chrome, Edge, Brave) for unlimited file sizes.`)}let o={id:t,type:"receive",manifest:e,state:"initialized",nextPieceToRequest:0,piecesReceivedCount:0,receivedWaterLevel:-1,receivedOutOfOrder:new Set,maxOutOfOrderSize:1e4,piecesRequested:new Set,bytesTransferred:0,streamingHandler:s.streamingHandler||null,fileBuffer:null,pieceBuffers:new Map,paused:!1,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,pieceTimeouts:new Map,idleTimeoutId:null,pieceRetries:new Map};return o.streamingHandler||this._initReceiveBuffer(o,e),this.transfers.set(t,o),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(o)}}startReceive(t,e){let s=this.transfers.get(t);if(!s||s.type!=="receive")throw new Error(`Receive transfer not found: ${t}`);s.state="transferring",s.startTime=s.startTime||Date.now(),s.requestPiece=e,this._startIdleTimeout(t),this._requestNextPieces(t)}async handlePieceData(t,e,s){let i=this.transfers.get(t);if(!i||i.type!=="receive")return{success:!1,error:"Transfer not found"};if(i.state==="completing"||i.state==="completed")return{success:!0,duplicate:!0,progress:100};if(e<0||e>=i.manifest.totalPieces)return{success:!1,error:"Invalid piece index"};if(!(s instanceof ArrayBuffer||s instanceof Uint8Array||ArrayBuffer.isView(s)))return{success:!1,error:"Invalid piece data type"};let r=s.byteLength;if(r===0&&!(e===0&&i.manifest.fileSize===0))return{success:!1,error:"Empty piece data"};let n=this._getPieceSize(i.manifest,e);if(r>n)return{success:!1,error:"Invalid piece size"};let c=e===i.manifest.totalPieces-1?1:Math.floor(n*.5);if(this._checkRateLimit(t),this._clearPieceTimeout(t,e),e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e))return{success:!0,duplicate:!0,progress:this._calculateProgress(i)};try{if(i.lastActivityTime=Date.now(),i.streamingHandler){let f=await i.streamingHandler.writeChunk(t,e,new Uint8Array(s));if(f===!1||f&&f.success===!1)return{success:!1,error:f&&f.error||"Failed to write piece to streaming handler",retryable:!0,pieceIndex:e}}else this._storePieceInMemory(i,e,s);if(e===i.receivedWaterLevel+1)for(i.receivedWaterLevel=e;i.receivedOutOfOrder.has(i.receivedWaterLevel+1);)i.receivedOutOfOrder.delete(i.receivedWaterLevel+1),i.receivedWaterLevel++;else{if(!!!i.streamingHandler&&i.receivedOutOfOrder.size>=i.maxOutOfOrderSize)return{success:!1,error:"Out-of-order buffer full - retry later",retryable:!0,pieceIndex:e};i.receivedOutOfOrder.add(e)}i.piecesReceivedCount++,i.piecesRequested.delete(e),i.bytesTransferred+=r;let l=this._calculateProgress(i);return this._checkProgressMilestones(t,i,l),this._emitProgress(t,i),i.piecesReceivedCount===i.manifest.totalPieces?(i.state="completing",this._stopIdleTimeout(t),{success:!0,complete:!0,progress:100}):(i.paused||this._requestNextPieces(t),{success:!0,progress:this._calculateProgress(i),piecesReceived:i.piecesReceivedCount,totalPieces:i.manifest.totalPieces})}catch(l){return{success:!1,error:l.message}}}async completeReceive(t){let e=this.transfers.get(t);if(!e||e.type!=="receive")return{success:!1,error:"Transfer not found"};if(e.piecesReceivedCount!==e.manifest.totalPieces)return{success:!1,error:`Incomplete: ${e.piecesReceivedCount}/${e.manifest.totalPieces}`};e.state="completing",this._stopIdleTimeout(t);try{return e.streamingHandler?(await e.streamingHandler.completeStream(t),{success:!0,streaming:!0,manifest:e.manifest}):{success:!0,blob:this._assembleFile(e),manifest:e.manifest,fileName:e.manifest.fileName}}catch(s){return{success:!1,error:s.message}}}pause(t){let e=this.transfers.get(t);e&&(e.paused=!0,this._stopPacingLoop(t),this._stopIdleTimeout(t),this.emit("paused",{transferId:t}))}resume(t){let e=this.transfers.get(t);!e||!e.paused||(e.paused=!1,this._startIdleTimeout(t),e.type==="send"?e.usePacing?this._startPacingLoop(t):this._sendNextPieces(t):this._requestNextPieces(t),this.emit("resumed",{transferId:t}))}cancel(t){let e=this.transfers.get(t);e&&(this._cleanupTransfer(t),this.emit("cancelled",{transferId:t,type:e.type}))}cleanup(t){this._cleanupTransfer(t)}getStats(t){let e=this.transfers.get(t);if(!e)return null;let s=e.startTime?Date.now()-e.startTime:0,i=s>0?e.bytesTransferred/s*1e3:0;return{transferId:t,type:e.type,state:e.state,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),piecesCompleted:e.type==="send"?e.piecesAckedCount:e.piecesReceivedCount,totalPieces:e.manifest.totalPieces,speed:i,elapsed:s,paused:e.paused}}async _sendNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.retryQueue.length>=this.config.maxRetryQueueSize){this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let s=this.config.maxConcurrentPieces*e.channels.length*this.config.maxInFlightMultiplier;if(e.inFlightPieces.size>=s)return;let i=e.channel,r=e.channels.length===1;if(r){if(i.readyState!=="open"||i.bufferedAmount>this.config.highWaterMark)return}else{let u=!1;for(let S of e.channels)if(S.readyState==="open"&&S.bufferedAmount<=this.config.highWaterMark){u=!0;break}if(!u)return}let o=this.config.maxConcurrentPieces*e.channels.length-e.inFlightPieces.size;if(o<=0)return;let c=[];for(;e.retryQueue.length>0&&c.length<o;){let u=e.retryQueue.shift();e.inFlightPieces.has(u)||c.push(u)}for(;e.nextPieceToSend<e.manifest.totalPieces&&c.length<o;){let u=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(u)||c.push(u)}if(c.length===0)return;let l=Date.now();for(let u of c)e.inFlightPieces.set(u,l);let f=r?4:4*e.channels.length;try{for(let u=0;u<c.length;u+=f){let A=c.slice(u,u+f).map(async v=>{let g=await this._readPiece(e.file,e.manifest,v);return{pieceIndex:v,data:g}}),F=await Promise.all(A);if(r)for(let{pieceIndex:v,data:g}of F){if(i.readyState!=="open"||i.bufferedAmount>this.config.highWaterMark)return;i.send(this._encodePieceMessage(t,v,g)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,v)}else for(let{pieceIndex:v,data:g}of F){let w=null,h=-1;for(let C=0;C<e.channels.length;C++){let m=(e.channelIndex+C)%e.channels.length,_=e.channels[m];if(_.readyState==="open"&&_.bufferedAmount<=this.config.highWaterMark){w=_,h=m,e.channelIndex=(m+1)%e.channels.length;break}}if(!w)return;w.send(this._encodePieceMessage(t,v,g)),e.inFlightPieces.set(v,{sendTime:Date.now(),channelIndex:h}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,v)}}}catch(u){for(let S of c)e.inFlightPieces.delete(S),e.retryQueue.push(S);this.emit("error",{transferId:t,error:`Failed to send pieces: ${u.message}`})}}_completeSend(t){let e=this.transfers.get(t);e&&(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(s=>clearTimeout(s)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),this.emit("complete",{transferId:t,type:"send",bytesTransferred:e.bytesTransferred,duration:Date.now()-e.startTime}))}_startPacingLoop(t){let e=this.transfers.get(t);!e||e.pacingIntervalId||(e.pacingIntervalId=setInterval(()=>{this._pacingTick(t)},this.config.pacingIntervalMs),this._pacingTick(t))}_stopPacingLoop(t){let e=this.transfers.get(t);!e||!e.pacingIntervalId||(clearInterval(e.pacingIntervalId),e.pacingIntervalId=null)}_pacingTick(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.pendingReads===void 0&&(e.pendingReads=0),e.retryQueue.length>=this.config.maxRetryQueueSize){this._stopPacingLoop(t),this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let s=e.inFlightPieces.size;if(s>=this.config.maxInFlightPieces)return;let i=e.channel;if(!i||i.readyState!=="open")return;let r=i.bufferedAmount,n=0;if(r>this.config.maxBufferedAmount)return;r>this.config.targetBufferedAmount?n=32:r>this.config.minBufferedAmount?n=this.config.maxPiecesPerTick:n=this.config.burstPiecesPerTick;let o=this.config.maxPendingReads-e.pendingReads;if(o<=0)return;n=Math.min(n,o);let c=this.config.maxInFlightPieces-s;n=Math.min(n,c);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let u=e.retryQueue.shift();e.inFlightPieces.has(u)||l.push(u)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let u=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(u)||l.push(u)}if(l.length===0){e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.inFlightPieces.size===0&&e.pendingReads===0&&this._stopPacingLoop(t);return}e.pendingReads+=l.length;let f=Date.now();for(let u of l)e.inFlightPieces.set(u,f);this._readAndSendPieces(t,e,i,l)}async _readAndSendPieces(t,e,s,i){try{let r=i.map(o=>this._readPiece(e.file,e.manifest,o).then(c=>({pieceIndex:o,data:c})).catch(c=>({pieceIndex:o,error:c}))),n=await Promise.all(r);for(let o of n){if(o.error){e.inFlightPieces.delete(o.pieceIndex),e.retryQueue.push(o.pieceIndex);continue}let{pieceIndex:c,data:l}=o,f=e.channel;if(!f||f.readyState!=="open"){e.inFlightPieces.delete(c),e.retryQueue.push(c);continue}if(f.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(c),e.retryQueue.unshift(c);continue}try{f.send(this._encodePieceMessage(t,c,l)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,c)}catch{e.inFlightPieces.delete(c),e.retryQueue.unshift(c)}}}catch{for(let n of i)e.inFlightPieces.has(n)&&(e.inFlightPieces.delete(n),e.retryQueue.push(n))}finally{e.pendingReads-=i.length}}_requestNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;let s=this._getNextPiecesToRequest(e);for(let i of s)e.piecesRequested.add(i),e.requestPiece(i),this._startPieceTimeout(t,i)}_getNextPiecesToRequest(t){let e=this.config.maxConcurrentPieces-t.piecesRequested.size;if(e<=0)return[];let s=[];for(;t.nextPieceToRequest<t.manifest.totalPieces&&s.length<e;){let i=t.nextPieceToRequest;t.nextPieceToRequest++,!(i<=t.receivedWaterLevel||t.receivedOutOfOrder.has(i))&&!t.piecesRequested.has(i)&&s.push(i)}return s}_initReceiveBuffer(t,e){if(e.fileSize<=104857600)try{t.fileBuffer=new ArrayBuffer(e.fileSize),t.useChunkedBuffer=!1}catch{t.useChunkedBuffer=!0}else t.useChunkedBuffer=!0}_storePieceInMemory(t,e,s){if(t.useChunkedBuffer)t.pieceBuffers.set(e,new Uint8Array(s));else{let i=e*t.manifest.pieceSize;new Uint8Array(t.fileBuffer,i,s.byteLength).set(new Uint8Array(s))}}_assembleFile(t){let{manifest:e,fileBuffer:s,pieceBuffers:i,useChunkedBuffer:r}=t;if(r){let n=[];for(let c=0;c<e.totalPieces;c++){let l=i.get(c);if(!l)throw new Error(`Missing piece ${c}`);n.push(l)}let o=new Blob(n,{type:e.fileType||"application/octet-stream"});return i.clear(),o}else return new Blob([s],{type:e.fileType||"application/octet-stream"})}_createManifest(t){let e=this.config.pieceSize,s=Math.ceil(t.size/e);return{fileName:t.name,fileSize:t.size,fileType:t.type||"application/octet-stream",pieceSize:e,totalPieces:s,createdAt:Date.now()}}async _readPiece(t,e,s){let i=s*e.pieceSize,r=Math.min(i+e.pieceSize,t.size),o=await t.slice(i,r).arrayBuffer(),c=new Uint8Array(4+o.byteLength);return c.set(new Uint8Array(o),4),c}_getPieceSize(t,e){return e===t.totalPieces-1?t.fileSize-e*t.pieceSize:t.pieceSize}_calculateProgress(t){return(t.type==="send"?t.piecesAckedCount:t.piecesReceivedCount)/t.manifest.totalPieces*100}_emitProgress(t,e){let s=Date.now();if(s-(e.lastProgressEmit||0)<100)return;let i=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||s,o=s-n;if(o>=200){let f=e.bytesTransferred-r;i=o>0?f/o*1e3:0,e.lastSpeedBytes=e.bytesTransferred,e.lastSpeedTime=s}else e.smoothedSpeed?i=e.smoothedSpeed:e.startTime&&s>e.startTime&&(i=e.bytesTransferred/(s-e.startTime)*1e3);i=Math.max(0,i),Number.isFinite(i)||(i=0);let c=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=i:i>0&&(e.smoothedSpeed=c*i+(1-c)*e.smoothedSpeed);let l=Math.round(e.smoothedSpeed);e.lastProgressEmit=s,this.emit("progress",{transferId:t,type:e.type,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),speed:l})}_encodePieceMessage(t,e,s){return new DataView(s.buffer).setUint32(0,e,!0),s.buffer}decodePieceMessage(t){let s=new DataView(t).getUint32(0,!0),i=t.slice(4);return{pieceIndex:s,data:i}}_checkProgressMilestones(t,e,s){let i=Date.now(),r=36e5,n=0;if(e.lastMilestoneCheck){let o=i-e.lastMilestoneCheck.time,c=e.bytesTransferred-e.lastMilestoneCheck.bytes;if(o>0&&o<r&&c>0)n=c/o*1e3/1e6;else if(o>=r){let l=e.startTime?i-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let o=e.startTime?i-e.startTime:0;n=o>0?e.bytesTransferred/o*1e3/1e6:0}e.lastMilestoneCheck={time:i,bytes:e.bytesTransferred},(!Number.isFinite(n)||n<0)&&(n=0),s>=90&&!e.milestone90&&(e.milestone90=!0,e.speedAt90Time=i,this.emit("progress-milestone",{transferId:t,milestone:90,speed:n})),s>=99&&!e.milestone99&&(e.milestone99=!0,this.emit("progress-milestone",{transferId:t,milestone:99,speed:n}))}_startPieceTimeout(t,e){let s=this.transfers.get(t);if(!s)return;let i=s.pieceTimeouts.get(e);i&&clearTimeout(i);let r=setTimeout(()=>{this._handlePieceTimeout(t,e)},this.config.pieceTimeout);s.pieceTimeouts.set(e,r)}_clearPieceTimeout(t,e){let s=this.transfers.get(t);if(!s)return;let i=s.pieceTimeouts.get(e);i&&(clearTimeout(i),s.pieceTimeouts.delete(e))}_handlePieceTimeout(t,e){let s=this.transfers.get(t);if(!s||s.state==="completed"||s.state==="completing")return;if(s.type==="receive"&&s.receivedOutOfOrder&&(e<=s.receivedWaterLevel||s.receivedOutOfOrder.has(e))){s.pieceTimeouts.delete(e);return}let i=Date.now()-s.lastActivityTime,r=this.config.pieceTimeout*.5,o=s.manifest.totalPieces-s.piecesReceivedCount<=10;if(i<r&&!o){this._startPieceTimeout(t,e);return}let c=s.pieceRetries.get(e)||0;c<this.config.maxPieceRetries?(s.pieceRetries.set(e,c+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:c+1}),s.type==="receive"?(s.piecesRequested.delete(e),s.piecesRequested.add(e),s.requestPiece(e),this._startPieceTimeout(t,e)):(s.inFlightPieces.delete(e),s.retryQueue.push(e),s.usePacing||this._sendNextPieces(t))):(s.inFlightPieces.delete(e),s.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}_startIdleTimeout(t){this._stopIdleTimeout(t);let e=this.transfers.get(t);e&&(e.idleTimeoutId=setTimeout(()=>{let s=this.transfers.get(t);if(!s||s.state==="completing"||s.state==="completed")return;let i=Date.now()-s.lastActivityTime;i>=this.config.idleTimeout?this.emit("idle-timeout",{transferId:t,timeSinceActivity:i}):this._startIdleTimeout(t)},this.config.idleTimeout))}_stopIdleTimeout(t){let e=this.transfers.get(t);!e||!e.idleTimeoutId||(clearTimeout(e.idleTimeoutId),e.idleTimeoutId=null)}_onBufferedAmountLow(t){this.transfers.forEach((e,s)=>{e.type==="send"&&!e.usePacing&&(e.channel===t.target||e.channels?.includes(t.target))&&this._sendNextPieces(s)})}_cleanupTransfer(t){let e=this.transfers.get(t);if(e){if(this._stopPacingLoop(t),e.pieceTimeouts.forEach(s=>clearTimeout(s)),e.pieceTimeouts.clear(),this._stopIdleTimeout(t),e.channels)for(let s of e.channels)s.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);else e.channel&&e.channel.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);e.pieceBuffers&&e.pieceBuffers.clear(),e.fileBuffer=null,this.rateLimits.delete(t),this.transfers.delete(t)}}_checkRateLimit(t){let e=Date.now(),s=this.rateLimits.get(t);return s||(s={count:0,windowStart:e},this.rateLimits.set(t,s)),e-s.windowStart>this.config.rateLimitWindowMs&&(s.count=0,s.windowStart=e),s.count++,{allowed:s.count<=this.config.maxPiecesPerSecond,currentRate:s.count,limit:this.config.maxPiecesPerSecond}}};var fe="0.4.0",I="https://perkoon.com",$=class extends Ie{constructor(t=I,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new W,this.transport=null,this.signaling=null,this.fileSource=null}async send(t,e={}){this.fileSource=await ee(t),this.emit("file-ready",{name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type});let s=e.session?await this._joinSession(e.session,e.password,"sender",e.senderKey):await this._createSession(e.password,e.source);this.sessionCode=s.session_code,this.emit("session-created",{sessionCode:s.session_code,expiresAt:s.expires_at}),this.signaling=new z(this.serverUrl);let{peers:i}=await this.signaling.connect(s.session_code,s.token,s.peer_id,"sender");this.emit("waiting-for-receiver");let r=i.length>0?i[0]:await this._waitForPeer();this.emit("receiver-connected",{peerId:r});let n=`batch_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`,o=[{id:`file_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`,name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type}];this.signaling.sendFilesAdded(n,o,this.fileSource.size),this.emit("waiting-for-acceptance");let c=await this._waitForSignal("transfer_accepted",this.peerTimeout,w=>w.batch_id===n);this.emit("transfer-accepted",{batchId:c.batch_id});let l=`transfer_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new B,this.transport.initialize(s.peer_id,s.ice_servers);let f=await this.transport.createOffer(l);this.signaling.sendOffer(r,f),this.transport.on("ice-candidate",({candidate:w,mid:h})=>{this.signaling.sendIceCandidate(r,{candidate:w,sdpMid:h||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",w=>{let h=w.candidate;this.transport.addIceCandidate({candidate:h.candidate||h,mid:h.sdpMid||h.mid||"0"})});let u=await this._waitForSignal("answer");this.transport.handleAnswer(u.answer);let S=await this.transport.waitForChannel("control"),A=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,A);let F=this.engine.transfers.get(l).manifest;this._setupControlHandler(S,l);let v=JSON.stringify({type:"manifest",transfer_id:l,manifest:F,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});S.send(v),this._setupProgressEvents(l),await this.engine.startSend(l);let g=await this._waitForCompletion(l);return await this.fileSource.close(),this.signaling.disconnect(),this.transport.close(),{sessionCode:s.session_code,speed:g.speed,duration:g.duration}}async receive(t,e,s={}){let i=await this._joinSession(t,s.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new z(this.serverUrl);let{peers:r}=await this.signaling.connect(t,i.token,i.peer_id,"receiver");this.signaling.on("files_offered",g=>{this.emit("files-offered",{files:g.files,from:g.from}),this.signaling.channel.push("accept_transfer",{batch_id:g.batch_id})});let n=r.length>0?r[0]:await this._waitForPeer();this.emit("sender-found",{peerId:n}),this.signaling.channel.push("signal",{to:n,type:"ready",data:{role:"receiver"}}),this.transport=new B,this.transport.initialize(i.peer_id,i.ice_servers),this.transport.on("ice-candidate",({candidate:g,mid:w})=>{this.signaling.sendIceCandidate(n,{candidate:g,sdpMid:w||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",g=>{let w=g.candidate;this.transport.addIceCandidate({candidate:w.candidate||w,mid:w.sdpMid||w.mid||"0"})});let o=await this._waitForSignal("offer"),c=await this.transport.handleOffer(o.offer);this.signaling.sendAnswer(n,c);let l=await this.transport.waitForChannel("control"),{channel:f,label:u}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=s.stdout?ne():ie(e,{overwrite:s.overwrite});let S=this.fileSink,A=[],v=await new Promise((g,w)=>{let h=null;l.addEventListener("message",async C=>{if(typeof C.data=="string")try{let m=JSON.parse(C.data);if(m.type==="manifest"&&!h){h=m.transfer_id,this._activeTransferId=h;let _=m.manifest;this.emit("receiving-file",{name:_.fileName,size:_.fileSize}),await S.startStreamingReceive(h,{fileName:_.fileName,size:_.fileSize,fileSize:_.fileSize,type:_.fileType,pieceSize:_.pieceSize},{}),this.engine.initReceive(h,_,{streamingHandler:S}),this._setupProgressEvents(h),this.engine.startReceive(h,b=>{this._safeSend(l,JSON.stringify({type:"request",transfer_id:h,piece_index:b}))});let M=-1;this._ackInterval=setInterval(()=>{let b=this.engine.transfers.get(h);if(!b||b.state==="completed"||b.state==="completing"){clearInterval(this._ackInterval);return}let N=b.receivedWaterLevel;if(N>M){M=N;let D=b.receivedOutOfOrder.size>0?Array.from(b.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:h,wl:N,ooo:D}))}},100)}}catch(m){m.message&&!m.message.includes("Unexpected token")&&w(m)}}),f.addEventListener("message",async C=>{let m=C.data;if(!h||!(m instanceof ArrayBuffer||Buffer.isBuffer(m)))return;let _=m instanceof ArrayBuffer?m:m.buffer.slice(m.byteOffset,m.byteOffset+m.byteLength),{pieceIndex:M,data:b}=this.engine.decodePieceMessage(_);if(M>=0&&b&&(await this.engine.handlePieceData(h,M,b))?.complete){this._ackInterval&&clearInterval(this._ackInterval);let D=this.engine.transfers.get(h);D&&this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:h,wl:D.receivedWaterLevel}));let ye=S.getFilePath(h);A.push(ye),await this.engine.completeReceive(h),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:h}));let V=this.engine.transfers.get(h),Z=Date.now()-(V?.startTime||Date.now()),Y=V?.manifest;g({files:A,speed:Y?Y.fileSize/(Z/1e3):0,duration:Z})}})});return this.signaling.disconnect(),this.transport.close(),v}async _createSession(t,e="agent_cli"){let s=`${this.serverUrl}/api/v1/sessions`,i={client_version:fe,source:e};t&&(i.password=t);let r=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!r.ok){let n=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${n.error||r.statusText}`)}return r.json()}async _joinSession(t,e,s="receiver",i=void 0){let r=`${this.serverUrl}/api/v1/sessions/${t}/join`,n={role:s,client_version:fe};e&&(n.password=e),i&&(n.sender_key=i);let o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok){let c=await o.json().catch(()=>({}));throw o.status===404?new Error(`Session ${t} not found. Check the code and try again.`):o.status===410?new Error(`Session ${t} has expired.`):o.status===403?new Error(c.error||"Access denied"):new Error(`Failed to join session: ${c.error||o.statusText}`)}return o.json()}_waitForPeer(){return new Promise((t,e)=>{let s=setTimeout(()=>{i(),e(new Error(`No peer connected within ${Math.round(this.peerTimeout/1e3)}s`))},this.peerTimeout),i=()=>{clearTimeout(s),this.signaling.removeListener("ready",r),this.signaling.removeListener("offer",r),this.signaling.removeListener("peer_joined",r)},r=n=>{(n.type==="ready"||n.type==="offer"||n.type==="peer_joined"&&(!n.role||n.role==="receiver"))&&(i(),t(n.from))};this.signaling.on("ready",r),this.signaling.on("offer",r),this.signaling.on("peer_joined",r)})}_waitForSignal(t,e=3e4,s=null){return new Promise((i,r)=>{let n=setTimeout(()=>{this.signaling.removeListener(t,o),r(new Error(`Timed out waiting for ${t} signal`))},e),o=c=>{s&&!s(c)||(this.signaling.removeListener(t,o),clearTimeout(n),i(c))};this.signaling.on(t,o)})}_setupProgressEvents(t){let e=setInterval(()=>{let s=this.engine.transfers.get(t);if(!s){clearInterval(e);return}let i=s.manifest,r=i.totalPieces,n;s.type==="send"?n=s.piecesAckedCount||0:n=s.piecesReceivedCount||0;let o=r>0?Math.round(n/r*100):0,c=Date.now()-(s.startTime||Date.now()),l=n*i.pieceSize,f=c>0?l/(c/1e3):0,u=f>0?Math.round((i.fileSize-l)/f):0;this.emit("progress",{transferId:t,percent:Math.min(o,100),speed:f,eta:u,bytesTransferred:l,totalBytes:i.fileSize}),(s.state==="completed"||s.state==="cancelled")&&clearInterval(e)},200)}_setupControlHandler(t,e){t.addEventListener("message",s=>{if(typeof s.data=="string")try{let i=JSON.parse(s.data);switch(i.type){case"ack":this.engine.handlePieceAck(e,i.piece_index);break;case"ack_wl":this.engine.handleWaterLevelAck(e,i.wl,i.ooo);break;case"water_level_ack":this.engine.handleWaterLevelAck(e,i.water_level,i.out_of_order);break;case"ack_batch":Array.isArray(i.pieces)&&i.pieces.length>0&&this.engine.handlePieceAckBatch(e,i.pieces);break;case"request":this.engine.requeuePiece(e,i.piece_index);break;case"complete":{let r=this.engine.transfers.get(e);r&&r.state!=="completed"&&(r.piecesAckedCount=r.manifest.totalPieces,r.bytesTransferred=r.manifest.fileSize,r.inFlightPieces.clear(),r.state="completed")}this.emit("transfer-complete",{transferId:e});break;case"backpressure":i.pause?this.engine.pause(e):this.engine.resume(e);break}}catch{}})}_waitForCompletion(t){return new Promise((e,s)=>{let i=()=>{let n=this.engine.transfers.get(t);if(!n){clearInterval(r),s(new Error("Transfer context lost"));return}if(n.state==="completed"){clearInterval(r);let o=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(o/1e3),duration:o});return}if(n.state==="cancelled"||n.state==="failed"){clearInterval(r),s(new Error("Transfer failed"));return}},r=setInterval(()=>{i()},500);this.on("transfer-complete",({transferId:n})=>{n===t&&i()})})}_safeSend(t,e){try{t.readyState==="open"&&t.send(e)}catch{}}destroy(){this._ackInterval&&clearInterval(this._ackInterval),this.fileSink&&this._activeTransferId&&(this.fileSink.abort(this._activeTransferId).catch(()=>{}),this.fileSink=null),this.fileSource&&this.fileSource.close().catch(()=>{}),this.signaling&&this.signaling.disconnect(),this.transport&&this.transport.close()}};import qe from"node:path";import{access as je,stat as He}from"node:fs/promises";var ue="0.4.0",pe=0,R=1,he=2,G=3,Qe=4,Ge=5,k=process.argv.slice(2),de=k[0],p={};for(let a=1;a<k.length;a++)if(k[a].startsWith("--")){let t=k[a].slice(2);k[a+1]&&!k[a+1].startsWith("--")?p[t]=k[++a]:p[t]=!0}else p._positional||(p._positional=k[a]);var E=(typeof p.server=="string"?p.server:null)||process.env.PERKOON_URL||I,L=p.json===!0,x=p.quiet===!0,Je=p.overwrite===!0,X=p.output||"./received",O=typeof p.password=="string"?p.password:void 0,Xe=typeof p.session=="string"?p.session:void 0,Ke=typeof p["sender-key"]=="string"?p["sender-key"]:void 0,Ve=typeof p.source=="string"?p.source:void 0,Ze=Ve||process.env.PERKOON_SOURCE||"agent_cli",J=typeof p.timeout=="string"?parseInt(p.timeout,10):NaN,K=!isNaN(J)&&J>0?J:300;L&&X==="-"&&(process.stderr.write(` Error: --json and --output - are mutually exclusive
3
- `),process.exit(R));function d(a){!x&&!L&&process.stderr.write(a+`
2
+ import{EventEmitter as qe}from"node:events";import Q from"node:crypto";import _e from"node-datachannel";import{EventEmitter as Se}from"node:events";var{PeerConnection:ve}=_e,B=class extends Se{constructor(){super(),this.pc=null,this.channels=new Map,this.remoteDescriptionSet=!1,this.pendingCandidates=[]}initialize(t,e){let s={iceServers:e.map(i=>(Array.isArray(i.urls)?i.urls:[i.urls]).map(n=>i.username&&i.credential?`${n}:${i.username}:${i.credential}`:n).join(",")),maxMessageSize:262144};this.pc=new ve(t,s),this.pc.onLocalCandidate((i,r)=>{this.emit("ice-candidate",{candidate:i,mid:r})}),this.pc.onStateChange(i=>{this.emit("connection-state",i)}),this.pc.onGatheringStateChange(i=>{this.emit("gathering-state",i)}),this.pc.onDataChannel(i=>{let r=this._wrapChannel(i),n=i.getLabel();this.channels.set(n,r),this.emit("data-channel",r,n)})}async createOffer(t){return new Promise(e=>{this.pc.onLocalDescription((n,o)=>{e({sdp:n,type:o})});let s=this.pc.createDataChannel("control",{ordered:!0});this.channels.set("control",this._wrapChannel(s));let i=`transfer-${t}`,r=this.pc.createDataChannel(i,{ordered:!0,maxRetransmits:3});this.channels.set(i,this._wrapChannel(r))})}async handleOffer(t){return new Promise(e=>{this.pc.onLocalDescription((s,i)=>{e({sdp:s,type:i})}),this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()})}handleAnswer(t){this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()}addIceCandidate(t){t.candidate&&(this.remoteDescriptionSet?this.pc.addRemoteCandidate(t.candidate,t.mid||"0"):this.pendingCandidates.push(t))}_flushPendingCandidates(){for(let t of this.pendingCandidates)this.pc.addRemoteCandidate(t.candidate,t.mid||"0");this.pendingCandidates=[]}getChannel(t){return this.channels.get(t)||null}async waitForChannel(t,e=3e4){let s=this.channels.get(t);return s?.readyState==="open"?s:new Promise((i,r)=>{let n=setTimeout(()=>{r(new Error(`Data channel "${t}" open timeout`))},e),o=c=>{c.readyState==="open"?(clearTimeout(n),i(c)):c.addEventListener("open",()=>{clearTimeout(n),i(c)})};if(s)o(s);else{let c=(l,f)=>{f===t&&(this.removeListener("data-channel",c),o(l))};this.on("data-channel",c)}})}async waitForChannelByPrefix(t,e=3e4){for(let[s,i]of this.channels)if(s.startsWith(t))return i.readyState==="open"?{channel:i,label:s}:new Promise((r,n)=>{let o=setTimeout(()=>n(new Error(`Channel "${s}" open timeout`)),e);i.addEventListener("open",()=>{clearTimeout(o),r({channel:i,label:s})})});return new Promise((s,i)=>{let r=setTimeout(()=>{this.removeListener("data-channel",n),i(new Error(`No channel with prefix "${t}" within ${e}ms`))},e),n=(o,c)=>{c.startsWith(t)&&(this.removeListener("data-channel",n),o.readyState==="open"?(clearTimeout(r),s({channel:o,label:c})):o.addEventListener("open",()=>{clearTimeout(r),s({channel:o,label:c})}))};this.on("data-channel",n)})}close(){for(let[,t]of this.channels)try{t.close()}catch{}if(this.channels.clear(),this.pc){try{this.pc.close()}catch{}this.pc=null}}_wrapChannel(t){let e=new Map,s="connecting";try{t.isOpen&&t.isOpen()&&(s="open")}catch{}let i={get readyState(){return s},get bufferedAmount(){try{return t.bufferedAmount()||0}catch{return 0}},send(n){n instanceof ArrayBuffer?t.sendMessageBinary(Buffer.from(n)):n instanceof Uint8Array?t.sendMessageBinary(Buffer.from(n.buffer,n.byteOffset,n.byteLength)):Buffer.isBuffer(n)?t.sendMessageBinary(n):t.sendMessage(String(n))},close(){s="closed";try{t.close()}catch{}},addEventListener(n,o){e.has(n)||e.set(n,new Set),e.get(n).add(o)},removeEventListener(n,o){let c=e.get(n);c&&c.delete(o)},set onmessage(n){i.addEventListener("message",n)},set onopen(n){i.addEventListener("open",n)},set onclose(n){i.addEventListener("close",n)},set onerror(n){i.addEventListener("error",n)}};t.onOpen(()=>{s="open",r("open",{})}),t.onClosed(()=>{s="closed",r("close",{})}),t.onError(n=>{r("error",{error:n})}),t.onMessage(n=>{let o;Buffer.isBuffer(n)?o=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):o=n,r("message",{data:o})});function r(n,o){let c=e.get(n);if(c)for(let l of c)try{l(o)}catch{}}return i}};import{Socket as Pe}from"phoenix";import Te from"ws";import{EventEmitter as be}from"node:events";var z=class extends be{constructor(t){super(),this.serverUrl=t.replace(/\/$/,""),this.socket=null,this.channel=null,this.connected=!1,this.heartbeatInterval=null,this.peers=[]}async connect(t,e,s,i){let r=this.serverUrl.replace(/^https:/,"wss:").replace(/^http:/,"ws:")+"/socket";return new Promise((n,o)=>{this.socket=new Pe(r,{params:{},transport:Te,timeout:15e3,reconnectAfterMs:()=>36e5}),this.socket.onError(c=>{this.connected||o(new Error(`Signaling connection failed: ${c?.message||"unknown error"}`))}),this.socket.connect(),this.channel=this.socket.channel(`p2p:${t}`,{token:e,peer_id:s,role:i}),this.channel.on("message",c=>{this.emit(c.type,c)}),this.channel.onError(()=>{this.connected=!1}),this.channel.onClose(()=>{this.connected=!1,this._stopHeartbeat(),this.emit("disconnected")}),this.channel.join().receive("ok",c=>{this.connected=!0,this.peers=c.peers||[],this._startHeartbeat(),n({peers:this.peers})}).receive("error",c=>{o(new Error(`Channel join failed: ${c.reason||"unknown"}`))}).receive("timeout",()=>{o(new Error("Channel join timed out"))})})}sendOffer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("offer",{to:t,offer:e})}sendAnswer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("answer",{to:t,answer:e})}sendIceCandidate(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("ice_candidate",{to:t,candidate:e})}sendFilesAdded(t,e,s){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("files_added",{batch_id:t,files:e,total_size:s})}disconnect(){if(this._stopHeartbeat(),this.channel){try{this.channel.leave()}catch{}this.channel=null}if(this.socket){try{this.socket.disconnect()}catch{}this.socket=null}this.connected=!1}_startHeartbeat(){this._stopHeartbeat(),this.heartbeatInterval=setInterval(()=>{this.connected&&this.channel&&this.channel.push("heartbeat",{})},1e4)}_stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}};import{open as Ce}from"node:fs/promises";import ke from"node:path";import{lookup as Re}from"mime-types";async function ee(a){let t=await Ce(a,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${a}`);return{name:ke.basename(a),size:e.size,type:Re(a)||"application/octet-stream",async readSlice(s,i){let r=i-s,n=Buffer.alloc(r),{bytesRead:o}=await t.read(n,0,r,s);return o===r?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n.buffer.slice(n.byteOffset,n.byteOffset+o)},async close(){await t.close()}}}import{open as Ee,mkdir as Le,access as te}from"node:fs/promises";import se from"node:path";function ie(a,t={}){let e=new Map;return{async startStreamingReceive(s,i,r){await Le(a,{recursive:!0});let n=Fe(i.fileName),o=se.join(a,n);t.overwrite||(o=await Ae(o));let c=await Ee(o,"w");i.size>0&&await c.truncate(i.size),e.set(s,{fh:c,filePath:o,pieceSize:i.pieceSize,size:i.size,fileName:i.fileName})},async writeChunk(s,i,r){let n=e.get(s);if(!n)return{success:!1,error:"NO_HANDLE"};let o=i*n.pieceSize,c=Buffer.isBuffer(r)?r:Buffer.from(r);try{return await n.fh.write(c,0,c.byteLength,o),{success:!0}}catch(l){return{success:!1,error:l.message}}},async completeStream(s){let i=e.get(s);i&&(await i.fh.datasync(),await i.fh.close(),e.delete(s))},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(s){return e.get(s)?.filePath},async abort(s){let i=e.get(s);if(i){try{await i.fh.close()}catch{}e.delete(s);try{let{unlink:r}=await import("node:fs/promises");await r(i.filePath)}catch{}}}}}async function Ae(a){try{await te(a)}catch{return a}let t=se.extname(a),e=a.slice(0,-t.length||void 0);for(let s=1;s<1e3;s++){let i=`${e}_${s}${t}`;try{await te(i)}catch{return i}}return`${e}_${Date.now()}${t}`}function Fe(a){return a.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function ne(){let a=new Map;return{async startStreamingReceive(t,e,s){a.set(t,{pieceSize:e.pieceSize,totalPieces:Math.ceil(e.size/e.pieceSize),size:e.size,nextPiece:0,buffer:new Map,bytesWritten:0})},async writeChunk(t,e,s){let i=a.get(t);if(!i)return{success:!1,error:"NO_HANDLE"};let r=Buffer.isBuffer(s)?s:Buffer.from(s);for(i.buffer.set(e,r);i.buffer.has(i.nextPiece);){let n=i.buffer.get(i.nextPiece);i.buffer.delete(i.nextPiece);let o=i.nextPiece*i.pieceSize,c=i.size-o,l=c<n.byteLength?n.subarray(0,c):n;process.stdout.write(l),i.bytesWritten+=l.byteLength,i.nextPiece++}return{success:!0}},async completeStream(t){a.delete(t)},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(t){return"stdout"},async abort(t){a.delete(t)}}}var U=class{constructor(){this._listeners=new Map,this._onceListeners=new Map}on(t,e){return this._listeners.has(t)||this._listeners.set(t,new Set),this._listeners.get(t).add(e),()=>this.off(t,e)}once(t,e){return this._onceListeners.has(t)||this._onceListeners.set(t,new Set),this._onceListeners.get(t).add(e),()=>{let s=this._onceListeners.get(t);s&&s.delete(e)}}off(t,e){let s=this._listeners.get(t);s&&s.delete(e);let i=this._onceListeners.get(t);i&&i.delete(e)}removeAllListeners(t){t?(this._listeners.delete(t),this._onceListeners.delete(t)):(this._listeners.clear(),this._onceListeners.clear())}emit(t,e){let s=this._listeners.get(t);s&&s.forEach(r=>{try{r(e)}catch{}});let i=this._onceListeners.get(t);if(i){let r=[...i];this._onceListeners.delete(t),r.forEach(n=>{try{n(e)}catch{}})}}listenerCount(t){let e=this._listeners.get(t)?.size||0,s=this._onceListeners.get(t)?.size||0;return e+s}hasListeners(t){return this.listenerCount(t)>0}eventNames(){return[...new Set([...this._listeners.keys(),...this._onceListeners.keys()])]}waitFor(t,e){return new Promise((s,i)=>{let r,n=o=>{r&&clearTimeout(r),s(o)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),i(new Error(`Timeout waiting for event: ${t}`))},e))})}};function Me(){if(typeof window>"u"||typeof navigator>"u")return Be();let a=ze();return{browser:a.browser,version:a.version,platform:Oe(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:$e(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:Ne(a.browser),recommendedChunkSize:De(a.browser),maxFileSize:Ue(a.browser),maxFileSizeLabel:We(a.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:xe(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function Be(){return{browser:"node",version:0,platform:"node",supportsFileSystemAccess:!1,supportsDirectoryPicker:!1,supportsOPFS:!1,supportsWebRTC:!0,supportsDataChannel:!0,maxChannelBufferSize:256*1024*1024,recommendedChunkSize:256*1024,maxFileSize:Number.POSITIVE_INFINITY,maxFileSizeLabel:"unlimited",supportsTransferable:typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:typeof ReadableStream<"u"&&typeof WritableStream<"u",isMemoryConstrained:!1,isMobile:!1}}function ze(){let a=navigator.userAgent,t="unknown",e="0";if(a.includes("Edg/")){t="edge";let s=a.match(/Edg\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Chrome/")){t="chrome";let s=a.match(/Chrome\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Firefox/")){t="firefox";let s=a.match(/Firefox\/(\d+)/);e=s?s[1]:"0"}else if(a.includes("Safari/")&&!a.includes("Chrome")){t="safari";let s=a.match(/Version\/(\d+)/);e=s?s[1]:"0"}return{browser:t,version:parseInt(e,10)}}function Oe(){let a=navigator.userAgent;return a.includes("Windows")?"windows":a.includes("Mac")?"macos":a.includes("Linux")?"linux":a.includes("Android")?"android":a.includes("iPhone")||a.includes("iPad")?"ios":"unknown"}function $e(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function xe(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function Ne(a){switch(a){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function De(a){switch(a){case"chrome":case"edge":return 256*1024;case"firefox":return 64*1024;case"safari":return 128*1024;default:return 64*1024}}function Ue(a){switch(a){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return 1/0;case"safari":return 4*1024*1024*1024;default:return 2*1024*1024*1024}}function We(a){switch(a){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return"Unlimited";case"safari":return"4GB";default:return"2GB"}}var H=null;function re(){return H||(H=Me()),H}var oe={chrome:1/0,edge:1/0,brave:1/0,opera:1/0,vivaldi:1/0,samsung:1/0,ucbrowser:1/0,firefox:1/0,safari:4294967296,ios:4294967296,unknown:2147483648},ae={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function ce(a){return oe[a]||oe.unknown}function le(a){return ae[a]||ae.unknown}var Ie={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxPieceRetries:3,maxRetryQueueSize:1e4,maxInFlightMultiplier:10,highWaterMark:16*1024*1024,lowWaterMark:4*1024*1024,maxPiecesPerSecond:5e3,rateLimitWindowMs:1e3,channelRotationThreshold:512*1024*1024,channelRotationEnabled:!1,parallelChannels:1,parallelChannelsEnabled:!1,parallelChannelsThreshold:100*1024*1024,pacingEnabled:!0,pacingThreshold:0,pacingIntervalMs:10,targetBufferedAmount:8*1024*1024,minBufferedAmount:4*1024*1024,maxBufferedAmount:16*1024*1024,maxPiecesPerTick:32,burstPiecesPerTick:64,maxPendingReads:512,maxInFlightPieces:4e3},W=class extends U{constructor(t={}){super(),this.config={...Ie,...t},this.transfers=new Map,this.rateLimits=new Map,this._onBufferedAmountLow=this._onBufferedAmountLow.bind(this)}initSend(t,e,s){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let i=this._createManifest(e),r=this.config.parallelChannelsEnabled&&e.size>=this.config.parallelChannelsThreshold,n={id:t,type:"send",file:e,manifest:i,channel:s,channels:[s],channelIndex:0,useParallelChannels:r,state:"initialized",nextPieceToSend:0,piecesAckedCount:0,bytesTransferred:0,paused:!1,inFlightPieces:new Map,retryQueue:[],channelBytes:[0],channelThresholds:[this.config.channelRotationThreshold],channelRotationCounts:[0],pendingRotations:new Set,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,idleTimeoutId:null,pieceTimeouts:new Map,pieceRetries:new Map};return this.transfers.set(t,n),{transferId:t,manifest:i}}async startSend(t){let e=this.transfers.get(t);if(!e||e.type!=="send")throw new Error(`Send transfer not found: ${t}`);if(e.state==="transferring")return;e.state="transferring",e.startTime=e.startTime||Date.now();let s=this.config.pacingEnabled&&e.manifest.fileSize>=this.config.pacingThreshold;e.usePacing=s;for(let i of e.channels)i.addEventListener("bufferedamountlow",this._onBufferedAmountLow);e.useParallelChannels&&e.channels.length<this.config.parallelChannels&&this.emit("parallel-channels-needed",{transferId:t,currentCount:e.channels.length,targetCount:this.config.parallelChannels}),this._startIdleTimeout(t),s?this._startPacingLoop(t):await this._sendNextPieces(t)}addChannel(t,e){let s=this.transfers.get(t);if(!s||s.type!=="send")return;let i=s.channels.length;s.channels.push(e),e.addEventListener("bufferedamountlow",this._onBufferedAmountLow),s.channelBytes.push(0),s.channelRotationCounts.push(0),s.channelThresholds||(s.channelThresholds=[this.config.channelRotationThreshold]);let r=this.config.channelRotationThreshold*(1+i*.25);s.channelThresholds.push(r),!s.paused&&s.state==="transferring"&&this._sendNextPieces(t)}handlePieceAck(t,e){let s=this.transfers.get(t);if(!s||s.type!=="send")return;let i=s.inFlightPieces.get(e);if(!i)return;let r=i.channelIndex>=0?i.channelIndex:0;s.inFlightPieces.delete(e),s.lastActivityTime=Date.now(),s.piecesAckedCount++;let n=this._getPieceSize(s.manifest,e);s.bytesTransferred+=n,s.channelBytes[r]!==void 0&&(s.channelBytes[r]+=n),this._checkChannelRotation(t,s,r);let o=this._calculateProgress(s);if(this._checkProgressMilestones(t,s,o),this._emitProgress(t,s),s.piecesAckedCount===s.manifest.totalPieces){this._completeSend(t);return}!s.paused&&!s.usePacing&&this._sendNextPieces(t)}handlePieceAckBatch(t,e){if(Array.isArray(e))for(let s of e)this.handlePieceAck(t,s)}handleWaterLevelAck(t,e,s){let i=this.transfers.get(t);if(!(!i||i.type!=="send")){if(typeof e=="number"&&e>=0)for(let r=0;r<=e;r++)this.handlePieceAck(t,r);if(Array.isArray(s))for(let r of s)this.handlePieceAck(t,r)}}requeuePiece(t,e){let s=this.transfers.get(t);!s||s.type!=="send"||s.state==="completed"||s.state==="completing"||(s.inFlightPieces.delete(e),s.retryQueue.includes(e)||s.retryQueue.push(e),s.lastActivityTime=Date.now(),!s.paused&&!s.usePacing&&this._sendNextPieces(t))}markSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||this._completeSend(t)}_checkChannelRotation(t,e,s){if(!this.config.channelRotationEnabled||e.pendingRotations.has(s))return;let i=e.channelBytes[s]||0,r=e.channelThresholds?.[s]||this.config.channelRotationThreshold;i>=r&&(e.pendingRotations.add(s),this.emit("channel-rotation-needed",{transferId:t,channelIndex:s,bytesTransferred:e.bytesTransferred,channelBytes:i,threshold:r,rotationCount:e.channelRotationCounts[s]||0}))}rotateSingleChannel(t,e,s){let i=this.transfers.get(t);if(!i||i.type!=="send"||e>=i.channels.length)return;let r=i.channels[e],n=(i.channelRotationCounts[e]||0)+1;i.channels[e]=s,e===0&&(i.channel=s),i.channelBytes[e]=0,i.channelRotationCounts[e]=n,i.pendingRotations.delete(e),s.addEventListener("bufferedamountlow",this._onBufferedAmountLow),r&&r.readyState==="open"&&this._drainAndCloseChannel(r,e,n),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}_drainAndCloseChannel(t,e,s){let i=()=>{if(t.readyState==="open"){if(t.bufferedAmount===0){try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}return}setTimeout(i,50)}};i(),setTimeout(()=>{if(t.readyState==="open")try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}},5e3)}rotateChannel(t,e){this.rotateChannels(t,[e])}initReceive(t,e,s={}){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let i=re(),r=e.fileSize,n=ce(i.browser);if(r>n){let c=le(i.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${i.browser}'s ${c} limit. Use a Chromium browser (Chrome, Edge, Brave) for unlimited file sizes.`)}let o={id:t,type:"receive",manifest:e,state:"initialized",nextPieceToRequest:0,piecesReceivedCount:0,receivedWaterLevel:-1,receivedOutOfOrder:new Set,maxOutOfOrderSize:1e4,piecesRequested:new Set,bytesTransferred:0,streamingHandler:s.streamingHandler||null,fileBuffer:null,pieceBuffers:new Map,paused:!1,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,pieceTimeouts:new Map,idleTimeoutId:null,pieceRetries:new Map};return o.streamingHandler||this._initReceiveBuffer(o,e),this.transfers.set(t,o),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(o)}}startReceive(t,e){let s=this.transfers.get(t);if(!s||s.type!=="receive")throw new Error(`Receive transfer not found: ${t}`);s.state="transferring",s.startTime=s.startTime||Date.now(),s.requestPiece=e,this._startIdleTimeout(t),this._requestNextPieces(t)}async handlePieceData(t,e,s){let i=this.transfers.get(t);if(!i||i.type!=="receive")return{success:!1,error:"Transfer not found"};if(i.state==="completing"||i.state==="completed")return{success:!0,duplicate:!0,progress:100};if(e<0||e>=i.manifest.totalPieces)return{success:!1,error:"Invalid piece index"};if(!(s instanceof ArrayBuffer||s instanceof Uint8Array||ArrayBuffer.isView(s)))return{success:!1,error:"Invalid piece data type"};let r=s.byteLength;if(r===0&&!(e===0&&i.manifest.fileSize===0))return{success:!1,error:"Empty piece data"};let n=this._getPieceSize(i.manifest,e);if(r>n)return{success:!1,error:"Invalid piece size"};let c=e===i.manifest.totalPieces-1?1:Math.floor(n*.5);if(this._checkRateLimit(t),this._clearPieceTimeout(t,e),e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e))return{success:!0,duplicate:!0,progress:this._calculateProgress(i)};try{if(i.lastActivityTime=Date.now(),i.streamingHandler){let f=await i.streamingHandler.writeChunk(t,e,new Uint8Array(s));if(f===!1||f&&f.success===!1)return{success:!1,error:f&&f.error||"Failed to write piece to streaming handler",retryable:!0,pieceIndex:e}}else this._storePieceInMemory(i,e,s);if(e===i.receivedWaterLevel+1)for(i.receivedWaterLevel=e;i.receivedOutOfOrder.has(i.receivedWaterLevel+1);)i.receivedOutOfOrder.delete(i.receivedWaterLevel+1),i.receivedWaterLevel++;else{if(!!!i.streamingHandler&&i.receivedOutOfOrder.size>=i.maxOutOfOrderSize)return{success:!1,error:"Out-of-order buffer full - retry later",retryable:!0,pieceIndex:e};i.receivedOutOfOrder.add(e)}i.piecesReceivedCount++,i.piecesRequested.delete(e),i.bytesTransferred+=r;let l=this._calculateProgress(i);return this._checkProgressMilestones(t,i,l),this._emitProgress(t,i),i.piecesReceivedCount===i.manifest.totalPieces?(i.state="completing",this._stopIdleTimeout(t),{success:!0,complete:!0,progress:100}):(i.paused||this._requestNextPieces(t),{success:!0,progress:this._calculateProgress(i),piecesReceived:i.piecesReceivedCount,totalPieces:i.manifest.totalPieces})}catch(l){return{success:!1,error:l.message}}}async completeReceive(t){let e=this.transfers.get(t);if(!e||e.type!=="receive")return{success:!1,error:"Transfer not found"};if(e.piecesReceivedCount!==e.manifest.totalPieces)return{success:!1,error:`Incomplete: ${e.piecesReceivedCount}/${e.manifest.totalPieces}`};e.state="completing",this._stopIdleTimeout(t);try{return e.streamingHandler?(await e.streamingHandler.completeStream(t),{success:!0,streaming:!0,manifest:e.manifest}):{success:!0,blob:this._assembleFile(e),manifest:e.manifest,fileName:e.manifest.fileName}}catch(s){return{success:!1,error:s.message}}}pause(t){let e=this.transfers.get(t);e&&(e.paused=!0,this._stopPacingLoop(t),this._stopIdleTimeout(t),this.emit("paused",{transferId:t}))}resume(t){let e=this.transfers.get(t);!e||!e.paused||(e.paused=!1,this._startIdleTimeout(t),e.type==="send"?e.usePacing?this._startPacingLoop(t):this._sendNextPieces(t):this._requestNextPieces(t),this.emit("resumed",{transferId:t}))}cancel(t){let e=this.transfers.get(t);e&&(this._cleanupTransfer(t),this.emit("cancelled",{transferId:t,type:e.type}))}cleanup(t){this._cleanupTransfer(t)}getStats(t){let e=this.transfers.get(t);if(!e)return null;let s=e.startTime?Date.now()-e.startTime:0,i=s>0?e.bytesTransferred/s*1e3:0;return{transferId:t,type:e.type,state:e.state,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),piecesCompleted:e.type==="send"?e.piecesAckedCount:e.piecesReceivedCount,totalPieces:e.manifest.totalPieces,speed:i,elapsed:s,paused:e.paused}}async _sendNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.retryQueue.length>=this.config.maxRetryQueueSize){this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let s=this.config.maxConcurrentPieces*e.channels.length*this.config.maxInFlightMultiplier;if(e.inFlightPieces.size>=s)return;let i=e.channel,r=e.channels.length===1;if(r){if(i.readyState!=="open"||i.bufferedAmount>this.config.highWaterMark)return}else{let u=!1;for(let S of e.channels)if(S.readyState==="open"&&S.bufferedAmount<=this.config.highWaterMark){u=!0;break}if(!u)return}let o=this.config.maxConcurrentPieces*e.channels.length-e.inFlightPieces.size;if(o<=0)return;let c=[];for(;e.retryQueue.length>0&&c.length<o;){let u=e.retryQueue.shift();e.inFlightPieces.has(u)||c.push(u)}for(;e.nextPieceToSend<e.manifest.totalPieces&&c.length<o;){let u=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(u)||c.push(u)}if(c.length===0)return;let l=Date.now();for(let u of c)e.inFlightPieces.set(u,l);let f=r?4:4*e.channels.length;try{for(let u=0;u<c.length;u+=f){let A=c.slice(u,u+f).map(async v=>{let g=await this._readPiece(e.file,e.manifest,v);return{pieceIndex:v,data:g}}),F=await Promise.all(A);if(r)for(let{pieceIndex:v,data:g}of F){if(i.readyState!=="open"||i.bufferedAmount>this.config.highWaterMark)return;i.send(this._encodePieceMessage(t,v,g)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,v)}else for(let{pieceIndex:v,data:g}of F){let w=null,h=-1;for(let C=0;C<e.channels.length;C++){let m=(e.channelIndex+C)%e.channels.length,_=e.channels[m];if(_.readyState==="open"&&_.bufferedAmount<=this.config.highWaterMark){w=_,h=m,e.channelIndex=(m+1)%e.channels.length;break}}if(!w)return;w.send(this._encodePieceMessage(t,v,g)),e.inFlightPieces.set(v,{sendTime:Date.now(),channelIndex:h}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,v)}}}catch(u){for(let S of c)e.inFlightPieces.delete(S),e.retryQueue.push(S);this.emit("error",{transferId:t,error:`Failed to send pieces: ${u.message}`})}}_completeSend(t){let e=this.transfers.get(t);e&&(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(s=>clearTimeout(s)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),this.emit("complete",{transferId:t,type:"send",bytesTransferred:e.bytesTransferred,duration:Date.now()-e.startTime}))}_startPacingLoop(t){let e=this.transfers.get(t);!e||e.pacingIntervalId||(e.pacingIntervalId=setInterval(()=>{this._pacingTick(t)},this.config.pacingIntervalMs),this._pacingTick(t))}_stopPacingLoop(t){let e=this.transfers.get(t);!e||!e.pacingIntervalId||(clearInterval(e.pacingIntervalId),e.pacingIntervalId=null)}_pacingTick(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.pendingReads===void 0&&(e.pendingReads=0),e.retryQueue.length>=this.config.maxRetryQueueSize){this._stopPacingLoop(t),this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let s=e.inFlightPieces.size;if(s>=this.config.maxInFlightPieces)return;let i=e.channel;if(!i||i.readyState!=="open")return;let r=i.bufferedAmount,n=0;if(r>this.config.maxBufferedAmount)return;r>this.config.targetBufferedAmount?n=32:r>this.config.minBufferedAmount?n=this.config.maxPiecesPerTick:n=this.config.burstPiecesPerTick;let o=this.config.maxPendingReads-e.pendingReads;if(o<=0)return;n=Math.min(n,o);let c=this.config.maxInFlightPieces-s;n=Math.min(n,c);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let u=e.retryQueue.shift();e.inFlightPieces.has(u)||l.push(u)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let u=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(u)||l.push(u)}if(l.length===0){e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.inFlightPieces.size===0&&e.pendingReads===0&&this._stopPacingLoop(t);return}e.pendingReads+=l.length;let f=Date.now();for(let u of l)e.inFlightPieces.set(u,f);this._readAndSendPieces(t,e,i,l)}async _readAndSendPieces(t,e,s,i){try{let r=i.map(o=>this._readPiece(e.file,e.manifest,o).then(c=>({pieceIndex:o,data:c})).catch(c=>({pieceIndex:o,error:c}))),n=await Promise.all(r);for(let o of n){if(o.error){e.inFlightPieces.delete(o.pieceIndex),e.retryQueue.push(o.pieceIndex);continue}let{pieceIndex:c,data:l}=o,f=e.channel;if(!f||f.readyState!=="open"){e.inFlightPieces.delete(c),e.retryQueue.push(c);continue}if(f.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(c),e.retryQueue.unshift(c);continue}try{f.send(this._encodePieceMessage(t,c,l)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,c)}catch{e.inFlightPieces.delete(c),e.retryQueue.unshift(c)}}}catch{for(let n of i)e.inFlightPieces.has(n)&&(e.inFlightPieces.delete(n),e.retryQueue.push(n))}finally{e.pendingReads-=i.length}}_requestNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;let s=this._getNextPiecesToRequest(e);for(let i of s)e.piecesRequested.add(i),e.requestPiece(i),this._startPieceTimeout(t,i)}_getNextPiecesToRequest(t){let e=this.config.maxConcurrentPieces-t.piecesRequested.size;if(e<=0)return[];let s=[];for(;t.nextPieceToRequest<t.manifest.totalPieces&&s.length<e;){let i=t.nextPieceToRequest;t.nextPieceToRequest++,!(i<=t.receivedWaterLevel||t.receivedOutOfOrder.has(i))&&!t.piecesRequested.has(i)&&s.push(i)}return s}_initReceiveBuffer(t,e){if(e.fileSize<=104857600)try{t.fileBuffer=new ArrayBuffer(e.fileSize),t.useChunkedBuffer=!1}catch{t.useChunkedBuffer=!0}else t.useChunkedBuffer=!0}_storePieceInMemory(t,e,s){if(t.useChunkedBuffer)t.pieceBuffers.set(e,new Uint8Array(s));else{let i=e*t.manifest.pieceSize;new Uint8Array(t.fileBuffer,i,s.byteLength).set(new Uint8Array(s))}}_assembleFile(t){let{manifest:e,fileBuffer:s,pieceBuffers:i,useChunkedBuffer:r}=t;if(r){let n=[];for(let c=0;c<e.totalPieces;c++){let l=i.get(c);if(!l)throw new Error(`Missing piece ${c}`);n.push(l)}let o=new Blob(n,{type:e.fileType||"application/octet-stream"});return i.clear(),o}else return new Blob([s],{type:e.fileType||"application/octet-stream"})}_createManifest(t){let e=this.config.pieceSize,s=Math.ceil(t.size/e);return{fileName:t.name,fileSize:t.size,fileType:t.type||"application/octet-stream",pieceSize:e,totalPieces:s,createdAt:Date.now()}}async _readPiece(t,e,s){let i=s*e.pieceSize,r=Math.min(i+e.pieceSize,t.size),n=await t.readSlice(i,r),o=new Uint8Array(4+n.byteLength);return o.set(new Uint8Array(n),4),o}_getPieceSize(t,e){return e===t.totalPieces-1?t.fileSize-e*t.pieceSize:t.pieceSize}_calculateProgress(t){return(t.type==="send"?t.piecesAckedCount:t.piecesReceivedCount)/t.manifest.totalPieces*100}_emitProgress(t,e){let s=Date.now();if(s-(e.lastProgressEmit||0)<100)return;let i=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||s,o=s-n;if(o>=200){let f=e.bytesTransferred-r;i=o>0?f/o*1e3:0,e.lastSpeedBytes=e.bytesTransferred,e.lastSpeedTime=s}else e.smoothedSpeed?i=e.smoothedSpeed:e.startTime&&s>e.startTime&&(i=e.bytesTransferred/(s-e.startTime)*1e3);i=Math.max(0,i),Number.isFinite(i)||(i=0);let c=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=i:i>0&&(e.smoothedSpeed=c*i+(1-c)*e.smoothedSpeed);let l=Math.round(e.smoothedSpeed);e.lastProgressEmit=s,this.emit("progress",{transferId:t,type:e.type,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),speed:l})}_encodePieceMessage(t,e,s){return new DataView(s.buffer).setUint32(0,e,!0),s.buffer}decodePieceMessage(t){let s=new DataView(t).getUint32(0,!0),i=t.slice(4);return{pieceIndex:s,data:i}}_checkProgressMilestones(t,e,s){let i=Date.now(),r=36e5,n=0;if(e.lastMilestoneCheck){let o=i-e.lastMilestoneCheck.time,c=e.bytesTransferred-e.lastMilestoneCheck.bytes;if(o>0&&o<r&&c>0)n=c/o*1e3/1e6;else if(o>=r){let l=e.startTime?i-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let o=e.startTime?i-e.startTime:0;n=o>0?e.bytesTransferred/o*1e3/1e6:0}e.lastMilestoneCheck={time:i,bytes:e.bytesTransferred},(!Number.isFinite(n)||n<0)&&(n=0),s>=90&&!e.milestone90&&(e.milestone90=!0,e.speedAt90Time=i,this.emit("progress-milestone",{transferId:t,milestone:90,speed:n})),s>=99&&!e.milestone99&&(e.milestone99=!0,this.emit("progress-milestone",{transferId:t,milestone:99,speed:n}))}_startPieceTimeout(t,e){let s=this.transfers.get(t);if(!s)return;let i=s.pieceTimeouts.get(e);i&&clearTimeout(i);let r=setTimeout(()=>{this._handlePieceTimeout(t,e)},this.config.pieceTimeout);s.pieceTimeouts.set(e,r)}_clearPieceTimeout(t,e){let s=this.transfers.get(t);if(!s)return;let i=s.pieceTimeouts.get(e);i&&(clearTimeout(i),s.pieceTimeouts.delete(e))}_handlePieceTimeout(t,e){let s=this.transfers.get(t);if(!s||s.state==="completed"||s.state==="completing")return;if(s.type==="receive"&&s.receivedOutOfOrder&&(e<=s.receivedWaterLevel||s.receivedOutOfOrder.has(e))){s.pieceTimeouts.delete(e);return}let i=Date.now()-s.lastActivityTime,r=this.config.pieceTimeout*.5,o=s.manifest.totalPieces-s.piecesReceivedCount<=10;if(i<r&&!o){this._startPieceTimeout(t,e);return}let c=s.pieceRetries.get(e)||0;c<this.config.maxPieceRetries?(s.pieceRetries.set(e,c+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:c+1}),s.type==="receive"?(s.piecesRequested.delete(e),s.piecesRequested.add(e),s.requestPiece(e),this._startPieceTimeout(t,e)):(s.inFlightPieces.delete(e),s.retryQueue.push(e),s.usePacing||this._sendNextPieces(t))):(s.inFlightPieces.delete(e),s.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}_startIdleTimeout(t){this._stopIdleTimeout(t);let e=this.transfers.get(t);e&&(e.idleTimeoutId=setTimeout(()=>{let s=this.transfers.get(t);if(!s||s.state==="completing"||s.state==="completed")return;let i=Date.now()-s.lastActivityTime;i>=this.config.idleTimeout?this.emit("idle-timeout",{transferId:t,timeSinceActivity:i}):this._startIdleTimeout(t)},this.config.idleTimeout))}_stopIdleTimeout(t){let e=this.transfers.get(t);!e||!e.idleTimeoutId||(clearTimeout(e.idleTimeoutId),e.idleTimeoutId=null)}_onBufferedAmountLow(t){this.transfers.forEach((e,s)=>{e.type==="send"&&!e.usePacing&&(e.channel===t.target||e.channels?.includes(t.target))&&this._sendNextPieces(s)})}_cleanupTransfer(t){let e=this.transfers.get(t);if(e){if(this._stopPacingLoop(t),e.pieceTimeouts.forEach(s=>clearTimeout(s)),e.pieceTimeouts.clear(),this._stopIdleTimeout(t),e.channels)for(let s of e.channels)s.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);else e.channel&&e.channel.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);e.pieceBuffers&&e.pieceBuffers.clear(),e.fileBuffer=null,this.rateLimits.delete(t),this.transfers.delete(t)}}_checkRateLimit(t){let e=Date.now(),s=this.rateLimits.get(t);return s||(s={count:0,windowStart:e},this.rateLimits.set(t,s)),e-s.windowStart>this.config.rateLimitWindowMs&&(s.count=0,s.windowStart=e),s.count++,{allowed:s.count<=this.config.maxPiecesPerSecond,currentRate:s.count,limit:this.config.maxPiecesPerSecond}}};var fe="0.4.2",I="https://perkoon.com",O=class extends qe{constructor(t=I,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new W,this.transport=null,this.signaling=null,this.fileSource=null}async send(t,e={}){this.fileSource=await ee(t),this.emit("file-ready",{name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type});let s=e.session?await this._joinSession(e.session,e.password,"sender",e.senderKey):await this._createSession(e.password,e.source);this.sessionCode=s.session_code,this.emit("session-created",{sessionCode:s.session_code,expiresAt:s.expires_at}),this.signaling=new z(this.serverUrl);let{peers:i}=await this.signaling.connect(s.session_code,s.token,s.peer_id,"sender");this.emit("waiting-for-receiver");let r=i.length>0?i[0]:await this._waitForPeer();this.emit("receiver-connected",{peerId:r});let n=`batch_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`,o=[{id:`file_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`,name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type}];this.signaling.sendFilesAdded(n,o,this.fileSource.size),this.emit("waiting-for-acceptance");let c=await this._waitForSignal("transfer_accepted",this.peerTimeout,w=>w.batch_id===n);this.emit("transfer-accepted",{batchId:c.batch_id});let l=`transfer_${Date.now()}_${Q.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new B,this.transport.initialize(s.peer_id,s.ice_servers);let f=await this.transport.createOffer(l);this.signaling.sendOffer(r,f),this.transport.on("ice-candidate",({candidate:w,mid:h})=>{this.signaling.sendIceCandidate(r,{candidate:w,sdpMid:h||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",w=>{let h=w.candidate;this.transport.addIceCandidate({candidate:h.candidate||h,mid:h.sdpMid||h.mid||"0"})});let u=await this._waitForSignal("answer");this.transport.handleAnswer(u.answer);let S=await this.transport.waitForChannel("control"),A=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,A);let F=this.engine.transfers.get(l).manifest;this._setupControlHandler(S,l);let v=JSON.stringify({type:"manifest",transfer_id:l,manifest:F,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});S.send(v),this._setupProgressEvents(l),await this.engine.startSend(l);let g=await this._waitForCompletion(l);return await this.fileSource.close(),this.signaling.disconnect(),this.transport.close(),{sessionCode:s.session_code,speed:g.speed,duration:g.duration}}async receive(t,e,s={}){let i=await this._joinSession(t,s.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new z(this.serverUrl);let{peers:r}=await this.signaling.connect(t,i.token,i.peer_id,"receiver");this.signaling.on("files_offered",g=>{this.emit("files-offered",{files:g.files,from:g.from}),this.signaling.channel.push("accept_transfer",{batch_id:g.batch_id})});let n=r.length>0?r[0]:await this._waitForPeer();this.emit("sender-found",{peerId:n}),this.signaling.channel.push("signal",{to:n,type:"ready",data:{role:"receiver"}}),this.transport=new B,this.transport.initialize(i.peer_id,i.ice_servers),this.transport.on("ice-candidate",({candidate:g,mid:w})=>{this.signaling.sendIceCandidate(n,{candidate:g,sdpMid:w||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",g=>{let w=g.candidate;this.transport.addIceCandidate({candidate:w.candidate||w,mid:w.sdpMid||w.mid||"0"})});let o=await this._waitForSignal("offer"),c=await this.transport.handleOffer(o.offer);this.signaling.sendAnswer(n,c);let l=await this.transport.waitForChannel("control"),{channel:f,label:u}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=s.stdout?ne():ie(e,{overwrite:s.overwrite});let S=this.fileSink,A=[],v=await new Promise((g,w)=>{let h=null;l.addEventListener("message",async C=>{if(typeof C.data=="string")try{let m=JSON.parse(C.data);if(m.type==="manifest"&&!h){h=m.transfer_id,this._activeTransferId=h;let _=m.manifest;this.emit("receiving-file",{name:_.fileName,size:_.fileSize}),await S.startStreamingReceive(h,{fileName:_.fileName,size:_.fileSize,fileSize:_.fileSize,type:_.fileType,pieceSize:_.pieceSize},{}),this.engine.initReceive(h,_,{streamingHandler:S}),this._setupProgressEvents(h),this.engine.startReceive(h,b=>{this._safeSend(l,JSON.stringify({type:"request",transfer_id:h,piece_index:b}))});let M=-1;this._ackInterval=setInterval(()=>{let b=this.engine.transfers.get(h);if(!b||b.state==="completed"||b.state==="completing"){clearInterval(this._ackInterval);return}let N=b.receivedWaterLevel;if(N>M){M=N;let D=b.receivedOutOfOrder.size>0?Array.from(b.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:h,wl:N,ooo:D}))}},100)}}catch(m){m.message&&!m.message.includes("Unexpected token")&&w(m)}}),f.addEventListener("message",async C=>{let m=C.data;if(!h||!(m instanceof ArrayBuffer||Buffer.isBuffer(m)))return;let _=m instanceof ArrayBuffer?m:m.buffer.slice(m.byteOffset,m.byteOffset+m.byteLength),{pieceIndex:M,data:b}=this.engine.decodePieceMessage(_);if(M>=0&&b&&(await this.engine.handlePieceData(h,M,b))?.complete){this._ackInterval&&clearInterval(this._ackInterval);let D=this.engine.transfers.get(h);D&&this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:h,wl:D.receivedWaterLevel}));let ye=S.getFilePath(h);A.push(ye),await this.engine.completeReceive(h),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:h}));let K=this.engine.transfers.get(h),Z=Date.now()-(K?.startTime||Date.now()),Y=K?.manifest;g({files:A,speed:Y?Y.fileSize/(Z/1e3):0,duration:Z})}})});return this.signaling.disconnect(),this.transport.close(),v}async _createSession(t,e="agent_cli"){let s=`${this.serverUrl}/api/v1/sessions`,i={client_version:fe,source:e};t&&(i.password=t);let r=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!r.ok){let n=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${n.error||r.statusText}`)}return r.json()}async _joinSession(t,e,s="receiver",i=void 0){let r=`${this.serverUrl}/api/v1/sessions/${t}/join`,n={role:s,client_version:fe};e&&(n.password=e),i&&(n.sender_key=i);let o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok){let c=await o.json().catch(()=>({}));throw o.status===404?new Error(`Session ${t} not found. Check the code and try again.`):o.status===410?new Error(`Session ${t} has expired.`):o.status===403?new Error(c.error||"Access denied"):new Error(`Failed to join session: ${c.error||o.statusText}`)}return o.json()}_waitForPeer(){return new Promise((t,e)=>{let s=setTimeout(()=>{i(),e(new Error(`No peer connected within ${Math.round(this.peerTimeout/1e3)}s`))},this.peerTimeout),i=()=>{clearTimeout(s),this.signaling.removeListener("ready",r),this.signaling.removeListener("offer",r),this.signaling.removeListener("peer_joined",r)},r=n=>{(n.type==="ready"||n.type==="offer"||n.type==="peer_joined"&&(!n.role||n.role==="receiver"))&&(i(),t(n.from))};this.signaling.on("ready",r),this.signaling.on("offer",r),this.signaling.on("peer_joined",r)})}_waitForSignal(t,e=3e4,s=null){return new Promise((i,r)=>{let n=setTimeout(()=>{this.signaling.removeListener(t,o),r(new Error(`Timed out waiting for ${t} signal`))},e),o=c=>{s&&!s(c)||(this.signaling.removeListener(t,o),clearTimeout(n),i(c))};this.signaling.on(t,o)})}_setupProgressEvents(t){let e=setInterval(()=>{let s=this.engine.transfers.get(t);if(!s){clearInterval(e);return}let i=s.manifest,r=i.totalPieces,n;s.type==="send"?n=s.piecesAckedCount||0:n=s.piecesReceivedCount||0;let o=r>0?Math.round(n/r*100):0,c=Date.now()-(s.startTime||Date.now()),l=n*i.pieceSize,f=c>0?l/(c/1e3):0,u=f>0?Math.round((i.fileSize-l)/f):0;this.emit("progress",{transferId:t,percent:Math.min(o,100),speed:f,eta:u,bytesTransferred:l,totalBytes:i.fileSize}),(s.state==="completed"||s.state==="cancelled")&&clearInterval(e)},200)}_setupControlHandler(t,e){t.addEventListener("message",s=>{if(typeof s.data=="string")try{let i=JSON.parse(s.data);switch(i.type){case"ack":this.engine.handlePieceAck(e,i.piece_index);break;case"ack_wl":this.engine.handleWaterLevelAck(e,i.wl,i.ooo);break;case"water_level_ack":this.engine.handleWaterLevelAck(e,i.water_level,i.out_of_order);break;case"ack_batch":Array.isArray(i.pieces)&&i.pieces.length>0&&this.engine.handlePieceAckBatch(e,i.pieces);break;case"request":this.engine.requeuePiece(e,i.piece_index);break;case"complete":{let r=this.engine.transfers.get(e);r&&r.state!=="completed"&&(r.piecesAckedCount=r.manifest.totalPieces,r.bytesTransferred=r.manifest.fileSize,r.inFlightPieces.clear(),r.state="completed")}this.emit("transfer-complete",{transferId:e});break;case"backpressure":i.pause?this.engine.pause(e):this.engine.resume(e);break}}catch{}})}_waitForCompletion(t){return new Promise((e,s)=>{let i=()=>{let n=this.engine.transfers.get(t);if(!n){clearInterval(r),s(new Error("Transfer context lost"));return}if(n.state==="completed"){clearInterval(r);let o=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(o/1e3),duration:o});return}if(n.state==="cancelled"||n.state==="failed"){clearInterval(r),s(new Error("Transfer failed"));return}},r=setInterval(()=>{i()},500);this.on("transfer-complete",({transferId:n})=>{n===t&&i()})})}_safeSend(t,e){try{t.readyState==="open"&&t.send(e)}catch{}}destroy(){this._ackInterval&&clearInterval(this._ackInterval),this.fileSink&&this._activeTransferId&&(this.fileSink.abort(this._activeTransferId).catch(()=>{}),this.fileSink=null),this.fileSource&&this.fileSource.close().catch(()=>{}),this.signaling&&this.signaling.disconnect(),this.transport&&this.transport.close()}};import je from"node:path";import{access as He,stat as Qe}from"node:fs/promises";var ue="0.4.2",pe=0,E=1,he=2,G=3,Ge=4,Je=5,k=process.argv.slice(2),de=k[0],p={};for(let a=1;a<k.length;a++)if(k[a].startsWith("--")){let t=k[a].slice(2);k[a+1]&&!k[a+1].startsWith("--")?p[t]=k[++a]:p[t]=!0}else p._positional||(p._positional=k[a]);var R=(typeof p.server=="string"?p.server:null)||process.env.PERKOON_URL||I,L=p.json===!0,x=p.quiet===!0,Ve=p.overwrite===!0,V=p.output||"./received",$=typeof p.password=="string"?p.password:void 0,Xe=typeof p.session=="string"?p.session:void 0,Ke=typeof p["sender-key"]=="string"?p["sender-key"]:void 0,Ze=typeof p.source=="string"?p.source:void 0,Ye=Ze||process.env.PERKOON_SOURCE||"agent_cli",J=typeof p.timeout=="string"?parseInt(p.timeout,10):NaN,X=!isNaN(J)&&J>0?J:300;L&&V==="-"&&(process.stderr.write(` Error: --json and --output - are mutually exclusive
3
+ `),process.exit(E));function d(a){!x&&!L&&process.stderr.write(a+`
4
4
  `)}function y(a,t={}){L&&process.stdout.write(JSON.stringify({event:a,...t})+`
5
5
  `)}function P(a){d(` \u2713 ${a}`)}function q(a){return a>=1024*1024*1024?`${(a/(1024*1024*1024)).toFixed(1)} GB`:a>=1024*1024?`${(a/(1024*1024)).toFixed(1)} MB`:a>=1024?`${(a/1024).toFixed(0)} KB`:`${a} B`}function j(a){return q(a)+"/s"}function me(a){return a<=0?"...":a<60?`${a}s`:`${Math.floor(a/60)}m ${a%60}s`}function ge(a,t=30){let e=Math.round(t*a/100);return"\u2588".repeat(e)+"\u2591".repeat(t-e)}function T(a,t){d(`
6
6
  Error: ${a}`),t&&d(`
7
- ${t}`),d("")}function we(a){let t=a.message||"";return t.includes("No peer connected")||t.includes("timed out")?Ge:t.includes("Password required")||t.includes("Invalid password")||t.includes("Access denied")?Qe:(t.includes("Failed to create session")||t.includes("Failed to join")||t.includes("not found")||t.includes("expired"),G)}async function Ye(){let a=p._positional;a||(T("No file specified.","Usage: perkoon send <file>"),process.exit(R));let t=qe.resolve(a);try{await je(t),(await He(t)).isFile()||(T(`Not a regular file: ${t}`),process.exit(he))}catch{T(`File not found: ${t}`),process.exit(he)}let e=new $(E,{timeout:K*1e3});e.on("file-ready",({name:r,size:n})=>{d(""),P(`${r} (${q(n)})`),y("file_ready",{name:r,size:n})}),e.on("session-created",({sessionCode:r})=>{P(`Code: ${r}`),O&&P("Password protected"),d("");let n=O?` --password ${O}`:"";d(` Receiver command: perkoon receive ${r}${n}`),d(` Or open in browser: ${E}/${r}`),d(""),y("session_created",{session_code:r,share_url:`${E}/${r}`})}),e.on("waiting-for-receiver",()=>{d(" Waiting for receiver..."),y("waiting_for_receiver")}),e.on("receiver-connected",()=>{P("Receiver connected"),y("receiver_connected")}),e.on("waiting-for-acceptance",()=>{d(" Waiting for receiver to accept..."),y("waiting_for_acceptance")}),e.on("transfer-accepted",()=>{P("Transfer accepted"),y("transfer_accepted")}),e.on("connected",()=>{P("Direct connection established"),d(""),y("webrtc_connected")});let s="";e.on("progress",({percent:r,speed:n,eta:o,bytesTransferred:c})=>{if(L)y("progress",{percent:r,speed:Math.round(n),eta:o,bytes_transferred:c});else if(!x){let l=` ${ge(r)} ${String(r).padStart(3)}% ${j(n).padStart(10)} ETA ${me(o)}`;l!==s&&(process.stderr.write(`\r${l}`),s=l)}});let i=()=>{d(`
7
+ ${t}`),d("")}function we(a){let t=a.message||"";return t.includes("No peer connected")||t.includes("timed out")?Je:t.includes("Password required")||t.includes("Invalid password")||t.includes("Access denied")?Ge:(t.includes("Failed to create session")||t.includes("Failed to join")||t.includes("not found")||t.includes("expired"),G)}async function et(){let a=p._positional;a||(T("No file specified.","Usage: perkoon send <file>"),process.exit(E));let t=je.resolve(a);try{await He(t),(await Qe(t)).isFile()||(T(`Not a regular file: ${t}`),process.exit(he))}catch{T(`File not found: ${t}`),process.exit(he)}let e=new O(R,{timeout:X*1e3});e.on("file-ready",({name:r,size:n})=>{d(""),P(`${r} (${q(n)})`),y("file_ready",{name:r,size:n})}),e.on("session-created",({sessionCode:r})=>{P(`Code: ${r}`),$&&P("Password protected"),d("");let n=$?` --password ${$}`:"";d(` Receiver command: perkoon receive ${r}${n}`),d(` Or open in browser: ${R}/${r}`),d(""),y("session_created",{session_code:r,share_url:`${R}/${r}`})}),e.on("waiting-for-receiver",()=>{d(" Waiting for receiver..."),y("waiting_for_receiver")}),e.on("receiver-connected",()=>{P("Receiver connected"),y("receiver_connected")}),e.on("waiting-for-acceptance",()=>{d(" Waiting for receiver to accept..."),y("waiting_for_acceptance")}),e.on("transfer-accepted",()=>{P("Transfer accepted"),y("transfer_accepted")}),e.on("connected",()=>{P("Direct connection established"),d(""),y("webrtc_connected")});let s="";e.on("progress",({percent:r,speed:n,eta:o,bytesTransferred:c})=>{if(L)y("progress",{percent:r,speed:Math.round(n),eta:o,bytes_transferred:c});else if(!x){let l=` ${ge(r)} ${String(r).padStart(3)}% ${j(n).padStart(10)} ETA ${me(o)}`;l!==s&&(process.stderr.write(`\r${l}`),s=l)}});let i=()=>{d(`
8
8
 
9
- Cancelled.`),e.destroy(),process.exit(R)};process.on("SIGINT",i),process.on("SIGTERM",i);try{let r=await e.send(t,{password:O,session:Xe,senderKey:Ke,source:Ze});if(!x&&!L){process.stderr.write(`
10
- `);let n=q(r.speed*(r.duration/1e3));P(`Complete: ${n} in ${(r.duration/1e3).toFixed(1)}s (${j(r.speed)})`),d(""),d(` Tip: Send files from your browser at ${E}`),d("")}y("transfer_complete",{session_code:r.sessionCode,duration_ms:r.duration,speed:Math.round(r.speed)}),process.exit(pe)}catch(r){let n=we(r);r.message.includes("No peer connected")?T(`No receiver joined after ${K}s.`,`Make sure they entered the right code.
11
- Or share the link: ${E}/${e.sessionCode||""}`):r.message.includes("outdated")||r.message.includes("npm install")?T(r.message.replace("Failed to create session: ","")):r.message.includes("Failed to create session")?T("Could not reach perkoon.com","Check your internet connection and try again."):T(r.message),y("error",{message:r.message,exit_code:n}),e.destroy(),process.exit(n)}}async function et(){let a=p._positional;a||(T("No session code specified.","Usage: perkoon receive <code>"),process.exit(R)),/^[A-Za-z0-9]{12}$/i.test(a)||(T(`Invalid code: ${a}`,"Codes are 12 alphanumeric characters, like K7MX4QPR9W2N."),process.exit(R));let t=X==="-",e=a.toUpperCase(),s=new $(E,{timeout:K*1e3});s.on("session-joined",()=>{d(""),P(`Joined session ${e}`),y("session_joined",{session_code:e})}),s.on("sender-found",()=>{P("Sender found"),y("sender_found")}),s.on("connected",()=>{P("Direct connection established"),y("webrtc_connected")}),s.on("receiving-file",({name:n,size:o})=>{P(`Receiving: ${n} (${q(o)})`),d(""),y("receiving_file",{name:n,size:o})});let i="";s.on("progress",({percent:n,speed:o,eta:c,bytesTransferred:l})=>{if(L)y("progress",{percent:n,speed:Math.round(o),eta:c,bytes_transferred:l});else if(!x){let f=` ${ge(n)} ${String(n).padStart(3)}% ${j(o).padStart(10)} ETA ${me(c)}`;f!==i&&(process.stderr.write(`\r${f}`),i=f)}});let r=()=>{d(`
9
+ Cancelled.`),e.destroy(),process.exit(E)};process.on("SIGINT",i),process.on("SIGTERM",i);try{let r=await e.send(t,{password:$,session:Xe,senderKey:Ke,source:Ye});if(!x&&!L){process.stderr.write(`
10
+ `);let n=q(r.speed*(r.duration/1e3));P(`Complete: ${n} in ${(r.duration/1e3).toFixed(1)}s (${j(r.speed)})`),d(""),d(` Tip: Send files from your browser at ${R}`),d("")}y("transfer_complete",{session_code:r.sessionCode,duration_ms:r.duration,speed:Math.round(r.speed)}),process.exit(pe)}catch(r){let n=we(r);r.message.includes("No peer connected")?T(`No receiver joined after ${X}s.`,`Make sure they entered the right code.
11
+ Or share the link: ${R}/${e.sessionCode||""}`):r.message.includes("outdated")||r.message.includes("npm install")?T(r.message.replace("Failed to create session: ","")):r.message.includes("Failed to create session")?T("Could not reach perkoon.com","Check your internet connection and try again."):T(r.message),y("error",{message:r.message,exit_code:n}),e.destroy(),process.exit(n)}}async function tt(){let a=p._positional;a||(T("No session code specified.","Usage: perkoon receive <code>"),process.exit(E)),/^[A-Za-z0-9]{12}$/i.test(a)||(T(`Invalid code: ${a}`,"Codes are 12 alphanumeric characters, like K7MX4QPR9W2N."),process.exit(E));let t=V==="-",e=a.toUpperCase(),s=new O(R,{timeout:X*1e3});s.on("session-joined",()=>{d(""),P(`Joined session ${e}`),y("session_joined",{session_code:e})}),s.on("sender-found",()=>{P("Sender found"),y("sender_found")}),s.on("connected",()=>{P("Direct connection established"),y("webrtc_connected")}),s.on("receiving-file",({name:n,size:o})=>{P(`Receiving: ${n} (${q(o)})`),d(""),y("receiving_file",{name:n,size:o})});let i="";s.on("progress",({percent:n,speed:o,eta:c,bytesTransferred:l})=>{if(L)y("progress",{percent:n,speed:Math.round(o),eta:c,bytes_transferred:l});else if(!x){let f=` ${ge(n)} ${String(n).padStart(3)}% ${j(o).padStart(10)} ETA ${me(c)}`;f!==i&&(process.stderr.write(`\r${f}`),i=f)}});let r=()=>{d(`
12
12
 
13
- Cancelled.`),s.destroy(),process.exit(R)};process.on("SIGINT",r),process.on("SIGTERM",r);try{let n={overwrite:Je,password:O};t&&(n.stdout=!0);let o=await s.receive(e,t?null:X,n);if(!x&&!L){if(process.stderr.write(`
14
- `),!t)for(let c of o.files)P(`Saved: ${c}`);P(`Complete: ${(o.duration/1e3).toFixed(1)}s (${j(o.speed)})`),d(""),d(` Tip: Send files from your browser at ${E}`),d("")}y("transfer_complete",{files:o.files,duration_ms:o.duration,speed:Math.round(o.speed)}),process.exit(pe)}catch(n){let o=we(n);n.message.includes("not found")?T(`Session not found (${e})`,`The code may be expired or mistyped.
15
- Ask the sender for a new code.`):n.message.includes("expired")?T(`Session expired (${e})`,"Ask the sender to create a new session."):T(n.message),y("error",{message:n.message,exit_code:o}),s.destroy(),process.exit(o)}}switch(de){case"send":Ye();break;case"receive":et();break;case"--version":case"-v":console.log(`perkoon v${ue}`);break;case"--help":case"-h":case void 0:console.log(`
13
+ Cancelled.`),s.destroy(),process.exit(E)};process.on("SIGINT",r),process.on("SIGTERM",r);try{let n={overwrite:Ve,password:$};t&&(n.stdout=!0);let o=await s.receive(e,t?null:V,n);if(!x&&!L){if(process.stderr.write(`
14
+ `),!t)for(let c of o.files)P(`Saved: ${c}`);P(`Complete: ${(o.duration/1e3).toFixed(1)}s (${j(o.speed)})`),d(""),d(` Tip: Send files from your browser at ${R}`),d("")}y("transfer_complete",{files:o.files,duration_ms:o.duration,speed:Math.round(o.speed)}),process.exit(pe)}catch(n){let o=we(n);n.message.includes("not found")?T(`Session not found (${e})`,`The code may be expired or mistyped.
15
+ Ask the sender for a new code.`):n.message.includes("expired")?T(`Session expired (${e})`,"Ask the sender to create a new session."):T(n.message),y("error",{message:n.message,exit_code:o}),s.destroy(),process.exit(o)}}switch(de){case"send":et();break;case"receive":tt();break;case"--version":case"-v":console.log(`perkoon v${ue}`);break;case"--help":case"-h":case void 0:console.log(`
16
16
  perkoon v${ue} \u2014 File transfer for humans and the things replacing them.
17
17
 
18
18
  Usage:
@@ -43,4 +43,4 @@ import{EventEmitter as Ie}from"node:events";import Q from"node:crypto";import _e
43
43
  5 Timeout (no peer connected)
44
44
 
45
45
  Learn more: ${I}
46
- `);break;default:T(`Unknown command: ${de}`,"Run perkoon --help for usage."),process.exit(R)}
46
+ `);break;default:T(`Unknown command: ${de}`,"Run perkoon --help for usage."),process.exit(E)}
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{EventEmitter as we}from"node:events";import F from"node:crypto";import J from"node-datachannel";import{EventEmitter as V}from"node:events";var{PeerConnection:X}=J,v=class extends V{constructor(){super(),this.pc=null,this.channels=new Map,this.remoteDescriptionSet=!1,this.pendingCandidates=[]}initialize(t,e){let i={iceServers:e.map(s=>(Array.isArray(s.urls)?s.urls:[s.urls]).map(n=>s.username&&s.credential?`${n}:${s.username}:${s.credential}`:n).join(",")),maxMessageSize:262144};this.pc=new X(t,i),this.pc.onLocalCandidate((s,r)=>{this.emit("ice-candidate",{candidate:s,mid:r})}),this.pc.onStateChange(s=>{this.emit("connection-state",s)}),this.pc.onGatheringStateChange(s=>{this.emit("gathering-state",s)}),this.pc.onDataChannel(s=>{let r=this._wrapChannel(s),n=s.getLabel();this.channels.set(n,r),this.emit("data-channel",r,n)})}async createOffer(t){return new Promise(e=>{this.pc.onLocalDescription((n,a)=>{e({sdp:n,type:a})});let i=this.pc.createDataChannel("control",{ordered:!0});this.channels.set("control",this._wrapChannel(i));let s=`transfer-${t}`,r=this.pc.createDataChannel(s,{ordered:!0,maxRetransmits:3});this.channels.set(s,this._wrapChannel(r))})}async handleOffer(t){return new Promise(e=>{this.pc.onLocalDescription((i,s)=>{e({sdp:i,type:s})}),this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()})}handleAnswer(t){this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()}addIceCandidate(t){t.candidate&&(this.remoteDescriptionSet?this.pc.addRemoteCandidate(t.candidate,t.mid||"0"):this.pendingCandidates.push(t))}_flushPendingCandidates(){for(let t of this.pendingCandidates)this.pc.addRemoteCandidate(t.candidate,t.mid||"0");this.pendingCandidates=[]}getChannel(t){return this.channels.get(t)||null}async waitForChannel(t,e=3e4){let i=this.channels.get(t);return i?.readyState==="open"?i:new Promise((s,r)=>{let n=setTimeout(()=>{r(new Error(`Data channel "${t}" open timeout`))},e),a=o=>{o.readyState==="open"?(clearTimeout(n),s(o)):o.addEventListener("open",()=>{clearTimeout(n),s(o)})};if(i)a(i);else{let o=(l,f)=>{f===t&&(this.removeListener("data-channel",o),a(l))};this.on("data-channel",o)}})}async waitForChannelByPrefix(t,e=3e4){for(let[i,s]of this.channels)if(i.startsWith(t))return s.readyState==="open"?{channel:s,label:i}:new Promise((r,n)=>{let a=setTimeout(()=>n(new Error(`Channel "${i}" open timeout`)),e);s.addEventListener("open",()=>{clearTimeout(a),r({channel:s,label:i})})});return new Promise((i,s)=>{let r=setTimeout(()=>{this.removeListener("data-channel",n),s(new Error(`No channel with prefix "${t}" within ${e}ms`))},e),n=(a,o)=>{o.startsWith(t)&&(this.removeListener("data-channel",n),a.readyState==="open"?(clearTimeout(r),i({channel:a,label:o})):a.addEventListener("open",()=>{clearTimeout(r),i({channel:a,label:o})}))};this.on("data-channel",n)})}close(){for(let[,t]of this.channels)try{t.close()}catch{}if(this.channels.clear(),this.pc){try{this.pc.close()}catch{}this.pc=null}}_wrapChannel(t){let e=new Map,i="connecting";try{t.isOpen&&t.isOpen()&&(i="open")}catch{}let s={get readyState(){return i},get bufferedAmount(){try{return t.bufferedAmount()||0}catch{return 0}},send(n){n instanceof ArrayBuffer?t.sendMessageBinary(Buffer.from(n)):n instanceof Uint8Array?t.sendMessageBinary(Buffer.from(n.buffer,n.byteOffset,n.byteLength)):Buffer.isBuffer(n)?t.sendMessageBinary(n):t.sendMessage(String(n))},close(){i="closed";try{t.close()}catch{}},addEventListener(n,a){e.has(n)||e.set(n,new Set),e.get(n).add(a)},removeEventListener(n,a){let o=e.get(n);o&&o.delete(a)},set onmessage(n){s.addEventListener("message",n)},set onopen(n){s.addEventListener("open",n)},set onclose(n){s.addEventListener("close",n)},set onerror(n){s.addEventListener("error",n)}};t.onOpen(()=>{i="open",r("open",{})}),t.onClosed(()=>{i="closed",r("close",{})}),t.onError(n=>{r("error",{error:n})}),t.onMessage(n=>{let a;Buffer.isBuffer(n)?a=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):a=n,r("message",{data:a})});function r(n,a){let o=e.get(n);if(o)for(let l of o)try{l(a)}catch{}}return s}};import{Socket as Z}from"phoenix";import K from"ws";import{EventEmitter as Y}from"node:events";var T=class extends Y{constructor(t){super(),this.serverUrl=t.replace(/\/$/,""),this.socket=null,this.channel=null,this.connected=!1,this.heartbeatInterval=null,this.peers=[]}async connect(t,e,i,s){let r=this.serverUrl.replace(/^https:/,"wss:").replace(/^http:/,"ws:")+"/socket";return new Promise((n,a)=>{this.socket=new Z(r,{params:{},transport:K,timeout:15e3,reconnectAfterMs:()=>36e5}),this.socket.onError(o=>{this.connected||a(new Error(`Signaling connection failed: ${o?.message||"unknown error"}`))}),this.socket.connect(),this.channel=this.socket.channel(`p2p:${t}`,{token:e,peer_id:i,role:s}),this.channel.on("message",o=>{this.emit(o.type,o)}),this.channel.onError(()=>{this.connected=!1}),this.channel.onClose(()=>{this.connected=!1,this._stopHeartbeat(),this.emit("disconnected")}),this.channel.join().receive("ok",o=>{this.connected=!0,this.peers=o.peers||[],this._startHeartbeat(),n({peers:this.peers})}).receive("error",o=>{a(new Error(`Channel join failed: ${o.reason||"unknown"}`))}).receive("timeout",()=>{a(new Error("Channel join timed out"))})})}sendOffer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("offer",{to:t,offer:e})}sendAnswer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("answer",{to:t,answer:e})}sendIceCandidate(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("ice_candidate",{to:t,candidate:e})}sendFilesAdded(t,e,i){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("files_added",{batch_id:t,files:e,total_size:i})}disconnect(){if(this._stopHeartbeat(),this.channel){try{this.channel.leave()}catch{}this.channel=null}if(this.socket){try{this.socket.disconnect()}catch{}this.socket=null}this.connected=!1}_startHeartbeat(){this._stopHeartbeat(),this.heartbeatInterval=setInterval(()=>{this.connected&&this.channel&&this.channel.push("heartbeat",{})},1e4)}_stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}};import{open as ee}from"node:fs/promises";import te from"node:path";import{lookup as ie}from"mime-types";async function E(c){let t=await ee(c,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${c}`);return{name:te.basename(c),size:e.size,type:ie(c)||"application/octet-stream",async readSlice(i,s){let r=s-i,n=Buffer.alloc(r),{bytesRead:a}=await t.read(n,0,r,i);return a===r?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n.buffer.slice(n.byteOffset,n.byteOffset+a)},async close(){await t.close()}}}import{open as se,mkdir as ne,access as N}from"node:fs/promises";import $ from"node:path";function B(c,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await ne(c,{recursive:!0});let n=ae(s.fileName),a=$.join(c,n);t.overwrite||(a=await re(a));let o=await se(a,"w");s.size>0&&await o.truncate(s.size),e.set(i,{fh:o,filePath:a,pieceSize:s.pieceSize,size:s.size,fileName:s.fileName})},async writeChunk(i,s,r){let n=e.get(i);if(!n)return{success:!1,error:"NO_HANDLE"};let a=s*n.pieceSize,o=Buffer.isBuffer(r)?r:Buffer.from(r);try{return await n.fh.write(o,0,o.byteLength,a),{success:!0}}catch(l){return{success:!1,error:l.message}}},async completeStream(i){let s=e.get(i);s&&(await s.fh.datasync(),await s.fh.close(),e.delete(i))},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(i){return e.get(i)?.filePath},async abort(i){let s=e.get(i);if(s){try{await s.fh.close()}catch{}e.delete(i);try{let{unlink:r}=await import("node:fs/promises");await r(s.filePath)}catch{}}}}}async function re(c){try{await N(c)}catch{return c}let t=$.extname(c),e=c.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await N(s)}catch{return s}}return`${e}_${Date.now()}${t}`}function ae(c){return c.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function U(){let c=new Map;return{async startStreamingReceive(t,e,i){c.set(t,{pieceSize:e.pieceSize,totalPieces:Math.ceil(e.size/e.pieceSize),size:e.size,nextPiece:0,buffer:new Map,bytesWritten:0})},async writeChunk(t,e,i){let s=c.get(t);if(!s)return{success:!1,error:"NO_HANDLE"};let r=Buffer.isBuffer(i)?i:Buffer.from(i);for(s.buffer.set(e,r);s.buffer.has(s.nextPiece);){let n=s.buffer.get(s.nextPiece);s.buffer.delete(s.nextPiece);let a=s.nextPiece*s.pieceSize,o=s.size-a,l=o<n.byteLength?n.subarray(0,o):n;process.stdout.write(l),s.bytesWritten+=l.byteLength,s.nextPiece++}return{success:!0}},async completeStream(t){c.delete(t)},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(t){return"stdout"},async abort(t){c.delete(t)}}}var A=class{constructor(){this._listeners=new Map,this._onceListeners=new Map}on(t,e){return this._listeners.has(t)||this._listeners.set(t,new Set),this._listeners.get(t).add(e),()=>this.off(t,e)}once(t,e){return this._onceListeners.has(t)||this._onceListeners.set(t,new Set),this._onceListeners.get(t).add(e),()=>{let i=this._onceListeners.get(t);i&&i.delete(e)}}off(t,e){let i=this._listeners.get(t);i&&i.delete(e);let s=this._onceListeners.get(t);s&&s.delete(e)}removeAllListeners(t){t?(this._listeners.delete(t),this._onceListeners.delete(t)):(this._listeners.clear(),this._onceListeners.clear())}emit(t,e){let i=this._listeners.get(t);i&&i.forEach(r=>{try{r(e)}catch{}});let s=this._onceListeners.get(t);if(s){let r=[...s];this._onceListeners.delete(t),r.forEach(n=>{try{n(e)}catch{}})}}listenerCount(t){let e=this._listeners.get(t)?.size||0,i=this._onceListeners.get(t)?.size||0;return e+i}hasListeners(t){return this.listenerCount(t)>0}eventNames(){return[...new Set([...this._listeners.keys(),...this._onceListeners.keys()])]}waitFor(t,e){return new Promise((i,s)=>{let r,n=a=>{r&&clearTimeout(r),i(a)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),s(new Error(`Timeout waiting for event: ${t}`))},e))})}};function oe(){let c=ce();return{browser:c.browser,version:c.version,platform:le(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:he(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:ue(c.browser),recommendedChunkSize:de(c.browser),maxFileSize:pe(c.browser),maxFileSizeLabel:me(c.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:fe(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function ce(){let c=navigator.userAgent,t="unknown",e="0";if(c.includes("Edg/")){t="edge";let i=c.match(/Edg\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Chrome/")){t="chrome";let i=c.match(/Chrome\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Firefox/")){t="firefox";let i=c.match(/Firefox\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Safari/")&&!c.includes("Chrome")){t="safari";let i=c.match(/Version\/(\d+)/);e=i?i[1]:"0"}return{browser:t,version:parseInt(e,10)}}function le(){let c=navigator.userAgent;return c.includes("Windows")?"windows":c.includes("Mac")?"macos":c.includes("Linux")?"linux":c.includes("Android")?"android":c.includes("iPhone")||c.includes("iPad")?"ios":"unknown"}function he(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function fe(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function ue(c){switch(c){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function de(c){switch(c){case"chrome":case"edge":return 256*1024;case"firefox":return 64*1024;case"safari":return 128*1024;default:return 64*1024}}function pe(c){switch(c){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return 1/0;case"safari":return 4*1024*1024*1024;default:return 2*1024*1024*1024}}function me(c){switch(c){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return"Unlimited";case"safari":return"4GB";default:return"2GB"}}var z=null;function W(){return z||(z=oe()),z}var q={chrome:1/0,edge:1/0,brave:1/0,opera:1/0,vivaldi:1/0,samsung:1/0,ucbrowser:1/0,firefox:1/0,safari:4294967296,ios:4294967296,unknown:2147483648},H={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function Q(c){return q[c]||q.unknown}function j(c){return H[c]||H.unknown}var ge={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxPieceRetries:3,maxRetryQueueSize:1e4,maxInFlightMultiplier:10,highWaterMark:16*1024*1024,lowWaterMark:4*1024*1024,maxPiecesPerSecond:5e3,rateLimitWindowMs:1e3,channelRotationThreshold:512*1024*1024,channelRotationEnabled:!1,parallelChannels:1,parallelChannelsEnabled:!1,parallelChannelsThreshold:100*1024*1024,pacingEnabled:!0,pacingThreshold:0,pacingIntervalMs:10,targetBufferedAmount:8*1024*1024,minBufferedAmount:4*1024*1024,maxBufferedAmount:16*1024*1024,maxPiecesPerTick:32,burstPiecesPerTick:64,maxPendingReads:512,maxInFlightPieces:4e3},R=class extends A{constructor(t={}){super(),this.config={...ge,...t},this.transfers=new Map,this.rateLimits=new Map,this._onBufferedAmountLow=this._onBufferedAmountLow.bind(this)}initSend(t,e,i){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let s=this._createManifest(e),r=this.config.parallelChannelsEnabled&&e.size>=this.config.parallelChannelsThreshold,n={id:t,type:"send",file:e,manifest:s,channel:i,channels:[i],channelIndex:0,useParallelChannels:r,state:"initialized",nextPieceToSend:0,piecesAckedCount:0,bytesTransferred:0,paused:!1,inFlightPieces:new Map,retryQueue:[],channelBytes:[0],channelThresholds:[this.config.channelRotationThreshold],channelRotationCounts:[0],pendingRotations:new Set,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,idleTimeoutId:null,pieceTimeouts:new Map,pieceRetries:new Map};return this.transfers.set(t,n),{transferId:t,manifest:s}}async startSend(t){let e=this.transfers.get(t);if(!e||e.type!=="send")throw new Error(`Send transfer not found: ${t}`);if(e.state==="transferring")return;e.state="transferring",e.startTime=e.startTime||Date.now();let i=this.config.pacingEnabled&&e.manifest.fileSize>=this.config.pacingThreshold;e.usePacing=i;for(let s of e.channels)s.addEventListener("bufferedamountlow",this._onBufferedAmountLow);e.useParallelChannels&&e.channels.length<this.config.parallelChannels&&this.emit("parallel-channels-needed",{transferId:t,currentCount:e.channels.length,targetCount:this.config.parallelChannels}),this._startIdleTimeout(t),i?this._startPacingLoop(t):await this._sendNextPieces(t)}addChannel(t,e){let i=this.transfers.get(t);if(!i||i.type!=="send")return;let s=i.channels.length;i.channels.push(e),e.addEventListener("bufferedamountlow",this._onBufferedAmountLow),i.channelBytes.push(0),i.channelRotationCounts.push(0),i.channelThresholds||(i.channelThresholds=[this.config.channelRotationThreshold]);let r=this.config.channelRotationThreshold*(1+s*.25);i.channelThresholds.push(r),!i.paused&&i.state==="transferring"&&this._sendNextPieces(t)}handlePieceAck(t,e){let i=this.transfers.get(t);if(!i||i.type!=="send")return;let s=i.inFlightPieces.get(e);if(!s)return;let r=s.channelIndex>=0?s.channelIndex:0;i.inFlightPieces.delete(e),i.lastActivityTime=Date.now(),i.piecesAckedCount++;let n=this._getPieceSize(i.manifest,e);i.bytesTransferred+=n,i.channelBytes[r]!==void 0&&(i.channelBytes[r]+=n),this._checkChannelRotation(t,i,r);let a=this._calculateProgress(i);if(this._checkProgressMilestones(t,i,a),this._emitProgress(t,i),i.piecesAckedCount===i.manifest.totalPieces){this._completeSend(t);return}!i.paused&&!i.usePacing&&this._sendNextPieces(t)}handlePieceAckBatch(t,e){if(Array.isArray(e))for(let i of e)this.handlePieceAck(t,i)}handleWaterLevelAck(t,e,i){let s=this.transfers.get(t);if(!(!s||s.type!=="send")){if(typeof e=="number"&&e>=0)for(let r=0;r<=e;r++)this.handlePieceAck(t,r);if(Array.isArray(i))for(let r of i)this.handlePieceAck(t,r)}}requeuePiece(t,e){let i=this.transfers.get(t);!i||i.type!=="send"||i.state==="completed"||i.state==="completing"||(i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t))}markSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||this._completeSend(t)}_checkChannelRotation(t,e,i){if(!this.config.channelRotationEnabled||e.pendingRotations.has(i))return;let s=e.channelBytes[i]||0,r=e.channelThresholds?.[i]||this.config.channelRotationThreshold;s>=r&&(e.pendingRotations.add(i),this.emit("channel-rotation-needed",{transferId:t,channelIndex:i,bytesTransferred:e.bytesTransferred,channelBytes:s,threshold:r,rotationCount:e.channelRotationCounts[i]||0}))}rotateSingleChannel(t,e,i){let s=this.transfers.get(t);if(!s||s.type!=="send"||e>=s.channels.length)return;let r=s.channels[e],n=(s.channelRotationCounts[e]||0)+1;s.channels[e]=i,e===0&&(s.channel=i),s.channelBytes[e]=0,s.channelRotationCounts[e]=n,s.pendingRotations.delete(e),i.addEventListener("bufferedamountlow",this._onBufferedAmountLow),r&&r.readyState==="open"&&this._drainAndCloseChannel(r,e,n),!s.paused&&!s.usePacing&&this._sendNextPieces(t)}_drainAndCloseChannel(t,e,i){let s=()=>{if(t.readyState==="open"){if(t.bufferedAmount===0){try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}return}setTimeout(s,50)}};s(),setTimeout(()=>{if(t.readyState==="open")try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}},5e3)}rotateChannel(t,e){this.rotateChannels(t,[e])}initReceive(t,e,i={}){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let s=W(),r=e.fileSize,n=Q(s.browser);if(r>n){let o=j(s.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${s.browser}'s ${o} limit. Use a Chromium browser (Chrome, Edge, Brave) for unlimited file sizes.`)}let a={id:t,type:"receive",manifest:e,state:"initialized",nextPieceToRequest:0,piecesReceivedCount:0,receivedWaterLevel:-1,receivedOutOfOrder:new Set,maxOutOfOrderSize:1e4,piecesRequested:new Set,bytesTransferred:0,streamingHandler:i.streamingHandler||null,fileBuffer:null,pieceBuffers:new Map,paused:!1,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,pieceTimeouts:new Map,idleTimeoutId:null,pieceRetries:new Map};return a.streamingHandler||this._initReceiveBuffer(a,e),this.transfers.set(t,a),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(a)}}startReceive(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive")throw new Error(`Receive transfer not found: ${t}`);i.state="transferring",i.startTime=i.startTime||Date.now(),i.requestPiece=e,this._startIdleTimeout(t),this._requestNextPieces(t)}async handlePieceData(t,e,i){let s=this.transfers.get(t);if(!s||s.type!=="receive")return{success:!1,error:"Transfer not found"};if(s.state==="completing"||s.state==="completed")return{success:!0,duplicate:!0,progress:100};if(e<0||e>=s.manifest.totalPieces)return{success:!1,error:"Invalid piece index"};if(!(i instanceof ArrayBuffer||i instanceof Uint8Array||ArrayBuffer.isView(i)))return{success:!1,error:"Invalid piece data type"};let r=i.byteLength;if(r===0&&!(e===0&&s.manifest.fileSize===0))return{success:!1,error:"Empty piece data"};let n=this._getPieceSize(s.manifest,e);if(r>n)return{success:!1,error:"Invalid piece size"};let o=e===s.manifest.totalPieces-1?1:Math.floor(n*.5);if(this._checkRateLimit(t),this._clearPieceTimeout(t,e),e<=s.receivedWaterLevel||s.receivedOutOfOrder.has(e))return{success:!0,duplicate:!0,progress:this._calculateProgress(s)};try{if(s.lastActivityTime=Date.now(),s.streamingHandler){let f=await s.streamingHandler.writeChunk(t,e,new Uint8Array(i));if(f===!1||f&&f.success===!1)return{success:!1,error:f&&f.error||"Failed to write piece to streaming handler",retryable:!0,pieceIndex:e}}else this._storePieceInMemory(s,e,i);if(e===s.receivedWaterLevel+1)for(s.receivedWaterLevel=e;s.receivedOutOfOrder.has(s.receivedWaterLevel+1);)s.receivedOutOfOrder.delete(s.receivedWaterLevel+1),s.receivedWaterLevel++;else{if(!!!s.streamingHandler&&s.receivedOutOfOrder.size>=s.maxOutOfOrderSize)return{success:!1,error:"Out-of-order buffer full - retry later",retryable:!0,pieceIndex:e};s.receivedOutOfOrder.add(e)}s.piecesReceivedCount++,s.piecesRequested.delete(e),s.bytesTransferred+=r;let l=this._calculateProgress(s);return this._checkProgressMilestones(t,s,l),this._emitProgress(t,s),s.piecesReceivedCount===s.manifest.totalPieces?(s.state="completing",this._stopIdleTimeout(t),{success:!0,complete:!0,progress:100}):(s.paused||this._requestNextPieces(t),{success:!0,progress:this._calculateProgress(s),piecesReceived:s.piecesReceivedCount,totalPieces:s.manifest.totalPieces})}catch(l){return{success:!1,error:l.message}}}async completeReceive(t){let e=this.transfers.get(t);if(!e||e.type!=="receive")return{success:!1,error:"Transfer not found"};if(e.piecesReceivedCount!==e.manifest.totalPieces)return{success:!1,error:`Incomplete: ${e.piecesReceivedCount}/${e.manifest.totalPieces}`};e.state="completing",this._stopIdleTimeout(t);try{return e.streamingHandler?(await e.streamingHandler.completeStream(t),{success:!0,streaming:!0,manifest:e.manifest}):{success:!0,blob:this._assembleFile(e),manifest:e.manifest,fileName:e.manifest.fileName}}catch(i){return{success:!1,error:i.message}}}pause(t){let e=this.transfers.get(t);e&&(e.paused=!0,this._stopPacingLoop(t),this._stopIdleTimeout(t),this.emit("paused",{transferId:t}))}resume(t){let e=this.transfers.get(t);!e||!e.paused||(e.paused=!1,this._startIdleTimeout(t),e.type==="send"?e.usePacing?this._startPacingLoop(t):this._sendNextPieces(t):this._requestNextPieces(t),this.emit("resumed",{transferId:t}))}cancel(t){let e=this.transfers.get(t);e&&(this._cleanupTransfer(t),this.emit("cancelled",{transferId:t,type:e.type}))}cleanup(t){this._cleanupTransfer(t)}getStats(t){let e=this.transfers.get(t);if(!e)return null;let i=e.startTime?Date.now()-e.startTime:0,s=i>0?e.bytesTransferred/i*1e3:0;return{transferId:t,type:e.type,state:e.state,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),piecesCompleted:e.type==="send"?e.piecesAckedCount:e.piecesReceivedCount,totalPieces:e.manifest.totalPieces,speed:s,elapsed:i,paused:e.paused}}async _sendNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.retryQueue.length>=this.config.maxRetryQueueSize){this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let i=this.config.maxConcurrentPieces*e.channels.length*this.config.maxInFlightMultiplier;if(e.inFlightPieces.size>=i)return;let s=e.channel,r=e.channels.length===1;if(r){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return}else{let h=!1;for(let w of e.channels)if(w.readyState==="open"&&w.bufferedAmount<=this.config.highWaterMark){h=!0;break}if(!h)return}let a=this.config.maxConcurrentPieces*e.channels.length-e.inFlightPieces.size;if(a<=0)return;let o=[];for(;e.retryQueue.length>0&&o.length<a;){let h=e.retryQueue.shift();e.inFlightPieces.has(h)||o.push(h)}for(;e.nextPieceToSend<e.manifest.totalPieces&&o.length<a;){let h=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(h)||o.push(h)}if(o.length===0)return;let l=Date.now();for(let h of o)e.inFlightPieces.set(h,l);let f=r?4:4*e.channels.length;try{for(let h=0;h<o.length;h+=f){let P=o.slice(h,h+f).map(async y=>{let p=await this._readPiece(e.file,e.manifest,y);return{pieceIndex:y,data:p}}),b=await Promise.all(P);if(r)for(let{pieceIndex:y,data:p}of b){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.send(this._encodePieceMessage(t,y,p)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,y)}else for(let{pieceIndex:y,data:p}of b){let m=null,u=-1;for(let S=0;S<e.channels.length;S++){let d=(e.channelIndex+S)%e.channels.length,g=e.channels[d];if(g.readyState==="open"&&g.bufferedAmount<=this.config.highWaterMark){m=g,u=d,e.channelIndex=(d+1)%e.channels.length;break}}if(!m)return;m.send(this._encodePieceMessage(t,y,p)),e.inFlightPieces.set(y,{sendTime:Date.now(),channelIndex:u}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,y)}}}catch(h){for(let w of o)e.inFlightPieces.delete(w),e.retryQueue.push(w);this.emit("error",{transferId:t,error:`Failed to send pieces: ${h.message}`})}}_completeSend(t){let e=this.transfers.get(t);e&&(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),this.emit("complete",{transferId:t,type:"send",bytesTransferred:e.bytesTransferred,duration:Date.now()-e.startTime}))}_startPacingLoop(t){let e=this.transfers.get(t);!e||e.pacingIntervalId||(e.pacingIntervalId=setInterval(()=>{this._pacingTick(t)},this.config.pacingIntervalMs),this._pacingTick(t))}_stopPacingLoop(t){let e=this.transfers.get(t);!e||!e.pacingIntervalId||(clearInterval(e.pacingIntervalId),e.pacingIntervalId=null)}_pacingTick(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.pendingReads===void 0&&(e.pendingReads=0),e.retryQueue.length>=this.config.maxRetryQueueSize){this._stopPacingLoop(t),this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let i=e.inFlightPieces.size;if(i>=this.config.maxInFlightPieces)return;let s=e.channel;if(!s||s.readyState!=="open")return;let r=s.bufferedAmount,n=0;if(r>this.config.maxBufferedAmount)return;r>this.config.targetBufferedAmount?n=32:r>this.config.minBufferedAmount?n=this.config.maxPiecesPerTick:n=this.config.burstPiecesPerTick;let a=this.config.maxPendingReads-e.pendingReads;if(a<=0)return;n=Math.min(n,a);let o=this.config.maxInFlightPieces-i;n=Math.min(n,o);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let h=e.retryQueue.shift();e.inFlightPieces.has(h)||l.push(h)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let h=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(h)||l.push(h)}if(l.length===0){e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.inFlightPieces.size===0&&e.pendingReads===0&&this._stopPacingLoop(t);return}e.pendingReads+=l.length;let f=Date.now();for(let h of l)e.inFlightPieces.set(h,f);this._readAndSendPieces(t,e,s,l)}async _readAndSendPieces(t,e,i,s){try{let r=s.map(a=>this._readPiece(e.file,e.manifest,a).then(o=>({pieceIndex:a,data:o})).catch(o=>({pieceIndex:a,error:o}))),n=await Promise.all(r);for(let a of n){if(a.error){e.inFlightPieces.delete(a.pieceIndex),e.retryQueue.push(a.pieceIndex);continue}let{pieceIndex:o,data:l}=a,f=e.channel;if(!f||f.readyState!=="open"){e.inFlightPieces.delete(o),e.retryQueue.push(o);continue}if(f.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(o),e.retryQueue.unshift(o);continue}try{f.send(this._encodePieceMessage(t,o,l)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,o)}catch{e.inFlightPieces.delete(o),e.retryQueue.unshift(o)}}}catch{for(let n of s)e.inFlightPieces.has(n)&&(e.inFlightPieces.delete(n),e.retryQueue.push(n))}finally{e.pendingReads-=s.length}}_requestNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;let i=this._getNextPiecesToRequest(e);for(let s of i)e.piecesRequested.add(s),e.requestPiece(s),this._startPieceTimeout(t,s)}_getNextPiecesToRequest(t){let e=this.config.maxConcurrentPieces-t.piecesRequested.size;if(e<=0)return[];let i=[];for(;t.nextPieceToRequest<t.manifest.totalPieces&&i.length<e;){let s=t.nextPieceToRequest;t.nextPieceToRequest++,!(s<=t.receivedWaterLevel||t.receivedOutOfOrder.has(s))&&!t.piecesRequested.has(s)&&i.push(s)}return i}_initReceiveBuffer(t,e){if(e.fileSize<=104857600)try{t.fileBuffer=new ArrayBuffer(e.fileSize),t.useChunkedBuffer=!1}catch{t.useChunkedBuffer=!0}else t.useChunkedBuffer=!0}_storePieceInMemory(t,e,i){if(t.useChunkedBuffer)t.pieceBuffers.set(e,new Uint8Array(i));else{let s=e*t.manifest.pieceSize;new Uint8Array(t.fileBuffer,s,i.byteLength).set(new Uint8Array(i))}}_assembleFile(t){let{manifest:e,fileBuffer:i,pieceBuffers:s,useChunkedBuffer:r}=t;if(r){let n=[];for(let o=0;o<e.totalPieces;o++){let l=s.get(o);if(!l)throw new Error(`Missing piece ${o}`);n.push(l)}let a=new Blob(n,{type:e.fileType||"application/octet-stream"});return s.clear(),a}else return new Blob([i],{type:e.fileType||"application/octet-stream"})}_createManifest(t){let e=this.config.pieceSize,i=Math.ceil(t.size/e);return{fileName:t.name,fileSize:t.size,fileType:t.type||"application/octet-stream",pieceSize:e,totalPieces:i,createdAt:Date.now()}}async _readPiece(t,e,i){let s=i*e.pieceSize,r=Math.min(s+e.pieceSize,t.size),a=await t.slice(s,r).arrayBuffer(),o=new Uint8Array(4+a.byteLength);return o.set(new Uint8Array(a),4),o}_getPieceSize(t,e){return e===t.totalPieces-1?t.fileSize-e*t.pieceSize:t.pieceSize}_calculateProgress(t){return(t.type==="send"?t.piecesAckedCount:t.piecesReceivedCount)/t.manifest.totalPieces*100}_emitProgress(t,e){let i=Date.now();if(i-(e.lastProgressEmit||0)<100)return;let s=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||i,a=i-n;if(a>=200){let f=e.bytesTransferred-r;s=a>0?f/a*1e3:0,e.lastSpeedBytes=e.bytesTransferred,e.lastSpeedTime=i}else e.smoothedSpeed?s=e.smoothedSpeed:e.startTime&&i>e.startTime&&(s=e.bytesTransferred/(i-e.startTime)*1e3);s=Math.max(0,s),Number.isFinite(s)||(s=0);let o=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=s:s>0&&(e.smoothedSpeed=o*s+(1-o)*e.smoothedSpeed);let l=Math.round(e.smoothedSpeed);e.lastProgressEmit=i,this.emit("progress",{transferId:t,type:e.type,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),speed:l})}_encodePieceMessage(t,e,i){return new DataView(i.buffer).setUint32(0,e,!0),i.buffer}decodePieceMessage(t){let i=new DataView(t).getUint32(0,!0),s=t.slice(4);return{pieceIndex:i,data:s}}_checkProgressMilestones(t,e,i){let s=Date.now(),r=36e5,n=0;if(e.lastMilestoneCheck){let a=s-e.lastMilestoneCheck.time,o=e.bytesTransferred-e.lastMilestoneCheck.bytes;if(a>0&&a<r&&o>0)n=o/a*1e3/1e6;else if(a>=r){let l=e.startTime?s-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let a=e.startTime?s-e.startTime:0;n=a>0?e.bytesTransferred/a*1e3/1e6:0}e.lastMilestoneCheck={time:s,bytes:e.bytesTransferred},(!Number.isFinite(n)||n<0)&&(n=0),i>=90&&!e.milestone90&&(e.milestone90=!0,e.speedAt90Time=s,this.emit("progress-milestone",{transferId:t,milestone:90,speed:n})),i>=99&&!e.milestone99&&(e.milestone99=!0,this.emit("progress-milestone",{transferId:t,milestone:99,speed:n}))}_startPieceTimeout(t,e){let i=this.transfers.get(t);if(!i)return;let s=i.pieceTimeouts.get(e);s&&clearTimeout(s);let r=setTimeout(()=>{this._handlePieceTimeout(t,e)},this.config.pieceTimeout);i.pieceTimeouts.set(e,r)}_clearPieceTimeout(t,e){let i=this.transfers.get(t);if(!i)return;let s=i.pieceTimeouts.get(e);s&&(clearTimeout(s),i.pieceTimeouts.delete(e))}_handlePieceTimeout(t,e){let i=this.transfers.get(t);if(!i||i.state==="completed"||i.state==="completing")return;if(i.type==="receive"&&i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e))){i.pieceTimeouts.delete(e);return}let s=Date.now()-i.lastActivityTime,r=this.config.pieceTimeout*.5,a=i.manifest.totalPieces-i.piecesReceivedCount<=10;if(s<r&&!a){this._startPieceTimeout(t,e);return}let o=i.pieceRetries.get(e)||0;o<this.config.maxPieceRetries?(i.pieceRetries.set(e,o+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:o+1}),i.type==="receive"?(i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e)):(i.inFlightPieces.delete(e),i.retryQueue.push(e),i.usePacing||this._sendNextPieces(t))):(i.inFlightPieces.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}_startIdleTimeout(t){this._stopIdleTimeout(t);let e=this.transfers.get(t);e&&(e.idleTimeoutId=setTimeout(()=>{let i=this.transfers.get(t);if(!i||i.state==="completing"||i.state==="completed")return;let s=Date.now()-i.lastActivityTime;s>=this.config.idleTimeout?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.config.idleTimeout))}_stopIdleTimeout(t){let e=this.transfers.get(t);!e||!e.idleTimeoutId||(clearTimeout(e.idleTimeoutId),e.idleTimeoutId=null)}_onBufferedAmountLow(t){this.transfers.forEach((e,i)=>{e.type==="send"&&!e.usePacing&&(e.channel===t.target||e.channels?.includes(t.target))&&this._sendNextPieces(i)})}_cleanupTransfer(t){let e=this.transfers.get(t);if(e){if(this._stopPacingLoop(t),e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear(),this._stopIdleTimeout(t),e.channels)for(let i of e.channels)i.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);else e.channel&&e.channel.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);e.pieceBuffers&&e.pieceBuffers.clear(),e.fileBuffer=null,this.rateLimits.delete(t),this.transfers.delete(t)}}_checkRateLimit(t){let e=Date.now(),i=this.rateLimits.get(t);return i||(i={count:0,windowStart:e},this.rateLimits.set(t,i)),e-i.windowStart>this.config.rateLimitWindowMs&&(i.count=0,i.windowStart=e),i.count++,{allowed:i.count<=this.config.maxPiecesPerSecond,currentRate:i.count,limit:this.config.maxPiecesPerSecond}}};var I="0.4.0",ye="https://perkoon.com",M=class extends we{constructor(t=ye,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new R,this.transport=null,this.signaling=null,this.fileSource=null}async send(t,e={}){this.fileSource=await E(t),this.emit("file-ready",{name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type});let i=e.session?await this._joinSession(e.session,e.password,"sender",e.senderKey):await this._createSession(e.password,e.source);this.sessionCode=i.session_code,this.emit("session-created",{sessionCode:i.session_code,expiresAt:i.expires_at}),this.signaling=new T(this.serverUrl);let{peers:s}=await this.signaling.connect(i.session_code,i.token,i.peer_id,"sender");this.emit("waiting-for-receiver");let r=s.length>0?s[0]:await this._waitForPeer();this.emit("receiver-connected",{peerId:r});let n=`batch_${Date.now()}_${F.randomUUID().replace(/-/g,"").slice(0,9)}`,a=[{id:`file_${Date.now()}_${F.randomUUID().replace(/-/g,"").slice(0,9)}`,name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type}];this.signaling.sendFilesAdded(n,a,this.fileSource.size),this.emit("waiting-for-acceptance");let o=await this._waitForSignal("transfer_accepted",this.peerTimeout,m=>m.batch_id===n);this.emit("transfer-accepted",{batchId:o.batch_id});let l=`transfer_${Date.now()}_${F.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new v,this.transport.initialize(i.peer_id,i.ice_servers);let f=await this.transport.createOffer(l);this.signaling.sendOffer(r,f),this.transport.on("ice-candidate",({candidate:m,mid:u})=>{this.signaling.sendIceCandidate(r,{candidate:m,sdpMid:u||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",m=>{let u=m.candidate;this.transport.addIceCandidate({candidate:u.candidate||u,mid:u.sdpMid||u.mid||"0"})});let h=await this._waitForSignal("answer");this.transport.handleAnswer(h.answer);let w=await this.transport.waitForChannel("control"),P=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,P);let b=this.engine.transfers.get(l).manifest;this._setupControlHandler(w,l);let y=JSON.stringify({type:"manifest",transfer_id:l,manifest:b,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});w.send(y),this._setupProgressEvents(l),await this.engine.startSend(l);let p=await this._waitForCompletion(l);return await this.fileSource.close(),this.signaling.disconnect(),this.transport.close(),{sessionCode:i.session_code,speed:p.speed,duration:p.duration}}async receive(t,e,i={}){let s=await this._joinSession(t,i.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new T(this.serverUrl);let{peers:r}=await this.signaling.connect(t,s.token,s.peer_id,"receiver");this.signaling.on("files_offered",p=>{this.emit("files-offered",{files:p.files,from:p.from}),this.signaling.channel.push("accept_transfer",{batch_id:p.batch_id})});let n=r.length>0?r[0]:await this._waitForPeer();this.emit("sender-found",{peerId:n}),this.signaling.channel.push("signal",{to:n,type:"ready",data:{role:"receiver"}}),this.transport=new v,this.transport.initialize(s.peer_id,s.ice_servers),this.transport.on("ice-candidate",({candidate:p,mid:m})=>{this.signaling.sendIceCandidate(n,{candidate:p,sdpMid:m||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",p=>{let m=p.candidate;this.transport.addIceCandidate({candidate:m.candidate||m,mid:m.sdpMid||m.mid||"0"})});let a=await this._waitForSignal("offer"),o=await this.transport.handleOffer(a.offer);this.signaling.sendAnswer(n,o);let l=await this.transport.waitForChannel("control"),{channel:f,label:h}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=i.stdout?U():B(e,{overwrite:i.overwrite});let w=this.fileSink,P=[],y=await new Promise((p,m)=>{let u=null;l.addEventListener("message",async S=>{if(typeof S.data=="string")try{let d=JSON.parse(S.data);if(d.type==="manifest"&&!u){u=d.transfer_id,this._activeTransferId=u;let g=d.manifest;this.emit("receiving-file",{name:g.fileName,size:g.fileSize}),await w.startStreamingReceive(u,{fileName:g.fileName,size:g.fileSize,fileSize:g.fileSize,type:g.fileType,pieceSize:g.pieceSize},{}),this.engine.initReceive(u,g,{streamingHandler:w}),this._setupProgressEvents(u),this.engine.startReceive(u,_=>{this._safeSend(l,JSON.stringify({type:"request",transfer_id:u,piece_index:_}))});let C=-1;this._ackInterval=setInterval(()=>{let _=this.engine.transfers.get(u);if(!_||_.state==="completed"||_.state==="completing"){clearInterval(this._ackInterval);return}let k=_.receivedWaterLevel;if(k>C){C=k;let L=_.receivedOutOfOrder.size>0?Array.from(_.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:k,ooo:L}))}},100)}}catch(d){d.message&&!d.message.includes("Unexpected token")&&m(d)}}),f.addEventListener("message",async S=>{let d=S.data;if(!u||!(d instanceof ArrayBuffer||Buffer.isBuffer(d)))return;let g=d instanceof ArrayBuffer?d:d.buffer.slice(d.byteOffset,d.byteOffset+d.byteLength),{pieceIndex:C,data:_}=this.engine.decodePieceMessage(g);if(C>=0&&_&&(await this.engine.handlePieceData(u,C,_))?.complete){this._ackInterval&&clearInterval(this._ackInterval);let L=this.engine.transfers.get(u);L&&this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:L.receivedWaterLevel}));let G=w.getFilePath(u);P.push(G),await this.engine.completeReceive(u),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:u}));let O=this.engine.transfers.get(u),x=Date.now()-(O?.startTime||Date.now()),D=O?.manifest;p({files:P,speed:D?D.fileSize/(x/1e3):0,duration:x})}})});return this.signaling.disconnect(),this.transport.close(),y}async _createSession(t,e="agent_cli"){let i=`${this.serverUrl}/api/v1/sessions`,s={client_version:I,source:e};t&&(s.password=t);let r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let n=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${n.error||r.statusText}`)}return r.json()}async _joinSession(t,e,i="receiver",s=void 0){let r=`${this.serverUrl}/api/v1/sessions/${t}/join`,n={role:i,client_version:I};e&&(n.password=e),s&&(n.sender_key=s);let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok){let o=await a.json().catch(()=>({}));throw a.status===404?new Error(`Session ${t} not found. Check the code and try again.`):a.status===410?new Error(`Session ${t} has expired.`):a.status===403?new Error(o.error||"Access denied"):new Error(`Failed to join session: ${o.error||a.statusText}`)}return a.json()}_waitForPeer(){return new Promise((t,e)=>{let i=setTimeout(()=>{s(),e(new Error(`No peer connected within ${Math.round(this.peerTimeout/1e3)}s`))},this.peerTimeout),s=()=>{clearTimeout(i),this.signaling.removeListener("ready",r),this.signaling.removeListener("offer",r),this.signaling.removeListener("peer_joined",r)},r=n=>{(n.type==="ready"||n.type==="offer"||n.type==="peer_joined"&&(!n.role||n.role==="receiver"))&&(s(),t(n.from))};this.signaling.on("ready",r),this.signaling.on("offer",r),this.signaling.on("peer_joined",r)})}_waitForSignal(t,e=3e4,i=null){return new Promise((s,r)=>{let n=setTimeout(()=>{this.signaling.removeListener(t,a),r(new Error(`Timed out waiting for ${t} signal`))},e),a=o=>{i&&!i(o)||(this.signaling.removeListener(t,a),clearTimeout(n),s(o))};this.signaling.on(t,a)})}_setupProgressEvents(t){let e=setInterval(()=>{let i=this.engine.transfers.get(t);if(!i){clearInterval(e);return}let s=i.manifest,r=s.totalPieces,n;i.type==="send"?n=i.piecesAckedCount||0:n=i.piecesReceivedCount||0;let a=r>0?Math.round(n/r*100):0,o=Date.now()-(i.startTime||Date.now()),l=n*s.pieceSize,f=o>0?l/(o/1e3):0,h=f>0?Math.round((s.fileSize-l)/f):0;this.emit("progress",{transferId:t,percent:Math.min(a,100),speed:f,eta:h,bytesTransferred:l,totalBytes:s.fileSize}),(i.state==="completed"||i.state==="cancelled")&&clearInterval(e)},200)}_setupControlHandler(t,e){t.addEventListener("message",i=>{if(typeof i.data=="string")try{let s=JSON.parse(i.data);switch(s.type){case"ack":this.engine.handlePieceAck(e,s.piece_index);break;case"ack_wl":this.engine.handleWaterLevelAck(e,s.wl,s.ooo);break;case"water_level_ack":this.engine.handleWaterLevelAck(e,s.water_level,s.out_of_order);break;case"ack_batch":Array.isArray(s.pieces)&&s.pieces.length>0&&this.engine.handlePieceAckBatch(e,s.pieces);break;case"request":this.engine.requeuePiece(e,s.piece_index);break;case"complete":{let r=this.engine.transfers.get(e);r&&r.state!=="completed"&&(r.piecesAckedCount=r.manifest.totalPieces,r.bytesTransferred=r.manifest.fileSize,r.inFlightPieces.clear(),r.state="completed")}this.emit("transfer-complete",{transferId:e});break;case"backpressure":s.pause?this.engine.pause(e):this.engine.resume(e);break}}catch{}})}_waitForCompletion(t){return new Promise((e,i)=>{let s=()=>{let n=this.engine.transfers.get(t);if(!n){clearInterval(r),i(new Error("Transfer context lost"));return}if(n.state==="completed"){clearInterval(r);let a=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(a/1e3),duration:a});return}if(n.state==="cancelled"||n.state==="failed"){clearInterval(r),i(new Error("Transfer failed"));return}},r=setInterval(()=>{s()},500);this.on("transfer-complete",({transferId:n})=>{n===t&&s()})})}_safeSend(t,e){try{t.readyState==="open"&&t.send(e)}catch{}}destroy(){this._ackInterval&&clearInterval(this._ackInterval),this.fileSink&&this._activeTransferId&&(this.fileSink.abort(this._activeTransferId).catch(()=>{}),this.fileSink=null),this.fileSource&&this.fileSource.close().catch(()=>{}),this.signaling&&this.signaling.disconnect(),this.transport&&this.transport.close()}};export{v as NodeTransport,T as SignalingClient,M as TransferManager,B as createNodeFileSink,E as createNodeFileSource};
1
+ import{EventEmitter as ye}from"node:events";import E from"node:crypto";import J from"node-datachannel";import{EventEmitter as V}from"node:events";var{PeerConnection:X}=J,v=class extends V{constructor(){super(),this.pc=null,this.channels=new Map,this.remoteDescriptionSet=!1,this.pendingCandidates=[]}initialize(t,e){let i={iceServers:e.map(s=>(Array.isArray(s.urls)?s.urls:[s.urls]).map(n=>s.username&&s.credential?`${n}:${s.username}:${s.credential}`:n).join(",")),maxMessageSize:262144};this.pc=new X(t,i),this.pc.onLocalCandidate((s,r)=>{this.emit("ice-candidate",{candidate:s,mid:r})}),this.pc.onStateChange(s=>{this.emit("connection-state",s)}),this.pc.onGatheringStateChange(s=>{this.emit("gathering-state",s)}),this.pc.onDataChannel(s=>{let r=this._wrapChannel(s),n=s.getLabel();this.channels.set(n,r),this.emit("data-channel",r,n)})}async createOffer(t){return new Promise(e=>{this.pc.onLocalDescription((n,a)=>{e({sdp:n,type:a})});let i=this.pc.createDataChannel("control",{ordered:!0});this.channels.set("control",this._wrapChannel(i));let s=`transfer-${t}`,r=this.pc.createDataChannel(s,{ordered:!0,maxRetransmits:3});this.channels.set(s,this._wrapChannel(r))})}async handleOffer(t){return new Promise(e=>{this.pc.onLocalDescription((i,s)=>{e({sdp:i,type:s})}),this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()})}handleAnswer(t){this.pc.setRemoteDescription(t.sdp,t.type),this.remoteDescriptionSet=!0,this._flushPendingCandidates()}addIceCandidate(t){t.candidate&&(this.remoteDescriptionSet?this.pc.addRemoteCandidate(t.candidate,t.mid||"0"):this.pendingCandidates.push(t))}_flushPendingCandidates(){for(let t of this.pendingCandidates)this.pc.addRemoteCandidate(t.candidate,t.mid||"0");this.pendingCandidates=[]}getChannel(t){return this.channels.get(t)||null}async waitForChannel(t,e=3e4){let i=this.channels.get(t);return i?.readyState==="open"?i:new Promise((s,r)=>{let n=setTimeout(()=>{r(new Error(`Data channel "${t}" open timeout`))},e),a=o=>{o.readyState==="open"?(clearTimeout(n),s(o)):o.addEventListener("open",()=>{clearTimeout(n),s(o)})};if(i)a(i);else{let o=(l,f)=>{f===t&&(this.removeListener("data-channel",o),a(l))};this.on("data-channel",o)}})}async waitForChannelByPrefix(t,e=3e4){for(let[i,s]of this.channels)if(i.startsWith(t))return s.readyState==="open"?{channel:s,label:i}:new Promise((r,n)=>{let a=setTimeout(()=>n(new Error(`Channel "${i}" open timeout`)),e);s.addEventListener("open",()=>{clearTimeout(a),r({channel:s,label:i})})});return new Promise((i,s)=>{let r=setTimeout(()=>{this.removeListener("data-channel",n),s(new Error(`No channel with prefix "${t}" within ${e}ms`))},e),n=(a,o)=>{o.startsWith(t)&&(this.removeListener("data-channel",n),a.readyState==="open"?(clearTimeout(r),i({channel:a,label:o})):a.addEventListener("open",()=>{clearTimeout(r),i({channel:a,label:o})}))};this.on("data-channel",n)})}close(){for(let[,t]of this.channels)try{t.close()}catch{}if(this.channels.clear(),this.pc){try{this.pc.close()}catch{}this.pc=null}}_wrapChannel(t){let e=new Map,i="connecting";try{t.isOpen&&t.isOpen()&&(i="open")}catch{}let s={get readyState(){return i},get bufferedAmount(){try{return t.bufferedAmount()||0}catch{return 0}},send(n){n instanceof ArrayBuffer?t.sendMessageBinary(Buffer.from(n)):n instanceof Uint8Array?t.sendMessageBinary(Buffer.from(n.buffer,n.byteOffset,n.byteLength)):Buffer.isBuffer(n)?t.sendMessageBinary(n):t.sendMessage(String(n))},close(){i="closed";try{t.close()}catch{}},addEventListener(n,a){e.has(n)||e.set(n,new Set),e.get(n).add(a)},removeEventListener(n,a){let o=e.get(n);o&&o.delete(a)},set onmessage(n){s.addEventListener("message",n)},set onopen(n){s.addEventListener("open",n)},set onclose(n){s.addEventListener("close",n)},set onerror(n){s.addEventListener("error",n)}};t.onOpen(()=>{i="open",r("open",{})}),t.onClosed(()=>{i="closed",r("close",{})}),t.onError(n=>{r("error",{error:n})}),t.onMessage(n=>{let a;Buffer.isBuffer(n)?a=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):a=n,r("message",{data:a})});function r(n,a){let o=e.get(n);if(o)for(let l of o)try{l(a)}catch{}}return s}};import{Socket as Z}from"phoenix";import K from"ws";import{EventEmitter as Y}from"node:events";var T=class extends Y{constructor(t){super(),this.serverUrl=t.replace(/\/$/,""),this.socket=null,this.channel=null,this.connected=!1,this.heartbeatInterval=null,this.peers=[]}async connect(t,e,i,s){let r=this.serverUrl.replace(/^https:/,"wss:").replace(/^http:/,"ws:")+"/socket";return new Promise((n,a)=>{this.socket=new Z(r,{params:{},transport:K,timeout:15e3,reconnectAfterMs:()=>36e5}),this.socket.onError(o=>{this.connected||a(new Error(`Signaling connection failed: ${o?.message||"unknown error"}`))}),this.socket.connect(),this.channel=this.socket.channel(`p2p:${t}`,{token:e,peer_id:i,role:s}),this.channel.on("message",o=>{this.emit(o.type,o)}),this.channel.onError(()=>{this.connected=!1}),this.channel.onClose(()=>{this.connected=!1,this._stopHeartbeat(),this.emit("disconnected")}),this.channel.join().receive("ok",o=>{this.connected=!0,this.peers=o.peers||[],this._startHeartbeat(),n({peers:this.peers})}).receive("error",o=>{a(new Error(`Channel join failed: ${o.reason||"unknown"}`))}).receive("timeout",()=>{a(new Error("Channel join timed out"))})})}sendOffer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("offer",{to:t,offer:e})}sendAnswer(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("answer",{to:t,answer:e})}sendIceCandidate(t,e){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("ice_candidate",{to:t,candidate:e})}sendFilesAdded(t,e,i){if(!this.connected)throw new Error("Not connected to signaling server");this.channel.push("files_added",{batch_id:t,files:e,total_size:i})}disconnect(){if(this._stopHeartbeat(),this.channel){try{this.channel.leave()}catch{}this.channel=null}if(this.socket){try{this.socket.disconnect()}catch{}this.socket=null}this.connected=!1}_startHeartbeat(){this._stopHeartbeat(),this.heartbeatInterval=setInterval(()=>{this.connected&&this.channel&&this.channel.push("heartbeat",{})},1e4)}_stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}};import{open as ee}from"node:fs/promises";import te from"node:path";import{lookup as ie}from"mime-types";async function z(c){let t=await ee(c,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${c}`);return{name:te.basename(c),size:e.size,type:ie(c)||"application/octet-stream",async readSlice(i,s){let r=s-i,n=Buffer.alloc(r),{bytesRead:a}=await t.read(n,0,r,i);return a===r?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n.buffer.slice(n.byteOffset,n.byteOffset+a)},async close(){await t.close()}}}import{open as se,mkdir as ne,access as N}from"node:fs/promises";import $ from"node:path";function F(c,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await ne(c,{recursive:!0});let n=ae(s.fileName),a=$.join(c,n);t.overwrite||(a=await re(a));let o=await se(a,"w");s.size>0&&await o.truncate(s.size),e.set(i,{fh:o,filePath:a,pieceSize:s.pieceSize,size:s.size,fileName:s.fileName})},async writeChunk(i,s,r){let n=e.get(i);if(!n)return{success:!1,error:"NO_HANDLE"};let a=s*n.pieceSize,o=Buffer.isBuffer(r)?r:Buffer.from(r);try{return await n.fh.write(o,0,o.byteLength,a),{success:!0}}catch(l){return{success:!1,error:l.message}}},async completeStream(i){let s=e.get(i);s&&(await s.fh.datasync(),await s.fh.close(),e.delete(i))},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(i){return e.get(i)?.filePath},async abort(i){let s=e.get(i);if(s){try{await s.fh.close()}catch{}e.delete(i);try{let{unlink:r}=await import("node:fs/promises");await r(s.filePath)}catch{}}}}}async function re(c){try{await N(c)}catch{return c}let t=$.extname(c),e=c.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await N(s)}catch{return s}}return`${e}_${Date.now()}${t}`}function ae(c){return c.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function U(){let c=new Map;return{async startStreamingReceive(t,e,i){c.set(t,{pieceSize:e.pieceSize,totalPieces:Math.ceil(e.size/e.pieceSize),size:e.size,nextPiece:0,buffer:new Map,bytesWritten:0})},async writeChunk(t,e,i){let s=c.get(t);if(!s)return{success:!1,error:"NO_HANDLE"};let r=Buffer.isBuffer(i)?i:Buffer.from(i);for(s.buffer.set(e,r);s.buffer.has(s.nextPiece);){let n=s.buffer.get(s.nextPiece);s.buffer.delete(s.nextPiece);let a=s.nextPiece*s.pieceSize,o=s.size-a,l=o<n.byteLength?n.subarray(0,o):n;process.stdout.write(l),s.bytesWritten+=l.byteLength,s.nextPiece++}return{success:!0}},async completeStream(t){c.delete(t)},getBackpressureInfo(){return{paused:!1,bufferSize:0}},getFilePath(t){return"stdout"},async abort(t){c.delete(t)}}}var A=class{constructor(){this._listeners=new Map,this._onceListeners=new Map}on(t,e){return this._listeners.has(t)||this._listeners.set(t,new Set),this._listeners.get(t).add(e),()=>this.off(t,e)}once(t,e){return this._onceListeners.has(t)||this._onceListeners.set(t,new Set),this._onceListeners.get(t).add(e),()=>{let i=this._onceListeners.get(t);i&&i.delete(e)}}off(t,e){let i=this._listeners.get(t);i&&i.delete(e);let s=this._onceListeners.get(t);s&&s.delete(e)}removeAllListeners(t){t?(this._listeners.delete(t),this._onceListeners.delete(t)):(this._listeners.clear(),this._onceListeners.clear())}emit(t,e){let i=this._listeners.get(t);i&&i.forEach(r=>{try{r(e)}catch{}});let s=this._onceListeners.get(t);if(s){let r=[...s];this._onceListeners.delete(t),r.forEach(n=>{try{n(e)}catch{}})}}listenerCount(t){let e=this._listeners.get(t)?.size||0,i=this._onceListeners.get(t)?.size||0;return e+i}hasListeners(t){return this.listenerCount(t)>0}eventNames(){return[...new Set([...this._listeners.keys(),...this._onceListeners.keys()])]}waitFor(t,e){return new Promise((i,s)=>{let r,n=a=>{r&&clearTimeout(r),i(a)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),s(new Error(`Timeout waiting for event: ${t}`))},e))})}};function oe(){if(typeof window>"u"||typeof navigator>"u")return ce();let c=le();return{browser:c.browser,version:c.version,platform:he(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:fe(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:de(c.browser),recommendedChunkSize:pe(c.browser),maxFileSize:me(c.browser),maxFileSizeLabel:ge(c.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:ue(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function ce(){return{browser:"node",version:0,platform:"node",supportsFileSystemAccess:!1,supportsDirectoryPicker:!1,supportsOPFS:!1,supportsWebRTC:!0,supportsDataChannel:!0,maxChannelBufferSize:256*1024*1024,recommendedChunkSize:256*1024,maxFileSize:Number.POSITIVE_INFINITY,maxFileSizeLabel:"unlimited",supportsTransferable:typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:typeof ReadableStream<"u"&&typeof WritableStream<"u",isMemoryConstrained:!1,isMobile:!1}}function le(){let c=navigator.userAgent,t="unknown",e="0";if(c.includes("Edg/")){t="edge";let i=c.match(/Edg\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Chrome/")){t="chrome";let i=c.match(/Chrome\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Firefox/")){t="firefox";let i=c.match(/Firefox\/(\d+)/);e=i?i[1]:"0"}else if(c.includes("Safari/")&&!c.includes("Chrome")){t="safari";let i=c.match(/Version\/(\d+)/);e=i?i[1]:"0"}return{browser:t,version:parseInt(e,10)}}function he(){let c=navigator.userAgent;return c.includes("Windows")?"windows":c.includes("Mac")?"macos":c.includes("Linux")?"linux":c.includes("Android")?"android":c.includes("iPhone")||c.includes("iPad")?"ios":"unknown"}function fe(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function ue(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function de(c){switch(c){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function pe(c){switch(c){case"chrome":case"edge":return 256*1024;case"firefox":return 64*1024;case"safari":return 128*1024;default:return 64*1024}}function me(c){switch(c){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return 1/0;case"safari":return 4*1024*1024*1024;default:return 2*1024*1024*1024}}function ge(c){switch(c){case"chrome":case"edge":case"brave":case"opera":case"vivaldi":case"samsung":case"ucbrowser":case"firefox":return"Unlimited";case"safari":return"4GB";default:return"2GB"}}var B=null;function W(){return B||(B=oe()),B}var q={chrome:1/0,edge:1/0,brave:1/0,opera:1/0,vivaldi:1/0,samsung:1/0,ucbrowser:1/0,firefox:1/0,safari:4294967296,ios:4294967296,unknown:2147483648},H={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function Q(c){return q[c]||q.unknown}function I(c){return H[c]||H.unknown}var we={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxPieceRetries:3,maxRetryQueueSize:1e4,maxInFlightMultiplier:10,highWaterMark:16*1024*1024,lowWaterMark:4*1024*1024,maxPiecesPerSecond:5e3,rateLimitWindowMs:1e3,channelRotationThreshold:512*1024*1024,channelRotationEnabled:!1,parallelChannels:1,parallelChannelsEnabled:!1,parallelChannelsThreshold:100*1024*1024,pacingEnabled:!0,pacingThreshold:0,pacingIntervalMs:10,targetBufferedAmount:8*1024*1024,minBufferedAmount:4*1024*1024,maxBufferedAmount:16*1024*1024,maxPiecesPerTick:32,burstPiecesPerTick:64,maxPendingReads:512,maxInFlightPieces:4e3},R=class extends A{constructor(t={}){super(),this.config={...we,...t},this.transfers=new Map,this.rateLimits=new Map,this._onBufferedAmountLow=this._onBufferedAmountLow.bind(this)}initSend(t,e,i){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let s=this._createManifest(e),r=this.config.parallelChannelsEnabled&&e.size>=this.config.parallelChannelsThreshold,n={id:t,type:"send",file:e,manifest:s,channel:i,channels:[i],channelIndex:0,useParallelChannels:r,state:"initialized",nextPieceToSend:0,piecesAckedCount:0,bytesTransferred:0,paused:!1,inFlightPieces:new Map,retryQueue:[],channelBytes:[0],channelThresholds:[this.config.channelRotationThreshold],channelRotationCounts:[0],pendingRotations:new Set,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,idleTimeoutId:null,pieceTimeouts:new Map,pieceRetries:new Map};return this.transfers.set(t,n),{transferId:t,manifest:s}}async startSend(t){let e=this.transfers.get(t);if(!e||e.type!=="send")throw new Error(`Send transfer not found: ${t}`);if(e.state==="transferring")return;e.state="transferring",e.startTime=e.startTime||Date.now();let i=this.config.pacingEnabled&&e.manifest.fileSize>=this.config.pacingThreshold;e.usePacing=i;for(let s of e.channels)s.addEventListener("bufferedamountlow",this._onBufferedAmountLow);e.useParallelChannels&&e.channels.length<this.config.parallelChannels&&this.emit("parallel-channels-needed",{transferId:t,currentCount:e.channels.length,targetCount:this.config.parallelChannels}),this._startIdleTimeout(t),i?this._startPacingLoop(t):await this._sendNextPieces(t)}addChannel(t,e){let i=this.transfers.get(t);if(!i||i.type!=="send")return;let s=i.channels.length;i.channels.push(e),e.addEventListener("bufferedamountlow",this._onBufferedAmountLow),i.channelBytes.push(0),i.channelRotationCounts.push(0),i.channelThresholds||(i.channelThresholds=[this.config.channelRotationThreshold]);let r=this.config.channelRotationThreshold*(1+s*.25);i.channelThresholds.push(r),!i.paused&&i.state==="transferring"&&this._sendNextPieces(t)}handlePieceAck(t,e){let i=this.transfers.get(t);if(!i||i.type!=="send")return;let s=i.inFlightPieces.get(e);if(!s)return;let r=s.channelIndex>=0?s.channelIndex:0;i.inFlightPieces.delete(e),i.lastActivityTime=Date.now(),i.piecesAckedCount++;let n=this._getPieceSize(i.manifest,e);i.bytesTransferred+=n,i.channelBytes[r]!==void 0&&(i.channelBytes[r]+=n),this._checkChannelRotation(t,i,r);let a=this._calculateProgress(i);if(this._checkProgressMilestones(t,i,a),this._emitProgress(t,i),i.piecesAckedCount===i.manifest.totalPieces){this._completeSend(t);return}!i.paused&&!i.usePacing&&this._sendNextPieces(t)}handlePieceAckBatch(t,e){if(Array.isArray(e))for(let i of e)this.handlePieceAck(t,i)}handleWaterLevelAck(t,e,i){let s=this.transfers.get(t);if(!(!s||s.type!=="send")){if(typeof e=="number"&&e>=0)for(let r=0;r<=e;r++)this.handlePieceAck(t,r);if(Array.isArray(i))for(let r of i)this.handlePieceAck(t,r)}}requeuePiece(t,e){let i=this.transfers.get(t);!i||i.type!=="send"||i.state==="completed"||i.state==="completing"||(i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t))}markSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||this._completeSend(t)}_checkChannelRotation(t,e,i){if(!this.config.channelRotationEnabled||e.pendingRotations.has(i))return;let s=e.channelBytes[i]||0,r=e.channelThresholds?.[i]||this.config.channelRotationThreshold;s>=r&&(e.pendingRotations.add(i),this.emit("channel-rotation-needed",{transferId:t,channelIndex:i,bytesTransferred:e.bytesTransferred,channelBytes:s,threshold:r,rotationCount:e.channelRotationCounts[i]||0}))}rotateSingleChannel(t,e,i){let s=this.transfers.get(t);if(!s||s.type!=="send"||e>=s.channels.length)return;let r=s.channels[e],n=(s.channelRotationCounts[e]||0)+1;s.channels[e]=i,e===0&&(s.channel=i),s.channelBytes[e]=0,s.channelRotationCounts[e]=n,s.pendingRotations.delete(e),i.addEventListener("bufferedamountlow",this._onBufferedAmountLow),r&&r.readyState==="open"&&this._drainAndCloseChannel(r,e,n),!s.paused&&!s.usePacing&&this._sendNextPieces(t)}_drainAndCloseChannel(t,e,i){let s=()=>{if(t.readyState==="open"){if(t.bufferedAmount===0){try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}return}setTimeout(s,50)}};s(),setTimeout(()=>{if(t.readyState==="open")try{t.removeEventListener("bufferedamountlow",this._onBufferedAmountLow),t.close()}catch{}},5e3)}rotateChannel(t,e){this.rotateChannels(t,[e])}initReceive(t,e,i={}){if(this.transfers.has(t))throw new Error(`Transfer already exists: ${t}`);let s=W(),r=e.fileSize,n=Q(s.browser);if(r>n){let o=I(s.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${s.browser}'s ${o} limit. Use a Chromium browser (Chrome, Edge, Brave) for unlimited file sizes.`)}let a={id:t,type:"receive",manifest:e,state:"initialized",nextPieceToRequest:0,piecesReceivedCount:0,receivedWaterLevel:-1,receivedOutOfOrder:new Set,maxOutOfOrderSize:1e4,piecesRequested:new Set,bytesTransferred:0,streamingHandler:i.streamingHandler||null,fileBuffer:null,pieceBuffers:new Map,paused:!1,startTime:null,lastActivityTime:Date.now(),lastSpeedBytes:0,lastSpeedTime:0,smoothedSpeed:0,lastProgressEmit:0,pieceTimeouts:new Map,idleTimeoutId:null,pieceRetries:new Map};return a.streamingHandler||this._initReceiveBuffer(a,e),this.transfers.set(t,a),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(a)}}startReceive(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive")throw new Error(`Receive transfer not found: ${t}`);i.state="transferring",i.startTime=i.startTime||Date.now(),i.requestPiece=e,this._startIdleTimeout(t),this._requestNextPieces(t)}async handlePieceData(t,e,i){let s=this.transfers.get(t);if(!s||s.type!=="receive")return{success:!1,error:"Transfer not found"};if(s.state==="completing"||s.state==="completed")return{success:!0,duplicate:!0,progress:100};if(e<0||e>=s.manifest.totalPieces)return{success:!1,error:"Invalid piece index"};if(!(i instanceof ArrayBuffer||i instanceof Uint8Array||ArrayBuffer.isView(i)))return{success:!1,error:"Invalid piece data type"};let r=i.byteLength;if(r===0&&!(e===0&&s.manifest.fileSize===0))return{success:!1,error:"Empty piece data"};let n=this._getPieceSize(s.manifest,e);if(r>n)return{success:!1,error:"Invalid piece size"};let o=e===s.manifest.totalPieces-1?1:Math.floor(n*.5);if(this._checkRateLimit(t),this._clearPieceTimeout(t,e),e<=s.receivedWaterLevel||s.receivedOutOfOrder.has(e))return{success:!0,duplicate:!0,progress:this._calculateProgress(s)};try{if(s.lastActivityTime=Date.now(),s.streamingHandler){let f=await s.streamingHandler.writeChunk(t,e,new Uint8Array(i));if(f===!1||f&&f.success===!1)return{success:!1,error:f&&f.error||"Failed to write piece to streaming handler",retryable:!0,pieceIndex:e}}else this._storePieceInMemory(s,e,i);if(e===s.receivedWaterLevel+1)for(s.receivedWaterLevel=e;s.receivedOutOfOrder.has(s.receivedWaterLevel+1);)s.receivedOutOfOrder.delete(s.receivedWaterLevel+1),s.receivedWaterLevel++;else{if(!!!s.streamingHandler&&s.receivedOutOfOrder.size>=s.maxOutOfOrderSize)return{success:!1,error:"Out-of-order buffer full - retry later",retryable:!0,pieceIndex:e};s.receivedOutOfOrder.add(e)}s.piecesReceivedCount++,s.piecesRequested.delete(e),s.bytesTransferred+=r;let l=this._calculateProgress(s);return this._checkProgressMilestones(t,s,l),this._emitProgress(t,s),s.piecesReceivedCount===s.manifest.totalPieces?(s.state="completing",this._stopIdleTimeout(t),{success:!0,complete:!0,progress:100}):(s.paused||this._requestNextPieces(t),{success:!0,progress:this._calculateProgress(s),piecesReceived:s.piecesReceivedCount,totalPieces:s.manifest.totalPieces})}catch(l){return{success:!1,error:l.message}}}async completeReceive(t){let e=this.transfers.get(t);if(!e||e.type!=="receive")return{success:!1,error:"Transfer not found"};if(e.piecesReceivedCount!==e.manifest.totalPieces)return{success:!1,error:`Incomplete: ${e.piecesReceivedCount}/${e.manifest.totalPieces}`};e.state="completing",this._stopIdleTimeout(t);try{return e.streamingHandler?(await e.streamingHandler.completeStream(t),{success:!0,streaming:!0,manifest:e.manifest}):{success:!0,blob:this._assembleFile(e),manifest:e.manifest,fileName:e.manifest.fileName}}catch(i){return{success:!1,error:i.message}}}pause(t){let e=this.transfers.get(t);e&&(e.paused=!0,this._stopPacingLoop(t),this._stopIdleTimeout(t),this.emit("paused",{transferId:t}))}resume(t){let e=this.transfers.get(t);!e||!e.paused||(e.paused=!1,this._startIdleTimeout(t),e.type==="send"?e.usePacing?this._startPacingLoop(t):this._sendNextPieces(t):this._requestNextPieces(t),this.emit("resumed",{transferId:t}))}cancel(t){let e=this.transfers.get(t);e&&(this._cleanupTransfer(t),this.emit("cancelled",{transferId:t,type:e.type}))}cleanup(t){this._cleanupTransfer(t)}getStats(t){let e=this.transfers.get(t);if(!e)return null;let i=e.startTime?Date.now()-e.startTime:0,s=i>0?e.bytesTransferred/i*1e3:0;return{transferId:t,type:e.type,state:e.state,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),piecesCompleted:e.type==="send"?e.piecesAckedCount:e.piecesReceivedCount,totalPieces:e.manifest.totalPieces,speed:s,elapsed:i,paused:e.paused}}async _sendNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.retryQueue.length>=this.config.maxRetryQueueSize){this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let i=this.config.maxConcurrentPieces*e.channels.length*this.config.maxInFlightMultiplier;if(e.inFlightPieces.size>=i)return;let s=e.channel,r=e.channels.length===1;if(r){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return}else{let h=!1;for(let w of e.channels)if(w.readyState==="open"&&w.bufferedAmount<=this.config.highWaterMark){h=!0;break}if(!h)return}let a=this.config.maxConcurrentPieces*e.channels.length-e.inFlightPieces.size;if(a<=0)return;let o=[];for(;e.retryQueue.length>0&&o.length<a;){let h=e.retryQueue.shift();e.inFlightPieces.has(h)||o.push(h)}for(;e.nextPieceToSend<e.manifest.totalPieces&&o.length<a;){let h=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(h)||o.push(h)}if(o.length===0)return;let l=Date.now();for(let h of o)e.inFlightPieces.set(h,l);let f=r?4:4*e.channels.length;try{for(let h=0;h<o.length;h+=f){let P=o.slice(h,h+f).map(async y=>{let p=await this._readPiece(e.file,e.manifest,y);return{pieceIndex:y,data:p}}),b=await Promise.all(P);if(r)for(let{pieceIndex:y,data:p}of b){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.send(this._encodePieceMessage(t,y,p)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,y)}else for(let{pieceIndex:y,data:p}of b){let m=null,u=-1;for(let S=0;S<e.channels.length;S++){let d=(e.channelIndex+S)%e.channels.length,g=e.channels[d];if(g.readyState==="open"&&g.bufferedAmount<=this.config.highWaterMark){m=g,u=d,e.channelIndex=(d+1)%e.channels.length;break}}if(!m)return;m.send(this._encodePieceMessage(t,y,p)),e.inFlightPieces.set(y,{sendTime:Date.now(),channelIndex:u}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,y)}}}catch(h){for(let w of o)e.inFlightPieces.delete(w),e.retryQueue.push(w);this.emit("error",{transferId:t,error:`Failed to send pieces: ${h.message}`})}}_completeSend(t){let e=this.transfers.get(t);e&&(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),this.emit("complete",{transferId:t,type:"send",bytesTransferred:e.bytesTransferred,duration:Date.now()-e.startTime}))}_startPacingLoop(t){let e=this.transfers.get(t);!e||e.pacingIntervalId||(e.pacingIntervalId=setInterval(()=>{this._pacingTick(t)},this.config.pacingIntervalMs),this._pacingTick(t))}_stopPacingLoop(t){let e=this.transfers.get(t);!e||!e.pacingIntervalId||(clearInterval(e.pacingIntervalId),e.pacingIntervalId=null)}_pacingTick(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;if(e.pendingReads===void 0&&(e.pendingReads=0),e.retryQueue.length>=this.config.maxRetryQueueSize){this._stopPacingLoop(t),this.emit("piece-failed",{transferId:t,error:`Network too unstable: ${e.retryQueue.length} pieces pending retry`});return}let i=e.inFlightPieces.size;if(i>=this.config.maxInFlightPieces)return;let s=e.channel;if(!s||s.readyState!=="open")return;let r=s.bufferedAmount,n=0;if(r>this.config.maxBufferedAmount)return;r>this.config.targetBufferedAmount?n=32:r>this.config.minBufferedAmount?n=this.config.maxPiecesPerTick:n=this.config.burstPiecesPerTick;let a=this.config.maxPendingReads-e.pendingReads;if(a<=0)return;n=Math.min(n,a);let o=this.config.maxInFlightPieces-i;n=Math.min(n,o);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let h=e.retryQueue.shift();e.inFlightPieces.has(h)||l.push(h)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let h=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(h)||l.push(h)}if(l.length===0){e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.inFlightPieces.size===0&&e.pendingReads===0&&this._stopPacingLoop(t);return}e.pendingReads+=l.length;let f=Date.now();for(let h of l)e.inFlightPieces.set(h,f);this._readAndSendPieces(t,e,s,l)}async _readAndSendPieces(t,e,i,s){try{let r=s.map(a=>this._readPiece(e.file,e.manifest,a).then(o=>({pieceIndex:a,data:o})).catch(o=>({pieceIndex:a,error:o}))),n=await Promise.all(r);for(let a of n){if(a.error){e.inFlightPieces.delete(a.pieceIndex),e.retryQueue.push(a.pieceIndex);continue}let{pieceIndex:o,data:l}=a,f=e.channel;if(!f||f.readyState!=="open"){e.inFlightPieces.delete(o),e.retryQueue.push(o);continue}if(f.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(o),e.retryQueue.unshift(o);continue}try{f.send(this._encodePieceMessage(t,o,l)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,o)}catch{e.inFlightPieces.delete(o),e.retryQueue.unshift(o)}}}catch{for(let n of s)e.inFlightPieces.has(n)&&(e.inFlightPieces.delete(n),e.retryQueue.push(n))}finally{e.pendingReads-=s.length}}_requestNextPieces(t){let e=this.transfers.get(t);if(!e||e.paused||e.state!=="transferring")return;let i=this._getNextPiecesToRequest(e);for(let s of i)e.piecesRequested.add(s),e.requestPiece(s),this._startPieceTimeout(t,s)}_getNextPiecesToRequest(t){let e=this.config.maxConcurrentPieces-t.piecesRequested.size;if(e<=0)return[];let i=[];for(;t.nextPieceToRequest<t.manifest.totalPieces&&i.length<e;){let s=t.nextPieceToRequest;t.nextPieceToRequest++,!(s<=t.receivedWaterLevel||t.receivedOutOfOrder.has(s))&&!t.piecesRequested.has(s)&&i.push(s)}return i}_initReceiveBuffer(t,e){if(e.fileSize<=104857600)try{t.fileBuffer=new ArrayBuffer(e.fileSize),t.useChunkedBuffer=!1}catch{t.useChunkedBuffer=!0}else t.useChunkedBuffer=!0}_storePieceInMemory(t,e,i){if(t.useChunkedBuffer)t.pieceBuffers.set(e,new Uint8Array(i));else{let s=e*t.manifest.pieceSize;new Uint8Array(t.fileBuffer,s,i.byteLength).set(new Uint8Array(i))}}_assembleFile(t){let{manifest:e,fileBuffer:i,pieceBuffers:s,useChunkedBuffer:r}=t;if(r){let n=[];for(let o=0;o<e.totalPieces;o++){let l=s.get(o);if(!l)throw new Error(`Missing piece ${o}`);n.push(l)}let a=new Blob(n,{type:e.fileType||"application/octet-stream"});return s.clear(),a}else return new Blob([i],{type:e.fileType||"application/octet-stream"})}_createManifest(t){let e=this.config.pieceSize,i=Math.ceil(t.size/e);return{fileName:t.name,fileSize:t.size,fileType:t.type||"application/octet-stream",pieceSize:e,totalPieces:i,createdAt:Date.now()}}async _readPiece(t,e,i){let s=i*e.pieceSize,r=Math.min(s+e.pieceSize,t.size),n=await t.readSlice(s,r),a=new Uint8Array(4+n.byteLength);return a.set(new Uint8Array(n),4),a}_getPieceSize(t,e){return e===t.totalPieces-1?t.fileSize-e*t.pieceSize:t.pieceSize}_calculateProgress(t){return(t.type==="send"?t.piecesAckedCount:t.piecesReceivedCount)/t.manifest.totalPieces*100}_emitProgress(t,e){let i=Date.now();if(i-(e.lastProgressEmit||0)<100)return;let s=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||i,a=i-n;if(a>=200){let f=e.bytesTransferred-r;s=a>0?f/a*1e3:0,e.lastSpeedBytes=e.bytesTransferred,e.lastSpeedTime=i}else e.smoothedSpeed?s=e.smoothedSpeed:e.startTime&&i>e.startTime&&(s=e.bytesTransferred/(i-e.startTime)*1e3);s=Math.max(0,s),Number.isFinite(s)||(s=0);let o=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=s:s>0&&(e.smoothedSpeed=o*s+(1-o)*e.smoothedSpeed);let l=Math.round(e.smoothedSpeed);e.lastProgressEmit=i,this.emit("progress",{transferId:t,type:e.type,bytesTransferred:e.bytesTransferred,totalBytes:e.manifest.fileSize,progress:this._calculateProgress(e),speed:l})}_encodePieceMessage(t,e,i){return new DataView(i.buffer).setUint32(0,e,!0),i.buffer}decodePieceMessage(t){let i=new DataView(t).getUint32(0,!0),s=t.slice(4);return{pieceIndex:i,data:s}}_checkProgressMilestones(t,e,i){let s=Date.now(),r=36e5,n=0;if(e.lastMilestoneCheck){let a=s-e.lastMilestoneCheck.time,o=e.bytesTransferred-e.lastMilestoneCheck.bytes;if(a>0&&a<r&&o>0)n=o/a*1e3/1e6;else if(a>=r){let l=e.startTime?s-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let a=e.startTime?s-e.startTime:0;n=a>0?e.bytesTransferred/a*1e3/1e6:0}e.lastMilestoneCheck={time:s,bytes:e.bytesTransferred},(!Number.isFinite(n)||n<0)&&(n=0),i>=90&&!e.milestone90&&(e.milestone90=!0,e.speedAt90Time=s,this.emit("progress-milestone",{transferId:t,milestone:90,speed:n})),i>=99&&!e.milestone99&&(e.milestone99=!0,this.emit("progress-milestone",{transferId:t,milestone:99,speed:n}))}_startPieceTimeout(t,e){let i=this.transfers.get(t);if(!i)return;let s=i.pieceTimeouts.get(e);s&&clearTimeout(s);let r=setTimeout(()=>{this._handlePieceTimeout(t,e)},this.config.pieceTimeout);i.pieceTimeouts.set(e,r)}_clearPieceTimeout(t,e){let i=this.transfers.get(t);if(!i)return;let s=i.pieceTimeouts.get(e);s&&(clearTimeout(s),i.pieceTimeouts.delete(e))}_handlePieceTimeout(t,e){let i=this.transfers.get(t);if(!i||i.state==="completed"||i.state==="completing")return;if(i.type==="receive"&&i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e))){i.pieceTimeouts.delete(e);return}let s=Date.now()-i.lastActivityTime,r=this.config.pieceTimeout*.5,a=i.manifest.totalPieces-i.piecesReceivedCount<=10;if(s<r&&!a){this._startPieceTimeout(t,e);return}let o=i.pieceRetries.get(e)||0;o<this.config.maxPieceRetries?(i.pieceRetries.set(e,o+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:o+1}),i.type==="receive"?(i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e)):(i.inFlightPieces.delete(e),i.retryQueue.push(e),i.usePacing||this._sendNextPieces(t))):(i.inFlightPieces.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}_startIdleTimeout(t){this._stopIdleTimeout(t);let e=this.transfers.get(t);e&&(e.idleTimeoutId=setTimeout(()=>{let i=this.transfers.get(t);if(!i||i.state==="completing"||i.state==="completed")return;let s=Date.now()-i.lastActivityTime;s>=this.config.idleTimeout?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.config.idleTimeout))}_stopIdleTimeout(t){let e=this.transfers.get(t);!e||!e.idleTimeoutId||(clearTimeout(e.idleTimeoutId),e.idleTimeoutId=null)}_onBufferedAmountLow(t){this.transfers.forEach((e,i)=>{e.type==="send"&&!e.usePacing&&(e.channel===t.target||e.channels?.includes(t.target))&&this._sendNextPieces(i)})}_cleanupTransfer(t){let e=this.transfers.get(t);if(e){if(this._stopPacingLoop(t),e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear(),this._stopIdleTimeout(t),e.channels)for(let i of e.channels)i.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);else e.channel&&e.channel.removeEventListener("bufferedamountlow",this._onBufferedAmountLow);e.pieceBuffers&&e.pieceBuffers.clear(),e.fileBuffer=null,this.rateLimits.delete(t),this.transfers.delete(t)}}_checkRateLimit(t){let e=Date.now(),i=this.rateLimits.get(t);return i||(i={count:0,windowStart:e},this.rateLimits.set(t,i)),e-i.windowStart>this.config.rateLimitWindowMs&&(i.count=0,i.windowStart=e),i.count++,{allowed:i.count<=this.config.maxPiecesPerSecond,currentRate:i.count,limit:this.config.maxPiecesPerSecond}}};var j="0.4.2",_e="https://perkoon.com",M=class extends ye{constructor(t=_e,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new R,this.transport=null,this.signaling=null,this.fileSource=null}async send(t,e={}){this.fileSource=await z(t),this.emit("file-ready",{name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type});let i=e.session?await this._joinSession(e.session,e.password,"sender",e.senderKey):await this._createSession(e.password,e.source);this.sessionCode=i.session_code,this.emit("session-created",{sessionCode:i.session_code,expiresAt:i.expires_at}),this.signaling=new T(this.serverUrl);let{peers:s}=await this.signaling.connect(i.session_code,i.token,i.peer_id,"sender");this.emit("waiting-for-receiver");let r=s.length>0?s[0]:await this._waitForPeer();this.emit("receiver-connected",{peerId:r});let n=`batch_${Date.now()}_${E.randomUUID().replace(/-/g,"").slice(0,9)}`,a=[{id:`file_${Date.now()}_${E.randomUUID().replace(/-/g,"").slice(0,9)}`,name:this.fileSource.name,size:this.fileSource.size,type:this.fileSource.type}];this.signaling.sendFilesAdded(n,a,this.fileSource.size),this.emit("waiting-for-acceptance");let o=await this._waitForSignal("transfer_accepted",this.peerTimeout,m=>m.batch_id===n);this.emit("transfer-accepted",{batchId:o.batch_id});let l=`transfer_${Date.now()}_${E.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new v,this.transport.initialize(i.peer_id,i.ice_servers);let f=await this.transport.createOffer(l);this.signaling.sendOffer(r,f),this.transport.on("ice-candidate",({candidate:m,mid:u})=>{this.signaling.sendIceCandidate(r,{candidate:m,sdpMid:u||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",m=>{let u=m.candidate;this.transport.addIceCandidate({candidate:u.candidate||u,mid:u.sdpMid||u.mid||"0"})});let h=await this._waitForSignal("answer");this.transport.handleAnswer(h.answer);let w=await this.transport.waitForChannel("control"),P=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,P);let b=this.engine.transfers.get(l).manifest;this._setupControlHandler(w,l);let y=JSON.stringify({type:"manifest",transfer_id:l,manifest:b,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});w.send(y),this._setupProgressEvents(l),await this.engine.startSend(l);let p=await this._waitForCompletion(l);return await this.fileSource.close(),this.signaling.disconnect(),this.transport.close(),{sessionCode:i.session_code,speed:p.speed,duration:p.duration}}async receive(t,e,i={}){let s=await this._joinSession(t,i.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new T(this.serverUrl);let{peers:r}=await this.signaling.connect(t,s.token,s.peer_id,"receiver");this.signaling.on("files_offered",p=>{this.emit("files-offered",{files:p.files,from:p.from}),this.signaling.channel.push("accept_transfer",{batch_id:p.batch_id})});let n=r.length>0?r[0]:await this._waitForPeer();this.emit("sender-found",{peerId:n}),this.signaling.channel.push("signal",{to:n,type:"ready",data:{role:"receiver"}}),this.transport=new v,this.transport.initialize(s.peer_id,s.ice_servers),this.transport.on("ice-candidate",({candidate:p,mid:m})=>{this.signaling.sendIceCandidate(n,{candidate:p,sdpMid:m||"0",sdpMLineIndex:0})}),this.signaling.on("ice_candidate",p=>{let m=p.candidate;this.transport.addIceCandidate({candidate:m.candidate||m,mid:m.sdpMid||m.mid||"0"})});let a=await this._waitForSignal("offer"),o=await this.transport.handleOffer(a.offer);this.signaling.sendAnswer(n,o);let l=await this.transport.waitForChannel("control"),{channel:f,label:h}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=i.stdout?U():F(e,{overwrite:i.overwrite});let w=this.fileSink,P=[],y=await new Promise((p,m)=>{let u=null;l.addEventListener("message",async S=>{if(typeof S.data=="string")try{let d=JSON.parse(S.data);if(d.type==="manifest"&&!u){u=d.transfer_id,this._activeTransferId=u;let g=d.manifest;this.emit("receiving-file",{name:g.fileName,size:g.fileSize}),await w.startStreamingReceive(u,{fileName:g.fileName,size:g.fileSize,fileSize:g.fileSize,type:g.fileType,pieceSize:g.pieceSize},{}),this.engine.initReceive(u,g,{streamingHandler:w}),this._setupProgressEvents(u),this.engine.startReceive(u,_=>{this._safeSend(l,JSON.stringify({type:"request",transfer_id:u,piece_index:_}))});let C=-1;this._ackInterval=setInterval(()=>{let _=this.engine.transfers.get(u);if(!_||_.state==="completed"||_.state==="completing"){clearInterval(this._ackInterval);return}let k=_.receivedWaterLevel;if(k>C){C=k;let L=_.receivedOutOfOrder.size>0?Array.from(_.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:k,ooo:L}))}},100)}}catch(d){d.message&&!d.message.includes("Unexpected token")&&m(d)}}),f.addEventListener("message",async S=>{let d=S.data;if(!u||!(d instanceof ArrayBuffer||Buffer.isBuffer(d)))return;let g=d instanceof ArrayBuffer?d:d.buffer.slice(d.byteOffset,d.byteOffset+d.byteLength),{pieceIndex:C,data:_}=this.engine.decodePieceMessage(g);if(C>=0&&_&&(await this.engine.handlePieceData(u,C,_))?.complete){this._ackInterval&&clearInterval(this._ackInterval);let L=this.engine.transfers.get(u);L&&this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:L.receivedWaterLevel}));let G=w.getFilePath(u);P.push(G),await this.engine.completeReceive(u),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:u}));let O=this.engine.transfers.get(u),x=Date.now()-(O?.startTime||Date.now()),D=O?.manifest;p({files:P,speed:D?D.fileSize/(x/1e3):0,duration:x})}})});return this.signaling.disconnect(),this.transport.close(),y}async _createSession(t,e="agent_cli"){let i=`${this.serverUrl}/api/v1/sessions`,s={client_version:j,source:e};t&&(s.password=t);let r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let n=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${n.error||r.statusText}`)}return r.json()}async _joinSession(t,e,i="receiver",s=void 0){let r=`${this.serverUrl}/api/v1/sessions/${t}/join`,n={role:i,client_version:j};e&&(n.password=e),s&&(n.sender_key=s);let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok){let o=await a.json().catch(()=>({}));throw a.status===404?new Error(`Session ${t} not found. Check the code and try again.`):a.status===410?new Error(`Session ${t} has expired.`):a.status===403?new Error(o.error||"Access denied"):new Error(`Failed to join session: ${o.error||a.statusText}`)}return a.json()}_waitForPeer(){return new Promise((t,e)=>{let i=setTimeout(()=>{s(),e(new Error(`No peer connected within ${Math.round(this.peerTimeout/1e3)}s`))},this.peerTimeout),s=()=>{clearTimeout(i),this.signaling.removeListener("ready",r),this.signaling.removeListener("offer",r),this.signaling.removeListener("peer_joined",r)},r=n=>{(n.type==="ready"||n.type==="offer"||n.type==="peer_joined"&&(!n.role||n.role==="receiver"))&&(s(),t(n.from))};this.signaling.on("ready",r),this.signaling.on("offer",r),this.signaling.on("peer_joined",r)})}_waitForSignal(t,e=3e4,i=null){return new Promise((s,r)=>{let n=setTimeout(()=>{this.signaling.removeListener(t,a),r(new Error(`Timed out waiting for ${t} signal`))},e),a=o=>{i&&!i(o)||(this.signaling.removeListener(t,a),clearTimeout(n),s(o))};this.signaling.on(t,a)})}_setupProgressEvents(t){let e=setInterval(()=>{let i=this.engine.transfers.get(t);if(!i){clearInterval(e);return}let s=i.manifest,r=s.totalPieces,n;i.type==="send"?n=i.piecesAckedCount||0:n=i.piecesReceivedCount||0;let a=r>0?Math.round(n/r*100):0,o=Date.now()-(i.startTime||Date.now()),l=n*s.pieceSize,f=o>0?l/(o/1e3):0,h=f>0?Math.round((s.fileSize-l)/f):0;this.emit("progress",{transferId:t,percent:Math.min(a,100),speed:f,eta:h,bytesTransferred:l,totalBytes:s.fileSize}),(i.state==="completed"||i.state==="cancelled")&&clearInterval(e)},200)}_setupControlHandler(t,e){t.addEventListener("message",i=>{if(typeof i.data=="string")try{let s=JSON.parse(i.data);switch(s.type){case"ack":this.engine.handlePieceAck(e,s.piece_index);break;case"ack_wl":this.engine.handleWaterLevelAck(e,s.wl,s.ooo);break;case"water_level_ack":this.engine.handleWaterLevelAck(e,s.water_level,s.out_of_order);break;case"ack_batch":Array.isArray(s.pieces)&&s.pieces.length>0&&this.engine.handlePieceAckBatch(e,s.pieces);break;case"request":this.engine.requeuePiece(e,s.piece_index);break;case"complete":{let r=this.engine.transfers.get(e);r&&r.state!=="completed"&&(r.piecesAckedCount=r.manifest.totalPieces,r.bytesTransferred=r.manifest.fileSize,r.inFlightPieces.clear(),r.state="completed")}this.emit("transfer-complete",{transferId:e});break;case"backpressure":s.pause?this.engine.pause(e):this.engine.resume(e);break}}catch{}})}_waitForCompletion(t){return new Promise((e,i)=>{let s=()=>{let n=this.engine.transfers.get(t);if(!n){clearInterval(r),i(new Error("Transfer context lost"));return}if(n.state==="completed"){clearInterval(r);let a=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(a/1e3),duration:a});return}if(n.state==="cancelled"||n.state==="failed"){clearInterval(r),i(new Error("Transfer failed"));return}},r=setInterval(()=>{s()},500);this.on("transfer-complete",({transferId:n})=>{n===t&&s()})})}_safeSend(t,e){try{t.readyState==="open"&&t.send(e)}catch{}}destroy(){this._ackInterval&&clearInterval(this._ackInterval),this.fileSink&&this._activeTransferId&&(this.fileSink.abort(this._activeTransferId).catch(()=>{}),this.fileSink=null),this.fileSource&&this.fileSource.close().catch(()=>{}),this.signaling&&this.signaling.disconnect(),this.transport&&this.transport.close()}};export{v as NodeTransport,T as SignalingClient,M as TransferManager,F as createNodeFileSink,z as createNodeFileSource};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perkoon",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "File transfer for humans and the things replacing them. P2P CLI — send files directly between devices via WebRTC. No upload, no size limit. Agent-friendly: --json NDJSON output, --source attribution, --server override for self-hosted, deterministic exit codes.",
5
5
  "type": "module",
6
6
  "main": "dist/client.js",