(() => { const adapters = globalThis.NASDropProviders; let language=NASDropI18n.normalize(navigator.language); const text=new Proxy({}, {get:(_,key)=>NASDropI18n.dictionaries[language][key]}); let pending = false; const gofileQueue = []; const gofileQueued = new Set(); const GOFILE_QUEUE_LIMIT = 100; let gofileDraining = false; let sendNowTimer = 0; let xShareTimer = 0; let contextLost = false; let readinessCache = null; function invalidContext(error) { return /extension context invalidated/i.test(String(error?.message || error || '')); } let panel; function show(message) { if (!panel?.isConnected) { panel = document.createElement('div'); panel.style.cssText = 'position:fixed;right:20px;bottom:20px;z-index:2147483647;max-width:380px;'; const shadow = panel.attachShadow({mode:'closed'}); const card = document.createElement('div'); card.style.cssText = 'background:#101827;color:white;padding:16px;border:1px solid #5277bc;border-radius:12px;font:14px/1.5 system-ui;box-shadow:0 6px 30px #0005;'; const title = document.createElement('strong'); title.textContent = 'NASDrop'; const close = document.createElement('button'); close.textContent = '×'; close.setAttribute('aria-label', text.close); panel.closeButton=close; close.style.cssText = 'float:right;background:transparent;color:white;border:0;font-size:20px;cursor:pointer;'; close.addEventListener('click', () => panel.remove()); const status = document.createElement('div'); status.setAttribute('role', 'status'); card.append(close, title, status); shadow.append(card); panel.update = value => { status.textContent = value; }; (document.body || document.documentElement).append(panel); } panel.update(NASDropI18n.error(language,message)); } async function send(action) { if (contextLost) { show(text.reloadExtension); return; } pending = true; show(text.sending); try { const ready = await chrome.runtime.sendMessage({type:'pageReady'}); language=NASDropI18n.normalize(ready?.result?.language || language); if(panel?.closeButton)panel.closeButton.setAttribute('aria-label',text.close); if (action.error) { show(text[action.error] || text.failed); return; } if (!ready?.ok || !ready.result.ready) { show(text.login); return; } if (action.handoff && !ready.result.handoffSupported) { show(text.serverUnsupported); return; } let url = action.url; if (action.endpoint) { show(text.resolving); url = await adapters.resolveBuzzLink(action.endpoint, location.href); } show(text.sending); let response; try { response = await chrome.runtime.sendMessage({type:'pageSubmit', url, downloadKey:action.downloadKey || ''}); } catch (error) { if (invalidContext(error)) { contextLost=true; show(text.reloadExtension); } else show(text.uncertain); return; } show(response?.ok ? text.success : text[response?.code] || response?.error || text.failed); } catch (error) { if (invalidContext(error)) { contextLost=true; show(text.reloadExtension); } else show(error.message || text.failed); } finally { pending = false; } } function gofileQueueKey(action) { return String(action.url || action.endpoint || ''); } async function drainGoFileQueue() { if (gofileDraining || contextLost) return; gofileDraining = true; try { while (gofileQueue.length && !contextLost) { const entry = gofileQueue.shift(); try { await send(entry.action); } finally { gofileQueued.delete(entry.key); } } } finally { if (contextLost) { gofileQueue.length = 0; gofileQueued.clear(); } gofileDraining = false; } } function enqueueGoFile(action) { const key = gofileQueueKey(action); if (!key || gofileQueued.has(key)) { if (key) show(text.alreadyQueued); return; } if (gofileQueued.size >= GOFILE_QUEUE_LIMIT) { show(text.submissionQueueFull); return; } gofileQueued.add(key); gofileQueue.push({action,key}); if (gofileDraining) show(NASDropI18n.t(language,'submissionQueued',{count:gofileQueue.length})); void drainGoFileQueue(); } async function armDownload(action, element) { if (contextLost) { show(text.reloadExtension); return; } pending = true; show(text.sending); try { const response = await chrome.runtime.sendMessage({type:'pageArmDownload',source:action.source}); language=NASDropI18n.normalize(response?.result?.language || language); if(panel?.closeButton)panel.closeButton.setAttribute('aria-label',text.close); if (!response?.ok) { pending=false; if (['login','serverUnsupported'].includes(response?.code)) element.click(); show(text[response?.code] || response?.error || text.failed); return; } element.click(); sendNowTimer=setTimeout(()=>{pending=false;show(text.uncertain);},65000); } catch (error) { pending=false; if (invalidContext(error)) { contextLost=true; show(text.reloadExtension); } else show(error.message || text.failed); } } async function armXShare(action, element) { if (contextLost) { show(text.reloadExtension); return; } pending = true; show(text.sending); try { const response = await chrome.runtime.sendMessage({type:'pageArmXShare',source:action.source}); language=NASDropI18n.normalize(response?.result?.language || language); if(panel?.closeButton)panel.closeButton.setAttribute('aria-label',text.close); if (!response?.ok) { pending=false; if (['login','serverUnsupported'].includes(response?.code)) element.click(); show(text[response?.code] || response?.error || text.failed); return; } const shareId=new URL(action.source).pathname.split('/').filter(Boolean)[1] || ''; document.dispatchEvent(new CustomEvent('__nasdrop_xshare_arm_v1',{detail:{shareId,expiresAt:Date.now()+60000}})); element.click(); xShareTimer=setTimeout(()=>{pending=false;show(text.uncertain);},65000); } catch (error) { pending=false; if (invalidContext(error)) { contextLost=true; show(text.reloadExtension); } else show(error.message || text.failed); } } document.addEventListener('__nasdrop_xshare_url_v1', event => { const url=typeof event.detail === 'string' ? event.detail : ''; if (!pending || adapters.provider(location.href) !== 'xshare' || !adapters.allowedSubmission(location.href,url)) return; clearTimeout(xShareTimer);xShareTimer=0;pending=false; void send({url,handoff:'xshare'}); }); chrome.runtime.onMessage.addListener(message => { if (message?.type !== 'sendNowResult') return; clearTimeout(sendNowTimer); sendNowTimer=0; pending=false; show(message.ok ? text.success : message.error || text.failed); }); async function preloadReadiness() { if (!['gigafile','sendnow','xshare'].includes(adapters.provider(location.href))) return; try { const response = await chrome.runtime.sendMessage({type:'pageReady'}); if (response?.ok) readinessCache = response.result; } catch (error) { if (invalidContext(error)) contextLost=true; } } void preloadReadiness(); // Delegation also covers controls inserted after load and SPA navigation. window.addEventListener('click', event => { if (!event.isTrusted || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; const element = event.target?.closest?.('a, button, input[type="button"], input[type="submit"], [data-action], [onclick]'); if (!element || element.disabled || element.getAttribute('aria-disabled') === 'true') return; const action = adapters.resolve(element, location.href, document.referrer); if (!action) return; if ((action.captureDownload || action.captureXShare) && (!readinessCache?.ready || !readinessCache.handoffSupported)) return; if (action.gigafileKeyRequired) { // Unknown, signed-out and older servers leave the site's own download // behavior untouched. Capability is prefetched before the user clicks. if (!readinessCache?.ready || !readinessCache.gigafileDownloadKeySupported) return; const input = document.querySelector('#dlkey'); const downloadKey = String(input?.value || ''); if (downloadKey.length < 1 || downloadKey.length > 4) { event.preventDefault(); event.stopImmediatePropagation(); input?.focus(); show(text.gigafileKeyPrompt); return; } action.downloadKey = downloadKey; if (input) input.value = ''; } event.preventDefault(); event.stopImmediatePropagation(); if (adapters.provider(location.href) === 'gofile' && !action.captureDownload && !action.captureXShare) { enqueueGoFile(action); return; } if (!pending) void (action.captureDownload ? armDownload(action, element) : action.captureXShare ? armXShare(action,element) : send(action)); }, true); })();