Files
bitvid/torrent/dist/beacon.js
T

2 lines
18 KiB
JavaScript

var q={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},$="transition duration-200 ease-out",oe="beacon-toast-motion",P="data-beacon-motion";function ae(e,r){let t=e?.defaultView;if(t&&typeof t.requestAnimationFrame=="function"){t.requestAnimationFrame(r);return}setTimeout(r,16)}function se(e){let r=e||(typeof document<"u"?document:null);if(!r)return null;let t=r.getElementById("beacon-toast-container");if(!t){t=r.createElement("div"),t.id="beacon-toast-container",t.setAttribute("role","region"),t.setAttribute("aria-live","polite"),t.className=["pointer-events-none","fixed","inset-x-0","top-0","z-overlay-toast","flex","w-full","flex-col","items-center","gap-3","px-4","pt-6","sm:items-end","sm:px-6","sm:pt-8"].join(" ");let i=r.body||r.getElementsByTagName?.("body")?.[0];i&&i.appendChild(t)}return t}function ie(e,r,t){let i=e.createElement("div");i.className=[$,t].join(" "),i.setAttribute("role","status"),i.setAttribute("tabindex","0"),i.textContent=r;let a=e.createElement("button");a.type="button",a.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),a.setAttribute("aria-label","Dismiss notification"),a.innerHTML='<svg class="h-4 w-4" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.75 3.75l8.5 8.5"></path><path d="M12.25 3.75l-8.5 8.5"></path></svg>';let d=e.createElement("div");return d.className=["relative","w-full",$,oe].join(" "),d.setAttribute(P,"exit"),d.appendChild(i),i.appendChild(a),{wrapper:d,toast:i,closeButton:a}}function le(e,r=setTimeout,t=clearTimeout){return{show(i,a={}){if(!i)return;let d=a.type&&q[a.type]?a.type:"info",n=se(e);if(!n)return;let{wrapper:b,toast:c,closeButton:I}=ie(e,i,q[d]);n.appendChild(b),ae(e,()=>{b.setAttribute(P,"enter")});let A=typeof a.duration=="number"?a.duration:3500,m=()=>{b.setAttribute(P,"exit");let M=r(()=>{b.parentNode&&b.parentNode.removeChild(b)},200);return I.removeEventListener("click",S),c.removeEventListener("keydown",h),I.removeEventListener("keydown",h),M},S=()=>m(),h=M=>{M.key==="Escape"&&(M.stopPropagation(),m())};I.addEventListener("click",S),c.addEventListener("keydown",h),I.addEventListener("keydown",h);let B=null;return a.sticky||(B=r(()=>{m()},A)),{dismiss(){B&&t(B),m()}}},set(i,a){return typeof a=="string"?this.show(i,{type:a}):this.show(i,a)},info(i,a={}){return this.show(i,{...a,type:"info"})},success(i,a={}){return this.show(i,{...a,type:"success"})},error(i,a={}){return this.show(i,{...a,type:"error"})},warn(i,a={}){return this.show(i,{...a,type:"warn"})}}}function j(e,r=setTimeout,t=clearTimeout){return le(e,r,t)}var Y=[{key:"name",label:"Name",headerClass:"w-full sm:w-1/3 text-left",cellClass:"font-medium text-text truncate"},{key:"progress",label:"Progress",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"progress"},{key:"downloadSpeed",label:"\u2193 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"uploadSpeed",label:"\u2191 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"numPeers",label:"Peers",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"integer"},{key:"ratio",label:"Ratio",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"ratio"}];function ce(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function de(e,r){return!e||!r?!1:e===r||e.magnetURI&&e.magnetURI===r.magnetURI||e.infoHash&&e.infoHash===r.infoHash}function ue(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function pe(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function fe(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function me(e,r){if(typeof r=="function")return r(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let t=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),t.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${t[i]}/s`}function be(e,r){let t=e.formatter;if(typeof t=="function")return t;let i=r?.[t];if(typeof i=="function")return i;switch(t){case"progress":return ue;case"ratio":return pe;case"integer":return fe;case"bytesPerSecond":return a=>me(a,r?.bytes);default:return a=>a}}function G({documentRef:e=typeof document<"u"?document:null,root:r,onSelect:t,formatters:i={}}={}){if(!e)throw new Error("createTorrentTable requires a document reference");if(!(r instanceof e.defaultView.HTMLElement))throw new Error("createTorrentTable requires a valid root element");let a=e;r.innerHTML="";let d=a.createElement("div");d.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let n=a.createElement("div");n.className="overflow-x-auto",d.appendChild(n);let b=a.createElement("table");b.className="min-w-full divide-y divide-border-translucent text-sm",n.appendChild(b);let c=a.createElement("thead");c.className="bg-surface-alt text-muted-strong";let I=a.createElement("tr");Y.forEach(T=>{let v=a.createElement("th");v.setAttribute("scope","col"),v.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",T.headerClass&&(v.className+=` ${T.headerClass}`),v.textContent=T.label,I.appendChild(v)}),c.appendChild(I),b.appendChild(c);let A=a.createElement("tbody");A.className="divide-y divide-border-translucent",b.appendChild(A);let m=a.createElement("tbody"),S=a.createElement("tr"),h=a.createElement("td");h.className="px-4 py-8 text-center text-sm text-muted",h.colSpan=Y.length,h.textContent="No active torrents yet. Add a magnet link to get started.",S.appendChild(h),m.appendChild(S),b.appendChild(m),r.appendChild(d);function B(T=[],v=null){let D=Array.isArray(T)?T:[];if(A.innerHTML="",!D.length){A.hidden=!0,m.hidden=!1;return}A.hidden=!1,m.hidden=!0;for(let N of D){let _=a.createElement("tr");_.className=["group","align-middle","cursor-pointer","transition","hover:bg-panel-hover","focus-visible:outline","focus-visible:outline-2","focus-visible:outline-offset-2","focus-visible:outline-info"].join(" "),_.setAttribute("role","button"),_.setAttribute("tabindex","0"),_.dataset.torrentId=ce(N),de(v,N)?(_.classList.add("bg-info/10","text-text"),_.setAttribute("aria-pressed","true")):_.setAttribute("aria-pressed","false"),_.addEventListener("click",()=>{typeof t=="function"&&t(N)}),_.addEventListener("keydown",x=>{(x.key==="Enter"||x.key===" ")&&(x.preventDefault(),typeof t=="function"&&t(N))}),Y.forEach(x=>{let H=a.createElement("td");H.className="px-4 py-3",x.cellClass&&(H.className+=` ${x.cellClass}`);let W=N?N[x.key]:void 0,o=be(x,i)(W,N,x),s=a.createElement("span");s.textContent=o==null?"":String(o),H.appendChild(s),_.appendChild(H)}),A.appendChild(_)}}function M(){r.innerHTML=""}return{render:B,destroy:M}}var Ee=Object.freeze([]);var he=Object.freeze(["watch-history:v2:index"]);var Te=1440*60*1e3;var ye=300*1e3;var g=!!!0;typeof window<"u"&&(window.__BITVID_DEV_MODE__=g);var Qe=typeof globalThis<"u"&&globalThis.console||null;var et=typeof globalThis<"u"&&globalThis.bitvidLogger&&globalThis.bitvidLogger.user||null;var w=()=>{},p=typeof globalThis<"u"?globalThis.console:void 0,L={log:p?.log?.bind(p)??w,info:p?.info?.bind(p)??w,warn:p?.warn?.bind(p)??w,error:p?.error?.bind(p)??w,debug:p?.debug?.bind(p)??w,trace:p?.trace?.bind(p)??w,group:p?.group?.bind(p)??w,groupCollapsed:p?.groupCollapsed?.bind(p)??w,groupEnd:p?.groupEnd?.bind(p)??w,table:p?.table?.bind(p)??w,assert:p?.assert?.bind(p)??w};function K({gateLogs:e,alwaysWarnError:r}){return{log:(...t)=>{(!e||g)&&L.log(...t)},info:(...t)=>{(!e||g)&&L.info(...t)},debug:(...t)=>{(!e||g)&&L.debug(...t)},trace:(...t)=>{(!e||g)&&L.trace(...t)},group:(...t)=>{(!e||g)&&L.group(...t)},groupCollapsed:(...t)=>{(!e||g)&&L.groupCollapsed(...t)},groupEnd:(...t)=>{(!e||g)&&L.groupEnd(...t)},table:(...t)=>{(!e||g)&&L.table(...t)},assert:(...t)=>{(!e||g)&&L.assert(...t)},warn:(...t)=>{!r&&e&&!g||L.warn(...t)},error:(...t)=>{!r&&e&&!g||L.error(...t)}}}var X=K({gateLogs:!0,alwaysWarnError:!1}),z=K({gateLogs:!0,alwaysWarnError:!0}),ge={dev:X,user:z},Z=X,C=z;typeof window<"u"&&(window.bitvidLogger=ge);var Ce="2.0",te=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],J={announce:te},Ae={announce:te};function U(e,r=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return r?"":"0 B";let t=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),t.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${t[i]}${r?"/s":""}`}function Ie(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function Q(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function Se(e,r){return!e||!r?!1:!!(e===r||e.infoHash&&r.infoHash&&e.infoHash===r.infoHash||e.magnetURI&&r.magnetURI&&e.magnetURI===r.magnetURI)}function xe(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(r=>{typeof r.priority!="string"&&(r.priority="0"),typeof r.getBlobURL=="function"&&r.getBlobURL((t,i)=>{t||!i||(r.url=i)})}),e.torrentFile&&typeof Blob=="function"&&typeof URL<"u"))try{let r=new Blob([e.torrentFile],{type:"application/x-bittorrent"});if(e.torrentFileBlobURL=URL.createObjectURL(r),!e.fileName){let t=e.name||"download";e.fileName=`${t}.torrent`}}catch(r){C.warn("[beacon] Failed to create torrent blob URL",r)}}function we(e,r){if(!e)return;let t=typeof r=="string"?r:String(r??"0");if(e.priority=t,t==="-1"){typeof e.deselect=="function"&&e.deselect();return}let i=Number.parseInt(t,10);Number.isFinite(i)&&typeof e.select=="function"&&e.select(i)}function ee(e,r){if(!Array.isArray(r)||r.length===0)return null;if(!e)return r[0];let t=r.find(i=>Se(i,e));return t||r[0]}function re({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:r}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let t=e.defaultView||globalThis,i=typeof r=="function"?r:typeof t?.WebTorrent=="function"?t.WebTorrent:null;if(!i)throw new Error("createBeaconApp requires a WebTorrent constructor");let a=j(e),d=new i({tracker:Ae}),n={form:e.querySelector('[data-beacon="magnet-form"]'),magnetInput:e.querySelector('[data-beacon="magnet-input"]'),seedButton:e.querySelector('[data-beacon="seed-button"]'),seedInput:e.querySelector('[data-beacon="seed-input"]'),tableRoot:e.querySelector('[data-beacon="torrent-table"]'),selectedSection:e.querySelector('[data-beacon="selected"]'),selectedName:e.querySelector('[data-beacon="selected-name"]'),pauseButton:e.querySelector('[data-beacon-action="pause"]'),resumeButton:e.querySelector('[data-beacon-action="resume"]'),downloadAllButton:e.querySelector('[data-beacon-action="download-all"]'),removeButton:e.querySelector('[data-beacon-action="remove"]'),copyMagnetButton:e.querySelector('[data-beacon-action="copy-magnet"]'),infoHashValue:e.querySelector('[data-beacon="info-hash"]'),fileList:e.querySelector('[data-beacon="file-list"]'),stats:e.querySelector('[data-beacon="client-stats"]'),overlay:e.querySelector('[data-beacon="processing-overlay"]')},b=n.tableRoot?G({documentRef:e,root:n.tableRoot,formatters:{bytes:(o,s=!1)=>U(o,s),progress:o=>Ie(o),ratio:o=>Q(o),integer:o=>typeof o=="number"&&Number.isFinite(o)?Math.floor(o).toString():"0",bytesPerSecond:o=>U(o,!0)},onSelect(o){c.selectedTorrent=o,T()}}):null,c={selectedTorrent:null,processing:!1},I=[],A=[];function m(o){I.push(o)}function S(o){c.processing=!!o,n.overlay&&(n.overlay.hidden=!c.processing)}function h(){return Array.isArray(d.torrents)?[...d.torrents]:[]}function B(){if(!n.stats)return;let o=U(d.downloadSpeed,!0)||"0 B/s",s=U(d.uploadSpeed,!0)||"0 B/s",f=Q(d.ratio);n.stats.textContent=`Client stats: \u2193 ${o} \xB7 \u2191 ${s} \xB7 Ratio: ${f}`}function M(o){n.fileList&&(n.fileList.innerHTML="",!(!o||!Array.isArray(o.files)||!o.files.length)&&o.files.forEach((s,f)=>{let u=e.createElement("tr");u.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",s.done&&s.url){let y=e.createElement("a");y.className="text-info hover:text-info-strong",y.href=s.url,y.download=s.name||`file-${f+1}`,y.textContent=s.name||`File ${f+1}`,l.appendChild(y)}else{let y=e.createElement("span");y.textContent=s.name||`File ${f+1}`,l.appendChild(y)}u.appendChild(l);let E=e.createElement("td");E.className="px-3 py-2 text-muted-strong",E.textContent=U(s.length)||"0 B",u.appendChild(E);let R=e.createElement("td");R.className="px-3 py-2";let O=e.createElement("select");O.className="select",O.name=`${s.name||`file-${f+1}`}-priority`,O.setAttribute("aria-label",`Set priority for ${s.name||`file ${f+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(y=>{let k=e.createElement("option");k.value=y.value,k.textContent=y.label,y.value===s.priority&&(k.selected=!0),O.appendChild(k)}),O.value=s.priority??"0",O.addEventListener("change",y=>{we(s,y.target.value)}),R.appendChild(O),u.appendChild(R),n.fileList.appendChild(u)}))}function T(){if(!n.selectedSection)return;let o=h();c.selectedTorrent=ee(c.selectedTorrent,o);let s=c.selectedTorrent;n.selectedSection.hidden=!s,s&&(n.selectedName&&(n.selectedName.textContent=s.name||"Untitled torrent"),n.pauseButton&&(n.pauseButton.hidden=s.paused===!0,n.pauseButton.disabled=s.paused===!0),n.resumeButton&&(n.resumeButton.hidden=s.paused!==!0,n.resumeButton.disabled=s.paused!==!0),n.downloadAllButton&&(n.downloadAllButton.disabled=!(s.progress>=1)),n.infoHashValue&&(n.infoHashValue.textContent=s.infoHash||""),M(s))}function v(o,{isSeed:s=!1}={}){S(!1),xe(o),s||a.info(`Received ${o.name||"torrent"} metadata`),c.selectedTorrent||(c.selectedTorrent=o),T(),b?.render(h(),c.selectedTorrent)}function D(o){let s=typeof o=="string"?o.trim():"";s&&(S(!0),d.add(s,J,f=>{v(f,{isSeed:!1})}))}function N(o){!Array.isArray(o)||o.length===0||(S(!0),d.seed(o,J,s=>{v(s,{isSeed:!0}),a.success(`Seeding ${s.files.length} file(s)`)}))}function _(o){if(o){if(o.progress<1){a.warn("Torrent is not finished downloading yet.");return}o.files.forEach(s=>{typeof s.getBlob=="function"&&s.getBlob((f,u)=>{if(f||!u){C.error("[beacon] Failed to get blob for file",s?.name,f),a.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(u),E=e.createElement("a");E.className="beacon-hidden-download",E.setAttribute("data-beacon-hidden-download","true"),E.href=l,E.download=s.name||"download",e.body.appendChild(E),E.click(),e.body.removeChild(E),URL.revokeObjectURL(l)})})}}function x(o){if(!o?.magnetURI)return;let s=o.magnetURI,f=t?.navigator?.clipboard;if(f&&typeof f.writeText=="function"){f.writeText(s).then(()=>{a.success("Magnet URI copied to clipboard!")}).catch(u=>{C.error("[beacon] Clipboard error",u),a.error("Failed to copy magnet URI")});return}try{let u=e.createElement("textarea");u.value=s,u.className="beacon-clipboard-offscreen",u.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(u),u.select(),e.execCommand?.("copy"),e.body.removeChild(u),a.success("Magnet URI copied (fallback)!")}catch(u){C.error("[beacon] Clipboard fallback error",u),a.error("Failed to copy magnet URI")}}function H(o){o&&o.destroy(s=>{s&&(C.error("[beacon] Failed to destroy torrent",s),a.error("Failed to remove torrent"));let f=h();c.selectedTorrent=ee(null,f),b?.render(f,c.selectedTorrent),T()})}function W(){if(Z.log(`[beacon] Starting beacon runtime v${Ce}`),a||C.warn("[beacon] Toast service unavailable"),i.WEBRTC_SUPPORT||a?.error("Please use a browser with WebRTC support.",{sticky:!0}),n.form){let l=E=>{E.preventDefault(),D(n.magnetInput?.value||""),n.magnetInput&&(n.magnetInput.value="")};n.form.addEventListener("submit",l),m(()=>n.form.removeEventListener("submit",l))}if(n.seedButton&&n.seedInput){let l=()=>{n.seedInput.click()},E=()=>{let R=Array.from(n.seedInput.files||[]);n.seedInput.value="",N(R)};n.seedButton.addEventListener("click",l),n.seedInput.addEventListener("change",E),m(()=>{n.seedButton.removeEventListener("click",l),n.seedInput.removeEventListener("change",E)})}if(n.pauseButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.pause=="function"&&(c.selectedTorrent.pause(),T())};n.pauseButton.addEventListener("click",l),m(()=>n.pauseButton.removeEventListener("click",l))}if(n.resumeButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.resume=="function"&&(c.selectedTorrent.resume(),T())};n.resumeButton.addEventListener("click",l),m(()=>n.resumeButton.removeEventListener("click",l))}if(n.downloadAllButton){let l=()=>_(c.selectedTorrent);n.downloadAllButton.addEventListener("click",l),m(()=>n.downloadAllButton.removeEventListener("click",l))}if(n.removeButton){let l=()=>H(c.selectedTorrent);n.removeButton.addEventListener("click",l),m(()=>n.removeButton.removeEventListener("click",l))}if(n.copyMagnetButton){let l=()=>x(c.selectedTorrent);n.copyMagnetButton.addEventListener("click",l),m(()=>n.copyMagnetButton.removeEventListener("click",l))}let o=l=>{C.error("[beacon] Torrent client error",l),a?.error(l?.message||String(l)),S(!1)};d.on("error",o),m(()=>d.removeListener("error",o));let s=l=>{if(d.torrents&&d.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};t&&typeof t.addEventListener=="function"&&(t.addEventListener("beforeunload",s),m(()=>t.removeEventListener("beforeunload",s))),b?.render(h(),c.selectedTorrent),T(),B(),S(!1);let f=t.setInterval(()=>{b?.render(h(),c.selectedTorrent),T(),B()},1e3);A.push(f);let u=t?.location?.hash;typeof u=="string"&&u.length>1&&D(u.slice(1))}function V(){A.forEach(o=>t.clearInterval(o)),A.length=0,I.forEach(o=>{try{o()}catch(s){C.warn("[beacon] Cleanup task failed",s)}}),I.length=0,b?.destroy();try{d.destroy()}catch(o){C.warn("[beacon] Failed to destroy client",o)}}return{mount:W,destroy:V}}function Le(){let e=typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null;if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var F=null;function ne(){if(F)return F;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=Le();return F=re({documentRef:document,WebTorrentCtor:e}),F.mount(),F}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{ne()}catch(e){C.error("[beacon] Failed to mount app",e)}});else try{ne()}catch(e){C.error("[beacon] Failed to mount app",e)}export{re as createBeaconApp,ne as mountBeaconApp};