chrome-extension/content.js
Rama main · réplica de lectura del repositorio original.
(() => {
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);
})();