perkoon 0.4.3 → 0.4.5

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,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import{EventEmitter as He}from"node:events";import I from"node:crypto";import Se from"node-datachannel";import{EventEmitter as ve}from"node:events";var{PeerConnection:Pe}=Se,B=class extends ve{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 Pe(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,o)=>{e({sdp:n,type:o})});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),o=c=>{c.readyState==="open"?(clearTimeout(n),s(c)):c.addEventListener("open",()=>{clearTimeout(n),s(c)})};if(i)o(i);else{let c=(l,u)=>{u===t&&(this.removeListener("data-channel",c),o(l))};this.on("data-channel",c)}})}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 o=setTimeout(()=>n(new Error(`Channel "${i}" open timeout`)),e);s.addEventListener("open",()=>{clearTimeout(o),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=(o,c)=>{c.startsWith(t)&&(this.removeListener("data-channel",n),o.readyState==="open"?(clearTimeout(r),i({channel:o,label:c})):o.addEventListener("open",()=>{clearTimeout(r),i({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,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,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){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 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 s}};import{Socket as Te}from"phoenix";import be from"ws";import{EventEmitter as Ce}from"node:events";var z=class extends Ce{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,o)=>{this.socket=new Te(r,{params:{},transport:be,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:i,role:s}),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,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 ke}from"node:fs/promises";import Re from"node:path";import{lookup as Ee}from"mime-types";async function te(a){let t=await ke(a,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${a}`);return{name:Re.basename(a),size:e.size,type:Ee(a)||"application/octet-stream",async readSlice(i,s){let r=s-i,n=Buffer.alloc(r),{bytesRead:o}=await t.read(n,0,r,i);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 Ae,mkdir as Le,access as ie}from"node:fs/promises";import se from"node:path";function ne(a,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await Le(a,{recursive:!0});let n=Me(s.fileName),o=se.join(a,n);t.overwrite||(o=await Fe(o));let c=await Ae(o,"w");s.size>0&&await c.truncate(s.size),e.set(i,{fh:c,filePath:o,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 o=s*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(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 Fe(a){try{await ie(a)}catch{return a}let t=se.extname(a),e=a.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await ie(s)}catch{return s}}return`${e}_${Date.now()}${t}`}function Me(a){return a.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function re(){let a=new Map;return{async startStreamingReceive(t,e,i){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,i){let s=a.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 o=s.nextPiece*s.pieceSize,c=s.size-o,l=c<n.byteLength?n.subarray(0,c):n;process.stdout.write(l),s.bytesWritten+=l.byteLength,s.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 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=o=>{r&&clearTimeout(r),i(o)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),s(new Error(`Timeout waiting for event: ${t}`))},e))})}};function Be(){if(typeof window>"u"||typeof navigator>"u")return ze();let a=Oe();return{browser:a.browser,version:a.version,platform:$e(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:xe(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:De(a.browser),recommendedChunkSize:Ue(a.browser),maxFileSize:We(a.browser),maxFileSizeLabel:qe(a.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:Ne(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function ze(){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 Oe(){let a=navigator.userAgent,t="unknown",e="0";if(a.includes("Edg/")){t="edge";let i=a.match(/Edg\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Chrome/")){t="chrome";let i=a.match(/Chrome\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Firefox/")){t="firefox";let i=a.match(/Firefox\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Safari/")&&!a.includes("Chrome")){t="safari";let i=a.match(/Version\/(\d+)/);e=i?i[1]:"0"}return{browser:t,version:parseInt(e,10)}}function $e(){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 xe(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function Ne(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function De(a){switch(a){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function Ue(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 We(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 qe(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 G=null;function oe(){return G||(G=Be()),G}var ae={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},ce={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function le(a){return ae[a]||ae.unknown}function fe(a){return ce[a]||ce.unknown}var W=class{constructor(t={}){this.pieceTimeoutMs=t.pieceTimeout??3e4,this.idleTimeoutMs=t.idleTimeout??6e4,this.maxPieceRetries=t.maxPieceRetries??3,this.graceFraction=t.pieceGraceFraction??.5,this.nearCompletionPieces=t.nearCompletionPieces??10,this.tailGraceMultiplier=t.tailGraceMultiplier??4,this.maxPieceRetriesTail=t.maxPieceRetriesTail??20}gracePeriodMs(){return this.pieceTimeoutMs*this.graceFraction}decidePieceTimeout({timeSinceActivityMs:t,piecesRemaining:e,retries:i}){let s=e<=this.nearCompletionPieces,r=s?this.gracePeriodMs()*this.tailGraceMultiplier:this.gracePeriodMs(),n=s?this.maxPieceRetriesTail:this.maxPieceRetries;return t<r?{action:"grace"}:(i||0)<n?{action:"retry"}:{action:"fail"}}isIdleExpired(t){return t>=this.idleTimeoutMs}};var je={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxConcurrentReServes:8,reServeContextTtlMs:12e4,maxPieceRetries:3,maxPieceEscalations:3,pieceGraceFraction:.5,nearCompletionPieces:10,tailGraceMultiplier:4,maxPieceRetriesTail:20,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},q=class extends U{constructor(t={}){super(),this.config={...je,...t},this.timeoutPolicy=new W(this.config),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,reServeInFlight:0,reServePending:new Set,reServeCleanupTimer:null};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 o=this._calculateProgress(i);if(this._checkProgressMilestones(t,i,o),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);if(!(!i||i.type!=="send")&&!(!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces)&&i.state!=="completing"){if(i.state==="completed"){this._reServePiece(t,e);return}i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}}async _reServePiece(t,e){let i=this.transfers.get(t);if(!i||!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces||i.reServeInFlight>=this.config.maxConcurrentReServes||i.reServePending.has(e))return;i.reServePending.add(e);let s=i.channel;if(!s||s.readyState!=="open"){i.reServePending.delete(e);return}i.reServeInFlight++;try{let r=await this._readPiece(i.file,i.manifest,e),n=this.transfers.get(t);n&&n.channel?.readyState==="open"&&n.channel.send(this._encodePieceMessage(t,e,r))}catch{}finally{this.transfers.has(t)&&(i.reServeInFlight--,i.reServePending.delete(e))}}_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=oe(),r=e.fileSize,n=le(s.browser);if(r>n){let c=fe(s.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${s.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: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,pieceEscalations:new Map};return o.streamingHandler||this._initReceiveBuffer(o,e),this.transfers.set(t,o),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(o)}}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 c=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 u=await s.streamingHandler.writeChunk(t,e,new Uint8Array(i));if(u===!1||u&&u.success===!1)return{success:!1,error:u&&u.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 f=!1;for(let S of e.channels)if(S.readyState==="open"&&S.bufferedAmount<=this.config.highWaterMark){f=!0;break}if(!f)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 f=e.retryQueue.shift();e.inFlightPieces.has(f)||c.push(f)}for(;e.nextPieceToSend<e.manifest.totalPieces&&c.length<o;){let f=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(f)||c.push(f)}if(c.length===0)return;let l=Date.now();for(let f of c)e.inFlightPieces.set(f,l);let u=r?4:4*e.channels.length;try{for(let f=0;f<c.length;f+=u){let L=c.slice(f,f+u).map(async v=>{let g=await this._readPiece(e.file,e.manifest,v);return{pieceIndex:v,data:g}}),F=await Promise.all(L);if(r)for(let{pieceIndex:v,data:g}of F){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.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(f){for(let S of c)e.inFlightPieces.delete(S),e.retryQueue.push(S);this.emit("error",{transferId:t,error:`Failed to send pieces: ${f.message}`})}}confirmSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||(e.bytesTransferred=e.manifest.fileSize,e.piecesAckedCount=e.manifest.totalPieces,e.inFlightPieces.clear(),this._completeSend(t))}_completeSend(t){let e=this.transfers.get(t);!e||e.state==="completed"||e.state==="completing"||(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),e.reServeCleanupTimer=setTimeout(()=>{this._cleanupTransfer(t)},this.config.reServeContextTtlMs),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 o=this.config.maxPendingReads-e.pendingReads;if(o<=0)return;n=Math.min(n,o);let c=this.config.maxInFlightPieces-i;n=Math.min(n,c);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let f=e.retryQueue.shift();e.inFlightPieces.has(f)||l.push(f)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let f=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(f)||l.push(f)}if(l.length===0){let f=e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.pendingReads===0;f&&e.inFlightPieces.size===0?this._stopPacingLoop(t):f&&s.bufferedAmount===0&&(this._stopPacingLoop(t),this._completeSend(t));return}e.pendingReads+=l.length;let u=Date.now();for(let f of l)e.inFlightPieces.set(f,u);this._readAndSendPieces(t,e,s,l)}async _readAndSendPieces(t,e,i,s){try{let r=s.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,u=e.channel;if(!u||u.readyState!=="open"){e.inFlightPieces.delete(c),e.retryQueue.push(c);continue}if(u.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(c),e.retryQueue.unshift(c);continue}try{u.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 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);if(i.length>0){e.lastActivityTime=Date.now();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 c=0;c<e.totalPieces;c++){let l=s.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 s.clear(),o}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),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 i=Date.now();if(i-(e.lastProgressEmit||0)<100)return;let s=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||i,o=i-n;if(o>=200){let u=e.bytesTransferred-r;s=o>0?u/o*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 c=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=s:s>0&&(e.smoothedSpeed=c*s+(1-c)*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 o=s-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?s-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let o=e.startTime?s-e.startTime:0;n=o>0?e.bytesTransferred/o*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.timeoutPolicy.pieceTimeoutMs);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=i.manifest.totalPieces-i.piecesReceivedCount,n=i.pieceRetries.get(e)||0,{action:o}=this.timeoutPolicy.decidePieceTimeout({timeSinceActivityMs:s,piecesRemaining:r,retries:n});if(o==="grace"){this._startPieceTimeout(t,e);return}o==="retry"?(i.pieceRetries.set(e,n+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:n+1}),i.type==="receive"?(i.lastActivityTime=Date.now(),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.type==="send"?i.inFlightPieces.delete(e):i.piecesRequested.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}reRequestPiece(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive"||i.state!=="transferring"||i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e)))return!1;let s=i.pieceEscalations.get(e)||0;return s>=this.config.maxPieceEscalations?!1:(i.pieceEscalations.set(e,s+1),i.pieceRetries.delete(e),i.lastActivityTime=Date.now(),i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e),!0)}_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;this.timeoutPolicy.isIdleExpired(s)?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.timeoutPolicy.idleTimeoutMs))}_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(e.reServeCleanupTimer&&(clearTimeout(e.reServeCleanupTimer),e.reServeCleanupTimer=null),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 ue="0.4.3",j="https://perkoon.com",O=class extends He{constructor(t=j,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new q,this.transport=null,this.signaling=null,this.fileSource=null,this.engine.on("idle-timeout",({transferId:i})=>{this._failTransfer(i,"Transfer stalled (idle timeout)")}),this.engine.on("piece-failed",({transferId:i,pieceIndex:s,error:r})=>{s!==void 0&&this.engine.reRequestPiece(i,s)||this._failTransfer(i,r||`Piece ${s} unrecoverable`)})}_failTransfer(t,e){let i=this.engine.transfers.get(t);!i||i.state==="completed"||i.state==="failed"||(i.state="failed",this.emit("transfer-error",{transferId:t,error:e}))}async send(t,e={}){this.fileSource=await te(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 z(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()}_${I.randomUUID().replace(/-/g,"").slice(0,9)}`,o=[{id:`file_${Date.now()}_${I.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()}_${I.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new B,this.transport.initialize(i.peer_id,i.ice_servers);let u=await this.transport.createOffer(l);this.signaling.sendOffer(r,u),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 f=await this._waitForSignal("answer");this.transport.handleAnswer(f.answer);let S=await this.transport.waitForChannel("control"),L=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,L);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:i.session_code,speed:g.speed,duration:g.duration}}async receive(t,e,i={}){let s=await this._joinSession(t,i.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new z(this.serverUrl);let{peers:r}=await this.signaling.connect(t,s.token,s.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(s.peer_id,s.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:u,label:f}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=i.stdout?re():ne(e,{overwrite:i.overwrite});let S=this.fileSink,L=[],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)}}),u.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 _e=S.getFilePath(h);L.push(_e),await this.engine.completeReceive(h),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:h}));let Z=this.engine.transfers.get(h),Y=Date.now()-(Z?.startTime||Date.now()),ee=Z?.manifest;g({files:L,speed:ee?ee.fileSize/(Y/1e3):0,duration:Y})}})});return this.signaling.disconnect(),this.transport.close(),v}async _createSession(t,e="agent_cli"){let i=`${this.serverUrl}/api/v1/sessions`,s={client_version:ue,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:ue};e&&(n.password=e),s&&(n.sender_key=s);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 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,o),r(new Error(`Timed out waiting for ${t} signal`))},e),o=c=>{i&&!i(c)||(this.signaling.removeListener(t,o),clearTimeout(n),s(c))};this.signaling.on(t,o)})}_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 o=r>0?Math.round(n/r*100):0,c=Date.now()-(i.startTime||Date.now()),l=n*s.pieceSize,u=c>0?l/(c/1e3):0,f=u>0?Math.round((s.fileSize-l)/u):0;this.emit("progress",{transferId:t,percent:Math.min(o,100),speed:u,eta:f,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":this.engine.confirmSendComplete(e),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 o=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(o/1e3),duration:o});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()}};import Qe from"node:path";import{access as Ge,stat as Ie}from"node:fs/promises";var he="0.4.3",me=0,E=1,de=2,J=3,Je=4,Ve=5,k=process.argv.slice(2),pe=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||j,A=p.json===!0,x=p.quiet===!0,Xe=p.overwrite===!0,X=p.output||"./received",$=typeof p.password=="string"?p.password:void 0,Ke=typeof p.session=="string"?p.session:void 0,Ze=typeof p["sender-key"]=="string"?p["sender-key"]:void 0,Ye=typeof p.source=="string"?p.source:void 0,et=Ye||process.env.PERKOON_SOURCE||"agent_cli",V=typeof p.timeout=="string"?parseInt(p.timeout,10):NaN,K=!isNaN(V)&&V>0?V:300;A&&X==="-"&&(process.stderr.write(` Error: --json and --output - are mutually exclusive
2
+ import{EventEmitter as He}from"node:events";import J from"node:crypto";import Se from"node-datachannel";import{EventEmitter as Pe}from"node:events";var{PeerConnection:ve}=Se,B=class extends Pe{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 ve(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,o)=>{e({sdp:n,type:o})});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),o=c=>{c.readyState==="open"?(clearTimeout(n),s(c)):c.addEventListener("open",()=>{clearTimeout(n),s(c)})};if(i)o(i);else{let c=(l,u)=>{u===t&&(this.removeListener("data-channel",c),o(l))};this.on("data-channel",c)}})}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 o=setTimeout(()=>n(new Error(`Channel "${i}" open timeout`)),e);s.addEventListener("open",()=>{clearTimeout(o),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=(o,c)=>{c.startsWith(t)&&(this.removeListener("data-channel",n),o.readyState==="open"?(clearTimeout(r),i({channel:o,label:c})):o.addEventListener("open",()=>{clearTimeout(r),i({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,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,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){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 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 s}};import{Socket as Te}from"phoenix";import be from"ws";import{EventEmitter as Ce}from"node:events";var z=class extends Ce{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,o)=>{this.socket=new Te(r,{params:{},transport:be,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:i,role:s}),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,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 ke}from"node:fs/promises";import Re from"node:path";import{lookup as Ee}from"mime-types";async function te(a){let t=await ke(a,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${a}`);return{name:Re.basename(a),size:e.size,type:Ee(a)||"application/octet-stream",async readSlice(i,s){let r=s-i,n=Buffer.alloc(r),{bytesRead:o}=await t.read(n,0,r,i);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 Ae,mkdir as Le,access as ie}from"node:fs/promises";import se from"node:path";function ne(a,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await Le(a,{recursive:!0});let n=Fe(s.fileName),o=se.join(a,n);t.overwrite||(o=await Me(o));let c=await Ae(o,"w");s.size>0&&await c.truncate(s.size),e.set(i,{fh:c,filePath:o,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 o=s*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(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 Me(a){try{await ie(a)}catch{return a}let t=se.extname(a),e=a.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await ie(s)}catch{return s}}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 re(){let a=new Map;return{async startStreamingReceive(t,e,i){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,i){let s=a.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 o=s.nextPiece*s.pieceSize,c=s.size-o,l=c<n.byteLength?n.subarray(0,c):n;process.stdout.write(l),s.bytesWritten+=l.byteLength,s.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 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=o=>{r&&clearTimeout(r),i(o)};this.once(t,n),e&&(r=setTimeout(()=>{this.off(t,n),s(new Error(`Timeout waiting for event: ${t}`))},e))})}};function Be(){if(typeof window>"u"||typeof navigator>"u")return ze();let a=Oe();return{browser:a.browser,version:a.version,platform:$e(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:xe(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:De(a.browser),recommendedChunkSize:Ue(a.browser),maxFileSize:We(a.browser),maxFileSizeLabel:qe(a.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:Ne(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function ze(){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 Oe(){let a=navigator.userAgent,t="unknown",e="0";if(a.includes("Edg/")){t="edge";let i=a.match(/Edg\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Chrome/")){t="chrome";let i=a.match(/Chrome\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Firefox/")){t="firefox";let i=a.match(/Firefox\/(\d+)/);e=i?i[1]:"0"}else if(a.includes("Safari/")&&!a.includes("Chrome")){t="safari";let i=a.match(/Version\/(\d+)/);e=i?i[1]:"0"}return{browser:t,version:parseInt(e,10)}}function $e(){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 xe(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function Ne(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function De(a){switch(a){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function Ue(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 We(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 qe(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 Q=null;function oe(){return Q||(Q=Be()),Q}var ae={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},ce={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function le(a){return ae[a]||ae.unknown}function fe(a){return ce[a]||ce.unknown}var W=class{constructor(t={}){this.pieceTimeoutMs=t.pieceTimeout??3e4,this.idleTimeoutMs=t.idleTimeout??6e4,this.maxPieceRetries=t.maxPieceRetries??3,this.graceFraction=t.pieceGraceFraction??.5,this.nearCompletionPieces=t.nearCompletionPieces??10,this.tailGraceMultiplier=t.tailGraceMultiplier??4,this.maxPieceRetriesTail=t.maxPieceRetriesTail??20}gracePeriodMs(){return this.pieceTimeoutMs*this.graceFraction}decidePieceTimeout({timeSinceActivityMs:t,piecesRemaining:e,retries:i}){let s=e<=this.nearCompletionPieces,r=s?this.gracePeriodMs()*this.tailGraceMultiplier:this.gracePeriodMs(),n=s?this.maxPieceRetriesTail:this.maxPieceRetries;return t<r?{action:"grace"}:(i||0)<n?{action:"retry"}:{action:"fail"}}isIdleExpired(t){return t>=this.idleTimeoutMs}};var je={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,postSendGraceMs:5e3,maxConcurrentReServes:8,reServeContextTtlMs:12e4,maxPieceRetries:3,maxPieceEscalations:3,pieceGraceFraction:.5,nearCompletionPieces:10,tailGraceMultiplier:4,maxPieceRetriesTail:20,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},q=class extends U{constructor(t={}){super(),this.config={...je,...t},this.timeoutPolicy=new W(this.config),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,reServeInFlight:0,reServePending:new Set,reServeCleanupTimer:null};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 o=this._calculateProgress(i);if(this._checkProgressMilestones(t,i,o),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);typeof e=="number"&&e>=s.manifest.totalPieces-1&&this.confirmSendComplete(t)}}requeuePiece(t,e){let i=this.transfers.get(t);if(!(!i||i.type!=="send")&&!(!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces)&&i.state!=="completing"){if(i.state==="completed"){this._reServePiece(t,e);return}i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}}async _reServePiece(t,e){let i=this.transfers.get(t);if(!i||!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces||i.reServeInFlight>=this.config.maxConcurrentReServes||i.reServePending.has(e))return;i.reServePending.add(e);let s=i.channel;if(!s||s.readyState!=="open"){i.reServePending.delete(e);return}i.reServeInFlight++;try{let r=await this._readPiece(i.file,i.manifest,e),n=this.transfers.get(t);n&&n.channel?.readyState==="open"&&n.channel.send(this._encodePieceMessage(t,e,r))}catch{}finally{this.transfers.has(t)&&(i.reServeInFlight--,i.reServePending.delete(e))}}_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=oe(),r=e.fileSize,n=le(s.browser);if(r>n){let c=fe(s.browser),l=(r/(1024*1024*1024)).toFixed(2);throw new Error(`FILE_TOO_LARGE: ${l}GB file exceeds ${s.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: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,pieceEscalations:new Map};return o.streamingHandler||this._initReceiveBuffer(o,e),this.transfers.set(t,o),{transferId:t,piecesToRequest:this._getNextPiecesToRequest(o)}}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 c=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 u=await s.streamingHandler.writeChunk(t,e,new Uint8Array(i));if(u===!1||u&&u.success===!1)return{success:!1,error:u&&u.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 f=!1;for(let S of e.channels)if(S.readyState==="open"&&S.bufferedAmount<=this.config.highWaterMark){f=!0;break}if(!f)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 f=e.retryQueue.shift();e.inFlightPieces.has(f)||c.push(f)}for(;e.nextPieceToSend<e.manifest.totalPieces&&c.length<o;){let f=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(f)||c.push(f)}if(c.length===0)return;let l=Date.now();for(let f of c)e.inFlightPieces.set(f,l);let u=r?4:4*e.channels.length;try{for(let f=0;f<c.length;f+=u){let L=c.slice(f,f+u).map(async P=>{let g=await this._readPiece(e.file,e.manifest,P);return{pieceIndex:P,data:g}}),M=await Promise.all(L);if(r)for(let{pieceIndex:P,data:g}of M){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.send(this._encodePieceMessage(t,P,g)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,P)}else for(let{pieceIndex:P,data:g}of M){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,P,g)),e.inFlightPieces.set(P,{sendTime:Date.now(),channelIndex:h}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,P)}}}catch(f){for(let S of c)e.inFlightPieces.delete(S),e.retryQueue.push(S);this.emit("error",{transferId:t,error:`Failed to send pieces: ${f.message}`})}}confirmSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||(e.bytesTransferred=e.manifest.fileSize,e.piecesAckedCount=e.manifest.totalPieces,e.inFlightPieces.clear(),this._completeSend(t))}_completeSend(t){let e=this.transfers.get(t);!e||e.state==="completed"||e.state==="completing"||(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),e.reServeCleanupTimer=setTimeout(()=>{this._cleanupTransfer(t)},this.config.reServeContextTtlMs),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)}_allPiecesWritten(t){return t.nextPieceToSend>=t.manifest.totalPieces&&t.retryQueue.length===0&&(t.pendingReads||0)===0}_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"){this._allPiecesWritten(e)&&(this._stopPacingLoop(t),this._completeSend(t));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 o=this.config.maxPendingReads-e.pendingReads;if(o<=0)return;n=Math.min(n,o);let c=this.config.maxInFlightPieces-i;n=Math.min(n,c);let l=[];for(;e.retryQueue.length>0&&l.length<n;){let f=e.retryQueue.shift();e.inFlightPieces.has(f)||l.push(f)}for(;e.nextPieceToSend<e.manifest.totalPieces&&l.length<n;){let f=e.nextPieceToSend;e.nextPieceToSend++,e.inFlightPieces.has(f)||l.push(f)}if(l.length===0){let f=this._allPiecesWritten(e);f&&e.inFlightPieces.size===0?this._stopPacingLoop(t):f&&s.bufferedAmount===0?(this._stopPacingLoop(t),this._completeSend(t)):f&&Date.now()-e.lastActivityTime>=this.config.postSendGraceMs&&(this._stopPacingLoop(t),this._completeSend(t));return}e.pendingReads+=l.length;let u=Date.now();for(let f of l)e.inFlightPieces.set(f,u);this._readAndSendPieces(t,e,s,l)}async _readAndSendPieces(t,e,i,s){try{let r=s.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,u=e.channel;if(!u||u.readyState!=="open"){e.inFlightPieces.delete(c),e.retryQueue.push(c);continue}if(u.bufferedAmount>this.config.highWaterMark){e.inFlightPieces.delete(c),e.retryQueue.unshift(c);continue}try{u.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 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);if(i.length>0){e.lastActivityTime=Date.now();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 c=0;c<e.totalPieces;c++){let l=s.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 s.clear(),o}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),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 i=Date.now();if(i-(e.lastProgressEmit||0)<100)return;let s=0,r=e.lastSpeedBytes||0,n=e.lastSpeedTime||e.startTime||i,o=i-n;if(o>=200){let u=e.bytesTransferred-r;s=o>0?u/o*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 c=.15;e.smoothedSpeed===void 0||e.smoothedSpeed===0?e.smoothedSpeed=s:s>0&&(e.smoothedSpeed=c*s+(1-c)*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 o=s-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?s-e.startTime:0;n=l>0?e.bytesTransferred/l*1e3/1e6:0}}else{let o=e.startTime?s-e.startTime:0;n=o>0?e.bytesTransferred/o*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.timeoutPolicy.pieceTimeoutMs);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=i.manifest.totalPieces-i.piecesReceivedCount,n=i.pieceRetries.get(e)||0,{action:o}=this.timeoutPolicy.decidePieceTimeout({timeSinceActivityMs:s,piecesRemaining:r,retries:n});if(o==="grace"){this._startPieceTimeout(t,e);return}o==="retry"?(i.pieceRetries.set(e,n+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:n+1}),i.type==="receive"?(i.lastActivityTime=Date.now(),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.type==="send"?i.inFlightPieces.delete(e):i.piecesRequested.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}reRequestPiece(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive"||i.state!=="transferring"||i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e)))return!1;let s=i.pieceEscalations.get(e)||0;return s>=this.config.maxPieceEscalations?!1:(i.pieceEscalations.set(e,s+1),i.pieceRetries.delete(e),i.lastActivityTime=Date.now(),i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e),!0)}_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;this.timeoutPolicy.isIdleExpired(s)?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.timeoutPolicy.idleTimeoutMs))}_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(e.reServeCleanupTimer&&(clearTimeout(e.reServeCleanupTimer),e.reServeCleanupTimer=null),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 ue="0.4.5",j="https://perkoon.com",O=class extends He{constructor(t=j,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new q,this.transport=null,this.signaling=null,this.fileSource=null,this.engine.on("idle-timeout",({transferId:i})=>{this._failTransfer(i,"Transfer stalled (idle timeout)")}),this.engine.on("piece-failed",({transferId:i,pieceIndex:s,error:r})=>{s!==void 0&&this.engine.reRequestPiece(i,s)||this._failTransfer(i,r||`Piece ${s} unrecoverable`)})}_failTransfer(t,e){let i=this.engine.transfers.get(t);!i||i.state==="completed"||i.state==="failed"||(i.state="failed",this.emit("transfer-error",{transferId:t,error:e}))}async send(t,e={}){this.fileSource=await te(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 z(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()}_${J.randomUUID().replace(/-/g,"").slice(0,9)}`,o=[{id:`file_${Date.now()}_${J.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()}_${J.randomUUID().replace(/-/g,"").slice(0,9)}`;this.transport=new B,this.transport.initialize(i.peer_id,i.ice_servers);let u=await this.transport.createOffer(l);this.signaling.sendOffer(r,u),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 f=await this._waitForSignal("answer");this.transport.handleAnswer(f.answer);let S=await this.transport.waitForChannel("control"),L=await this.transport.waitForChannel(`transfer-${l}`);this.emit("connected"),this.engine.initSend(l,this.fileSource,L);let M=this.engine.transfers.get(l).manifest;this._setupControlHandler(S,l);let P=JSON.stringify({type:"manifest",transfer_id:l,manifest:M,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});S.send(P),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:i.session_code,speed:g.speed,duration:g.duration}}async receive(t,e,i={}){let s=await this._joinSession(t,i.password);this.emit("session-joined",{sessionCode:t}),this.signaling=new z(this.serverUrl);let{peers:r}=await this.signaling.connect(t,s.token,s.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(s.peer_id,s.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:u,label:f}=await this.transport.waitForChannelByPrefix("transfer-");this.emit("connected"),this.fileSink=i.stdout?re():ne(e,{overwrite:i.overwrite});let S=this.fileSink,L=[],P=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 F=-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>F){F=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)}}),u.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:F,data:b}=this.engine.decodePieceMessage(_);if(F>=0&&b&&(await this.engine.handlePieceData(h,F,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 _e=S.getFilePath(h);L.push(_e),await this.engine.completeReceive(h),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:h}));let Z=this.engine.transfers.get(h),Y=Date.now()-(Z?.startTime||Date.now()),ee=Z?.manifest;g({files:L,speed:ee?ee.fileSize/(Y/1e3):0,duration:Y})}})});return await this._drainChannel(l,2e3),this.signaling.disconnect(),this.transport.close(),P}async _createSession(t,e="agent_cli"){let i=`${this.serverUrl}/api/v1/sessions`,s={client_version:ue,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:ue};e&&(n.password=e),s&&(n.sender_key=s);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 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,o),r(new Error(`Timed out waiting for ${t} signal`))},e),o=c=>{i&&!i(c)||(this.signaling.removeListener(t,o),clearTimeout(n),s(c))};this.signaling.on(t,o)})}_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 o=r>0?Math.round(n/r*100):0,c=Date.now()-(i.startTime||Date.now()),l=n*s.pieceSize,u=c>0?l/(c/1e3):0,f=u>0?Math.round((s.fileSize-l)/u):0;this.emit("progress",{transferId:t,percent:Math.min(o,100),speed:u,eta:f,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":this.engine.confirmSendComplete(e),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 o=Date.now()-n.startTime;e({speed:n.manifest.fileSize/(o/1e3),duration:o});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{}}async _drainChannel(t,e){if(!t)return;let i=Date.now()+e;for(;Date.now()<i;){let s=t.bufferedAmount;if(s===void 0||s===0)break;await new Promise(r=>setTimeout(r,25))}await new Promise(s=>setTimeout(s,150))}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 Ge from"node:path";import{access as Qe,stat as Je}from"node:fs/promises";var he="0.4.5",me=0,E=1,de=2,V=3,Ve=4,Xe=5,k=process.argv.slice(2),pe=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||j,A=p.json===!0,x=p.quiet===!0,Ke=p.overwrite===!0,K=p.output||"./received",$=typeof p.password=="string"?p.password:void 0,Ie=typeof p.session=="string"?p.session:void 0,Ze=typeof p["sender-key"]=="string"?p["sender-key"]:void 0,Ye=typeof p.source=="string"?p.source:void 0,et=Ye||process.env.PERKOON_SOURCE||"agent_cli",X=typeof p.timeout=="string"?parseInt(p.timeout,10):NaN,I=!isNaN(X)&&X>0?X:300;A&&K==="-"&&(process.stderr.write(` Error: --json and --output - are mutually exclusive
3
3
  `),process.exit(E));function d(a){!x&&!A&&process.stderr.write(a+`
4
4
  `)}function y(a,t={}){A&&process.stdout.write(JSON.stringify({event:a,...t})+`
5
- `)}function P(a){d(` \u2713 ${a}`)}function H(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 Q(a){return H(a)+"/s"}function ge(a){return a<=0?"...":a<60?`${a}s`:`${Math.floor(a/60)}m ${a%60}s`}function we(a,t=30){let e=Math.round(t*a/100);return"\u2588".repeat(e)+"\u2591".repeat(t-e)}function T(a,t){d(`
5
+ `)}function v(a){d(` \u2713 ${a}`)}function H(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 G(a){return H(a)+"/s"}function ge(a){return a<=0?"...":a<60?`${a}s`:`${Math.floor(a/60)}m ${a%60}s`}function we(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 ye(a){let t=a.message||"";return t.includes("No peer connected")||t.includes("timed out")?Ve:t.includes("Password required")||t.includes("Invalid password")||t.includes("Access denied")?Je:(t.includes("Failed to create session")||t.includes("Failed to join")||t.includes("not found")||t.includes("expired"),J)}async function tt(){let a=p._positional;a||(T("No file specified.","Usage: perkoon send <file>"),process.exit(E));let t=Qe.resolve(a);try{await Ge(t),(await Ie(t)).isFile()||(T(`Not a regular file: ${t}`),process.exit(de))}catch{T(`File not found: ${t}`),process.exit(de)}let e=new O(R,{timeout:K*1e3});e.on("file-ready",({name:r,size:n})=>{d(""),P(`${r} (${H(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 i="";e.on("progress",({percent:r,speed:n,eta:o,bytesTransferred:c})=>{if(A)y("progress",{percent:r,speed:Math.round(n),eta:o,bytes_transferred:c});else if(!x){let l=` ${we(r)} ${String(r).padStart(3)}% ${Q(n).padStart(10)} ETA ${ge(o)}`;l!==i&&(process.stderr.write(`\r${l}`),i=l)}});let s=()=>{d(`
7
+ ${t}`),d("")}function ye(a){let t=a.message||"";return t.includes("No peer connected")||t.includes("timed out")?Xe:t.includes("Password required")||t.includes("Invalid password")||t.includes("Access denied")?Ve:(t.includes("Failed to create session")||t.includes("Failed to join")||t.includes("not found")||t.includes("expired"),V)}async function tt(){let a=p._positional;a||(T("No file specified.","Usage: perkoon send <file>"),process.exit(E));let t=Ge.resolve(a);try{await Qe(t),(await Je(t)).isFile()||(T(`Not a regular file: ${t}`),process.exit(de))}catch{T(`File not found: ${t}`),process.exit(de)}let e=new O(R,{timeout:I*1e3});e.on("file-ready",({name:r,size:n})=>{d(""),v(`${r} (${H(n)})`),y("file_ready",{name:r,size:n})}),e.on("session-created",({sessionCode:r})=>{v(`Code: ${r}`),$&&v("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",()=>{v("Receiver connected"),y("receiver_connected")}),e.on("waiting-for-acceptance",()=>{d(" Waiting for receiver to accept..."),y("waiting_for_acceptance")}),e.on("transfer-accepted",()=>{v("Transfer accepted"),y("transfer_accepted")}),e.on("connected",()=>{v("Direct connection established"),d(""),y("webrtc_connected")});let i="";e.on("progress",({percent:r,speed:n,eta:o,bytesTransferred:c})=>{if(A)y("progress",{percent:r,speed:Math.round(n),eta:o,bytes_transferred:c});else if(!x){let l=` ${we(r)} ${String(r).padStart(3)}% ${G(n).padStart(10)} ETA ${ge(o)}`;l!==i&&(process.stderr.write(`\r${l}`),i=l)}});let s=()=>{d(`
8
8
 
9
- Cancelled.`),e.destroy(),process.exit(E)};process.on("SIGINT",s),process.on("SIGTERM",s);try{let r=await e.send(t,{password:$,session:Ke,senderKey:Ze,source:et});if(!x&&!A){process.stderr.write(`
10
- `);let n=H(r.speed*(r.duration/1e3));P(`Complete: ${n} in ${(r.duration/1e3).toFixed(1)}s (${Q(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(me)}catch(r){let n=ye(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: ${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 it(){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=X==="-",e=a.toUpperCase(),i=new O(R,{timeout:K*1e3});i.on("session-joined",()=>{d(""),P(`Joined session ${e}`),y("session_joined",{session_code:e})}),i.on("sender-found",()=>{P("Sender found"),y("sender_found")}),i.on("connected",()=>{P("Direct connection established"),y("webrtc_connected")}),i.on("receiving-file",({name:n,size:o})=>{P(`Receiving: ${n} (${H(o)})`),d(""),y("receiving_file",{name:n,size:o})});let s="";i.on("progress",({percent:n,speed:o,eta:c,bytesTransferred:l})=>{if(A)y("progress",{percent:n,speed:Math.round(o),eta:c,bytes_transferred:l});else if(!x){let u=` ${we(n)} ${String(n).padStart(3)}% ${Q(o).padStart(10)} ETA ${ge(c)}`;u!==s&&(process.stderr.write(`\r${u}`),s=u)}});let r=()=>{d(`
9
+ Cancelled.`),e.destroy(),process.exit(E)};process.on("SIGINT",s),process.on("SIGTERM",s);try{let r=await e.send(t,{password:$,session:Ie,senderKey:Ze,source:et});if(!x&&!A){process.stderr.write(`
10
+ `);let n=H(r.speed*(r.duration/1e3));v(`Complete: ${n} in ${(r.duration/1e3).toFixed(1)}s (${G(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(me)}catch(r){let n=ye(r);r.message.includes("No peer connected")?T(`No receiver joined after ${I}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 it(){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=K==="-",e=a.toUpperCase(),i=new O(R,{timeout:I*1e3});i.on("session-joined",()=>{d(""),v(`Joined session ${e}`),y("session_joined",{session_code:e})}),i.on("sender-found",()=>{v("Sender found"),y("sender_found")}),i.on("connected",()=>{v("Direct connection established"),y("webrtc_connected")}),i.on("receiving-file",({name:n,size:o})=>{v(`Receiving: ${n} (${H(o)})`),d(""),y("receiving_file",{name:n,size:o})});let s="";i.on("progress",({percent:n,speed:o,eta:c,bytesTransferred:l})=>{if(A)y("progress",{percent:n,speed:Math.round(o),eta:c,bytes_transferred:l});else if(!x){let u=` ${we(n)} ${String(n).padStart(3)}% ${G(o).padStart(10)} ETA ${ge(c)}`;u!==s&&(process.stderr.write(`\r${u}`),s=u)}});let r=()=>{d(`
12
12
 
13
- Cancelled.`),i.destroy(),process.exit(E)};process.on("SIGINT",r),process.on("SIGTERM",r);try{let n={overwrite:Xe,password:$};t&&(n.stdout=!0);let o=await i.receive(e,t?null:X,n);if(!x&&!A){if(process.stderr.write(`
14
- `),!t)for(let c of o.files)P(`Saved: ${c}`);P(`Complete: ${(o.duration/1e3).toFixed(1)}s (${Q(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(me)}catch(n){let o=ye(n);n.message.includes("not found")?T(`Session not found (${e})`,`The code may be expired or mistyped.
13
+ Cancelled.`),i.destroy(),process.exit(E)};process.on("SIGINT",r),process.on("SIGTERM",r);try{let n={overwrite:Ke,password:$};t&&(n.stdout=!0);let o=await i.receive(e,t?null:K,n);if(!x&&!A){if(process.stderr.write(`
14
+ `),!t)for(let c of o.files)v(`Saved: ${c}`);v(`Complete: ${(o.duration/1e3).toFixed(1)}s (${G(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(me)}catch(n){let o=ye(n);n.message.includes("not found")?T(`Session not found (${e})`,`The code may be expired or mistyped.
15
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}),i.destroy(),process.exit(o)}}switch(pe){case"send":tt();break;case"receive":it();break;case"--version":case"-v":console.log(`perkoon v${he}`);break;case"--help":case"-h":case void 0:console.log(`
16
16
  perkoon v${he} \u2014 File transfer for humans and the things replacing them.
17
17
 
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{EventEmitter as _e}from"node:events";import B from"node:crypto";import X from"node-datachannel";import{EventEmitter as Z}from"node:events";var{PeerConnection:K}=X,v=class extends Z{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 K(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 Y}from"phoenix";import I from"ws";import{EventEmitter as ee}from"node:events";var T=class extends ee{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 Y(r,{params:{},transport:I,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 te}from"node:fs/promises";import ie from"node:path";import{lookup as se}from"mime-types";async function M(c){let t=await te(c,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${c}`);return{name:ie.basename(c),size:e.size,type:se(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 ne,mkdir as re,access as $}from"node:fs/promises";import U from"node:path";function E(c,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await re(c,{recursive:!0});let n=oe(s.fileName),a=U.join(c,n);t.overwrite||(a=await ae(a));let o=await ne(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 ae(c){try{await $(c)}catch{return c}let t=U.extname(c),e=c.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await $(s)}catch{return s}}return`${e}_${Date.now()}${t}`}function oe(c){return c.replace(/[/\\]/g,"_").replace(/\0/g,"").replace(/[<>:"|?*\x00-\x1f]/g,"_").replace(/^\.+/,"_").replace(/\s+/g,"_")||"unnamed_file"}function W(){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 k=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 ce(){if(typeof window>"u"||typeof navigator>"u")return le();let c=he();return{browser:c.browser,version:c.version,platform:fe(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:ue(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:pe(c.browser),recommendedChunkSize:me(c.browser),maxFileSize:ge(c.browser),maxFileSizeLabel:ye(c.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:de(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function le(){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 he(){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 fe(){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 ue(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function de(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function pe(c){switch(c){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function me(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 ge(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 ye(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 q(){return z||(z=ce()),z}var H={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},Q={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function j(c){return H[c]||H.unknown}function G(c){return Q[c]||Q.unknown}var A=class{constructor(t={}){this.pieceTimeoutMs=t.pieceTimeout??3e4,this.idleTimeoutMs=t.idleTimeout??6e4,this.maxPieceRetries=t.maxPieceRetries??3,this.graceFraction=t.pieceGraceFraction??.5,this.nearCompletionPieces=t.nearCompletionPieces??10,this.tailGraceMultiplier=t.tailGraceMultiplier??4,this.maxPieceRetriesTail=t.maxPieceRetriesTail??20}gracePeriodMs(){return this.pieceTimeoutMs*this.graceFraction}decidePieceTimeout({timeSinceActivityMs:t,piecesRemaining:e,retries:i}){let s=e<=this.nearCompletionPieces,r=s?this.gracePeriodMs()*this.tailGraceMultiplier:this.gracePeriodMs(),n=s?this.maxPieceRetriesTail:this.maxPieceRetries;return t<r?{action:"grace"}:(i||0)<n?{action:"retry"}:{action:"fail"}}isIdleExpired(t){return t>=this.idleTimeoutMs}};var we={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,maxConcurrentReServes:8,reServeContextTtlMs:12e4,maxPieceRetries:3,maxPieceEscalations:3,pieceGraceFraction:.5,nearCompletionPieces:10,tailGraceMultiplier:4,maxPieceRetriesTail:20,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},F=class extends k{constructor(t={}){super(),this.config={...we,...t},this.timeoutPolicy=new A(this.config),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,reServeInFlight:0,reServePending:new Set,reServeCleanupTimer:null};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);if(!(!i||i.type!=="send")&&!(!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces)&&i.state!=="completing"){if(i.state==="completed"){this._reServePiece(t,e);return}i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}}async _reServePiece(t,e){let i=this.transfers.get(t);if(!i||!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces||i.reServeInFlight>=this.config.maxConcurrentReServes||i.reServePending.has(e))return;i.reServePending.add(e);let s=i.channel;if(!s||s.readyState!=="open"){i.reServePending.delete(e);return}i.reServeInFlight++;try{let r=await this._readPiece(i.file,i.manifest,e),n=this.transfers.get(t);n&&n.channel?.readyState==="open"&&n.channel.send(this._encodePieceMessage(t,e,r))}catch{}finally{this.transfers.has(t)&&(i.reServeInFlight--,i.reServePending.delete(e))}}_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=q(),r=e.fileSize,n=j(s.browser);if(r>n){let o=G(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,pieceEscalations: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 y of e.channels)if(y.readyState==="open"&&y.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 w=>{let p=await this._readPiece(e.file,e.manifest,w);return{pieceIndex:w,data:p}}),b=await Promise.all(P);if(r)for(let{pieceIndex:w,data:p}of b){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.send(this._encodePieceMessage(t,w,p)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,w)}else for(let{pieceIndex:w,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,w,p)),e.inFlightPieces.set(w,{sendTime:Date.now(),channelIndex:u}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,w)}}}catch(h){for(let y of o)e.inFlightPieces.delete(y),e.retryQueue.push(y);this.emit("error",{transferId:t,error:`Failed to send pieces: ${h.message}`})}}confirmSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||(e.bytesTransferred=e.manifest.fileSize,e.piecesAckedCount=e.manifest.totalPieces,e.inFlightPieces.clear(),this._completeSend(t))}_completeSend(t){let e=this.transfers.get(t);!e||e.state==="completed"||e.state==="completing"||(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),e.reServeCleanupTimer=setTimeout(()=>{this._cleanupTransfer(t)},this.config.reServeContextTtlMs),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){let h=e.nextPieceToSend>=e.manifest.totalPieces&&e.retryQueue.length===0&&e.pendingReads===0;h&&e.inFlightPieces.size===0?this._stopPacingLoop(t):h&&s.bufferedAmount===0&&(this._stopPacingLoop(t),this._completeSend(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);if(i.length>0){e.lastActivityTime=Date.now();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.timeoutPolicy.pieceTimeoutMs);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=i.manifest.totalPieces-i.piecesReceivedCount,n=i.pieceRetries.get(e)||0,{action:a}=this.timeoutPolicy.decidePieceTimeout({timeSinceActivityMs:s,piecesRemaining:r,retries:n});if(a==="grace"){this._startPieceTimeout(t,e);return}a==="retry"?(i.pieceRetries.set(e,n+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:n+1}),i.type==="receive"?(i.lastActivityTime=Date.now(),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.type==="send"?i.inFlightPieces.delete(e):i.piecesRequested.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}reRequestPiece(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive"||i.state!=="transferring"||i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e)))return!1;let s=i.pieceEscalations.get(e)||0;return s>=this.config.maxPieceEscalations?!1:(i.pieceEscalations.set(e,s+1),i.pieceRetries.delete(e),i.lastActivityTime=Date.now(),i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e),!0)}_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;this.timeoutPolicy.isIdleExpired(s)?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.timeoutPolicy.idleTimeoutMs))}_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(e.reServeCleanupTimer&&(clearTimeout(e.reServeCleanupTimer),e.reServeCleanupTimer=null),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.3",Se="https://perkoon.com",O=class extends _e{constructor(t=Se,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new F,this.transport=null,this.signaling=null,this.fileSource=null,this.engine.on("idle-timeout",({transferId:i})=>{this._failTransfer(i,"Transfer stalled (idle timeout)")}),this.engine.on("piece-failed",({transferId:i,pieceIndex:s,error:r})=>{s!==void 0&&this.engine.reRequestPiece(i,s)||this._failTransfer(i,r||`Piece ${s} unrecoverable`)})}_failTransfer(t,e){let i=this.engine.transfers.get(t);!i||i.state==="completed"||i.state==="failed"||(i.state="failed",this.emit("transfer-error",{transferId:t,error:e}))}async send(t,e={}){this.fileSource=await M(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()}_${B.randomUUID().replace(/-/g,"").slice(0,9)}`,a=[{id:`file_${Date.now()}_${B.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()}_${B.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 y=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(y,l);let w=JSON.stringify({type:"manifest",transfer_id:l,manifest:b,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});y.send(w),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?W():E(e,{overwrite:i.overwrite});let y=this.fileSink,P=[],w=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 y.startStreamingReceive(u,{fileName:g.fileName,size:g.fileSize,fileSize:g.fileSize,type:g.fileType,pieceSize:g.pieceSize},{}),this.engine.initReceive(u,g,{streamingHandler:y}),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 R=_.receivedWaterLevel;if(R>C){C=R;let L=_.receivedOutOfOrder.size>0?Array.from(_.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:R,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 V=y.getFilePath(u);P.push(V),await this.engine.completeReceive(u),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:u}));let x=this.engine.transfers.get(u),D=Date.now()-(x?.startTime||Date.now()),N=x?.manifest;p({files:P,speed:N?N.fileSize/(D/1e3):0,duration:D})}})});return this.signaling.disconnect(),this.transport.close(),w}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":this.engine.confirmSendComplete(e),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,O as TransferManager,E as createNodeFileSink,M as createNodeFileSource};
1
+ import{EventEmitter as _e}from"node:events";import B from"node:crypto";import X from"node-datachannel";import{EventEmitter as Z}from"node:events";var{PeerConnection:K}=X,v=class extends Z{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 K(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 Y}from"phoenix";import I from"ws";import{EventEmitter as ee}from"node:events";var T=class extends ee{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 Y(r,{params:{},transport:I,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 te}from"node:fs/promises";import ie from"node:path";import{lookup as se}from"mime-types";async function F(c){let t=await te(c,"r"),e=await t.stat();if(!e.isFile())throw await t.close(),new Error(`Not a regular file: ${c}`);return{name:ie.basename(c),size:e.size,type:se(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 ne,mkdir as re,access as $}from"node:fs/promises";import W from"node:path";function E(c,t={}){let e=new Map;return{async startStreamingReceive(i,s,r){await re(c,{recursive:!0});let n=oe(s.fileName),a=W.join(c,n);t.overwrite||(a=await ae(a));let o=await ne(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 ae(c){try{await $(c)}catch{return c}let t=W.extname(c),e=c.slice(0,-t.length||void 0);for(let i=1;i<1e3;i++){let s=`${e}_${i}${t}`;try{await $(s)}catch{return s}}return`${e}_${Date.now()}${t}`}function oe(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 L=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 ce(){if(typeof window>"u"||typeof navigator>"u")return le();let c=he();return{browser:c.browser,version:c.version,platform:fe(),supportsFileSystemAccess:"showSaveFilePicker"in window,supportsDirectoryPicker:"showDirectoryPicker"in window,supportsOPFS:ue(),supportsWebRTC:"RTCPeerConnection"in window,supportsDataChannel:"RTCPeerConnection"in window&&typeof RTCPeerConnection.prototype.createDataChannel=="function",maxChannelBufferSize:pe(c.browser),recommendedChunkSize:me(c.browser),maxFileSize:ge(c.browser),maxFileSizeLabel:ye(c.browser),supportsTransferable:typeof ArrayBuffer.prototype.transfer=="function"||typeof structuredClone=="function",supportsSharedArrayBuffer:typeof SharedArrayBuffer<"u",supportsStreams:"ReadableStream"in window&&"WritableStream"in window,isMemoryConstrained:de(),isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}}function le(){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 he(){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 fe(){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 ue(){return"storage"in navigator&&"getDirectory"in navigator.storage&&typeof FileSystemFileHandle<"u"&&"createSyncAccessHandle"in FileSystemFileHandle.prototype}function de(){return"deviceMemory"in navigator?navigator.deviceMemory<4:/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}function pe(c){switch(c){case"chrome":case"edge":return 256*1024*1024;default:return 16*1024*1024}}function me(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 ge(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 ye(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 q(){return z||(z=ce()),z}var H={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},Q={chrome:"Unlimited",edge:"Unlimited",brave:"Unlimited",opera:"Unlimited",vivaldi:"Unlimited",samsung:"Unlimited",ucbrowser:"Unlimited",firefox:"Unlimited",safari:"4GB",ios:"4GB",unknown:"2GB"};function G(c){return H[c]||H.unknown}function j(c){return Q[c]||Q.unknown}var A=class{constructor(t={}){this.pieceTimeoutMs=t.pieceTimeout??3e4,this.idleTimeoutMs=t.idleTimeout??6e4,this.maxPieceRetries=t.maxPieceRetries??3,this.graceFraction=t.pieceGraceFraction??.5,this.nearCompletionPieces=t.nearCompletionPieces??10,this.tailGraceMultiplier=t.tailGraceMultiplier??4,this.maxPieceRetriesTail=t.maxPieceRetriesTail??20}gracePeriodMs(){return this.pieceTimeoutMs*this.graceFraction}decidePieceTimeout({timeSinceActivityMs:t,piecesRemaining:e,retries:i}){let s=e<=this.nearCompletionPieces,r=s?this.gracePeriodMs()*this.tailGraceMultiplier:this.gracePeriodMs(),n=s?this.maxPieceRetriesTail:this.maxPieceRetries;return t<r?{action:"grace"}:(i||0)<n?{action:"retry"}:{action:"fail"}}isIdleExpired(t){return t>=this.idleTimeoutMs}};var we={pieceSize:84*1024,maxConcurrentPieces:8,pieceTimeout:3e4,idleTimeout:6e4,postSendGraceMs:5e3,maxConcurrentReServes:8,reServeContextTtlMs:12e4,maxPieceRetries:3,maxPieceEscalations:3,pieceGraceFraction:.5,nearCompletionPieces:10,tailGraceMultiplier:4,maxPieceRetriesTail:20,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},M=class extends L{constructor(t={}){super(),this.config={...we,...t},this.timeoutPolicy=new A(this.config),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,reServeInFlight:0,reServePending:new Set,reServeCleanupTimer:null};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);typeof e=="number"&&e>=s.manifest.totalPieces-1&&this.confirmSendComplete(t)}}requeuePiece(t,e){let i=this.transfers.get(t);if(!(!i||i.type!=="send")&&!(!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces)&&i.state!=="completing"){if(i.state==="completed"){this._reServePiece(t,e);return}i.inFlightPieces.delete(e),i.retryQueue.includes(e)||i.retryQueue.push(e),i.lastActivityTime=Date.now(),!i.paused&&!i.usePacing&&this._sendNextPieces(t)}}async _reServePiece(t,e){let i=this.transfers.get(t);if(!i||!Number.isInteger(e)||e<0||e>=i.manifest.totalPieces||i.reServeInFlight>=this.config.maxConcurrentReServes||i.reServePending.has(e))return;i.reServePending.add(e);let s=i.channel;if(!s||s.readyState!=="open"){i.reServePending.delete(e);return}i.reServeInFlight++;try{let r=await this._readPiece(i.file,i.manifest,e),n=this.transfers.get(t);n&&n.channel?.readyState==="open"&&n.channel.send(this._encodePieceMessage(t,e,r))}catch{}finally{this.transfers.has(t)&&(i.reServeInFlight--,i.reServePending.delete(e))}}_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=q(),r=e.fileSize,n=G(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,pieceEscalations: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 y of e.channels)if(y.readyState==="open"&&y.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 w=>{let p=await this._readPiece(e.file,e.manifest,w);return{pieceIndex:w,data:p}}),b=await Promise.all(P);if(r)for(let{pieceIndex:w,data:p}of b){if(s.readyState!=="open"||s.bufferedAmount>this.config.highWaterMark)return;s.send(this._encodePieceMessage(t,w,p)),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,w)}else for(let{pieceIndex:w,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,w,p)),e.inFlightPieces.set(w,{sendTime:Date.now(),channelIndex:u}),e.lastActivityTime=Date.now(),this._startPieceTimeout(t,w)}}}catch(h){for(let y of o)e.inFlightPieces.delete(y),e.retryQueue.push(y);this.emit("error",{transferId:t,error:`Failed to send pieces: ${h.message}`})}}confirmSendComplete(t){let e=this.transfers.get(t);!e||e.type!=="send"||e.state==="completed"||e.state==="completing"||(e.bytesTransferred=e.manifest.fileSize,e.piecesAckedCount=e.manifest.totalPieces,e.inFlightPieces.clear(),this._completeSend(t))}_completeSend(t){let e=this.transfers.get(t);!e||e.state==="completed"||e.state==="completing"||(e.state="completed",this._stopPacingLoop(t),e.pieceTimeouts&&(e.pieceTimeouts.forEach(i=>clearTimeout(i)),e.pieceTimeouts.clear()),this._stopIdleTimeout(t),e.reServeCleanupTimer=setTimeout(()=>{this._cleanupTransfer(t)},this.config.reServeContextTtlMs),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)}_allPiecesWritten(t){return t.nextPieceToSend>=t.manifest.totalPieces&&t.retryQueue.length===0&&(t.pendingReads||0)===0}_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"){this._allPiecesWritten(e)&&(this._stopPacingLoop(t),this._completeSend(t));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){let h=this._allPiecesWritten(e);h&&e.inFlightPieces.size===0?this._stopPacingLoop(t):h&&s.bufferedAmount===0?(this._stopPacingLoop(t),this._completeSend(t)):h&&Date.now()-e.lastActivityTime>=this.config.postSendGraceMs&&(this._stopPacingLoop(t),this._completeSend(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);if(i.length>0){e.lastActivityTime=Date.now();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.timeoutPolicy.pieceTimeoutMs);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=i.manifest.totalPieces-i.piecesReceivedCount,n=i.pieceRetries.get(e)||0,{action:a}=this.timeoutPolicy.decidePieceTimeout({timeSinceActivityMs:s,piecesRemaining:r,retries:n});if(a==="grace"){this._startPieceTimeout(t,e);return}a==="retry"?(i.pieceRetries.set(e,n+1),this.emit("piece-retry",{transferId:t,pieceIndex:e,retryCount:n+1}),i.type==="receive"?(i.lastActivityTime=Date.now(),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.type==="send"?i.inFlightPieces.delete(e):i.piecesRequested.delete(e),i.pieceRetries.delete(e),this._clearPieceTimeout(t,e),this.emit("piece-failed",{transferId:t,pieceIndex:e,error:"Max retries exceeded"}))}reRequestPiece(t,e){let i=this.transfers.get(t);if(!i||i.type!=="receive"||i.state!=="transferring"||i.receivedOutOfOrder&&(e<=i.receivedWaterLevel||i.receivedOutOfOrder.has(e)))return!1;let s=i.pieceEscalations.get(e)||0;return s>=this.config.maxPieceEscalations?!1:(i.pieceEscalations.set(e,s+1),i.pieceRetries.delete(e),i.lastActivityTime=Date.now(),i.piecesRequested.delete(e),i.piecesRequested.add(e),i.requestPiece(e),this._startPieceTimeout(t,e),!0)}_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;this.timeoutPolicy.isIdleExpired(s)?this.emit("idle-timeout",{transferId:t,timeSinceActivity:s}):this._startIdleTimeout(t)},this.timeoutPolicy.idleTimeoutMs))}_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(e.reServeCleanupTimer&&(clearTimeout(e.reServeCleanupTimer),e.reServeCleanupTimer=null),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.5",Se="https://perkoon.com",O=class extends _e{constructor(t=Se,e={}){super(),this.serverUrl=t,this.peerTimeout=e.timeout||3e5,this.engine=new M,this.transport=null,this.signaling=null,this.fileSource=null,this.engine.on("idle-timeout",({transferId:i})=>{this._failTransfer(i,"Transfer stalled (idle timeout)")}),this.engine.on("piece-failed",({transferId:i,pieceIndex:s,error:r})=>{s!==void 0&&this.engine.reRequestPiece(i,s)||this._failTransfer(i,r||`Piece ${s} unrecoverable`)})}_failTransfer(t,e){let i=this.engine.transfers.get(t);!i||i.state==="completed"||i.state==="failed"||(i.state="failed",this.emit("transfer-error",{transferId:t,error:e}))}async send(t,e={}){this.fileSource=await F(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()}_${B.randomUUID().replace(/-/g,"").slice(0,9)}`,a=[{id:`file_${Date.now()}_${B.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()}_${B.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 y=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(y,l);let w=JSON.stringify({type:"manifest",transfer_id:l,manifest:b,batch_id:n,batch_total_files:1,batch_total_size:this.fileSource.size});y.send(w),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():E(e,{overwrite:i.overwrite});let y=this.fileSink,P=[],w=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 y.startStreamingReceive(u,{fileName:g.fileName,size:g.fileSize,fileSize:g.fileSize,type:g.fileType,pieceSize:g.pieceSize},{}),this.engine.initReceive(u,g,{streamingHandler:y}),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 R=_.receivedWaterLevel;if(R>C){C=R;let k=_.receivedOutOfOrder.size>0?Array.from(_.receivedOutOfOrder):void 0;this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:R,ooo:k}))}},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 k=this.engine.transfers.get(u);k&&this._safeSend(l,JSON.stringify({type:"ack_wl",transfer_id:u,wl:k.receivedWaterLevel}));let V=y.getFilePath(u);P.push(V),await this.engine.completeReceive(u),this._safeSend(l,JSON.stringify({type:"complete",transfer_id:u}));let D=this.engine.transfers.get(u),x=Date.now()-(D?.startTime||Date.now()),N=D?.manifest;p({files:P,speed:N?N.fileSize/(x/1e3):0,duration:x})}})});return await this._drainChannel(l,2e3),this.signaling.disconnect(),this.transport.close(),w}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":this.engine.confirmSendComplete(e),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{}}async _drainChannel(t,e){if(!t)return;let i=Date.now()+e;for(;Date.now()<i;){let s=t.bufferedAmount;if(s===void 0||s===0)break;await new Promise(r=>setTimeout(r,25))}await new Promise(s=>setTimeout(s,150))}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,O as TransferManager,E as createNodeFileSink,F as createNodeFileSource};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perkoon",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
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",