2026-05-20 13:57:11 +02:00
import { applyCors , isAllowedOrigin } from './_cors.js' ;
2026-04-10 11:24:28 +02:00
const DROPBOX_APP_KEY = process . env . DROPBOX_APP_KEY ;
const DROPBOX_APP_SECRET = process . env . DROPBOX_APP_SECRET ;
const DROPBOX_REFRESH_TOKEN = process . env . DROPBOX_REFRESH_TOKEN ;
2026-05-20 13:57:11 +02:00
const DROPBOX_SHARED_URL =
process . env . DROPBOX_SHARED_URL ||
process . env . DROPBOX_FILE_URL ||
'https://www.dropbox.com/scl/fi/usa8me7ywgylrij2bt6hj/Data-Matrix.xlsx?rlkey=tsec8csrhye54u1fdvk15ped1&dl=1' ;
2026-04-23 10:02:47 +02:00
function setCors ( req , res ) {
2026-05-20 13:57:11 +02:00
applyCors ( req , res , 'GET, OPTIONS' );
2026-04-23 10:02:47 +02:00
}
2026-04-10 11:24:28 +02:00
async function getAccessToken () {
const response = await fetch ( 'https://api.dropboxapi.com/oauth2/token' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/x-www-form-urlencoded' },
body : new URLSearchParams ({
grant_type : 'refresh_token' ,
refresh_token : DROPBOX_REFRESH_TOKEN ,
client_id : DROPBOX_APP_KEY ,
client_secret : DROPBOX_APP_SECRET ,
})
});
const data = await response . json ();
if ( ! data . access_token ) {
throw new Error ( 'Failed to get access token: ' + JSON . stringify ( data ));
}
return data . access_token ;
}
2026-04-10 10:54:50 +02:00
2026-03-27 13:26:16 +01:00
export default async function handler ( req , res ) {
2026-04-23 10:02:47 +02:00
setCors ( req , res );
if ( req . method === 'OPTIONS' ) {
return res . status ( 204 ). end ();
}
const origin = req . headers . origin ;
2026-05-20 13:57:11 +02:00
if ( origin && ! isAllowedOrigin ( origin )) {
2026-04-23 10:02:47 +02:00
return res . status ( 403 ). json ({ error : 'Forbidden' });
}
2026-04-10 10:54:50 +02:00
if ( req . method === 'GET' && req . query . info === '1' ) {
2026-04-23 10:02:47 +02:00
res . setHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, proxy-revalidate' );
res . setHeader ( 'Pragma' , 'no-cache' );
res . setHeader ( 'Expires' , '0' );
return res . json ({ rev : 'new-url-v1' , size : 0 , server_modified : new Date (). toISOString () });
2026-04-10 10:54:50 +02:00
}
2026-05-21 10:25:45 +02:00
// Strip dl=1 for the authenticated API call (browser-redirect hint, not needed for API).
const sharingUrlForApi = DROPBOX_SHARED_URL . replace ( /[&?]dl=1/ , '' );
// Try authenticated Dropbox API first — bypasses CDN cache so we always get the latest version.
// Falls back to DROPBOX_SHARED_URL if credentials are not configured.
let upstream = null ;
let usedAuth = false ;
2026-03-27 13:26:16 +01:00
try {
2026-05-21 10:25:45 +02:00
const accessToken = await getAccessToken ();
const apiRes = await fetch ( 'https://content.dropboxapi.com/2/sharing/get_shared_link_file' , {
method : 'POST' ,
2026-04-10 11:01:55 +02:00
headers : {
2026-05-21 10:25:45 +02:00
'Authorization' : `Bearer ${ accessToken } ` ,
'Dropbox-API-Arg' : JSON . stringify ({ url : sharingUrlForApi }),
'Content-Type' : 'text/plain; charset=utf-8' ,
},
2026-04-10 11:01:55 +02:00
});
2026-05-21 10:25:45 +02:00
if ( apiRes . ok ) {
upstream = apiRes ;
usedAuth = true ;
console . log ( 'Dropbox: downloaded via authenticated API (no CDN cache)' );
} else {
const errText = await apiRes . text ();
console . warn ( 'Dropbox API download failed, falling back to sharing link:' , apiRes . status , errText . substring ( 0 , 200 ));
}
} catch ( authErr ) {
console . warn ( 'Dropbox auth unavailable, falling back to sharing link:' , authErr . message );
}
try {
if ( ! upstream ) {
upstream = await fetch ( DROPBOX_SHARED_URL , {
method : 'GET' ,
headers : {
'Cache-Control' : 'no-cache' ,
'Pragma' : 'no-cache' ,
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
});
}
2026-04-10 11:20:16 +02:00
2026-04-22 16:17:07 +02:00
const contentType = upstream . headers . get ( 'content-type' );
2026-05-21 10:25:45 +02:00
if ( ! usedAuth && contentType && contentType . includes ( 'text/html' )) {
2026-04-22 16:17:07 +02:00
console . error ( 'Dropbox returned HTML instead of file' );
return res . status ( 500 ). send ( 'Error: El enlace de Dropbox ha devuelto una página HTML en lugar del archivo. Es probable que el enlace haya caducado o necesite ser renovado.' );
}
2026-04-10 11:20:16 +02:00
if ( ! upstream . ok ) {
const errText = await upstream . text ();
2026-04-21 08:40:54 +02:00
console . error ( 'Dropbox URL error:' , upstream . status , errText );
2026-05-20 13:57:11 +02:00
return res . status ( upstream . status ). send (
'Dropbox error: ' +
errText +
'\n\nSet DROPBOX_SHARED_URL in Vercel if the sharing link changed.'
);
2026-04-10 11:20:16 +02:00
}
2026-04-10 11:01:55 +02:00
const buffer = await upstream . arrayBuffer ();
2026-03-27 13:26:16 +01:00
res . setHeader ( 'Content-Type' , 'application/octet-stream' );
res . setHeader ( 'Cache-Control' , 'no-store' );
2026-04-10 11:01:55 +02:00
res . send ( Buffer . from ( buffer ));
2026-03-27 13:26:16 +01:00
} catch ( err ) {
2026-04-10 11:20:16 +02:00
console . error ( 'Dropbox proxy error:' , err );
2026-03-27 13:26:16 +01:00
res . status ( 500 ). send ( 'Proxy error: ' + err . message );
}
}