2026-05-20 13:57:11 +02:00
import { applyCors , isAllowedOrigin } from './_cors.js' ;
2026-05-21 14:08:41 +02:00
const DEFAULT_DROPBOX_SHARED_URL = 'https://www.dropbox.com/scl/fi/usa8me7ywgylrij2bt6hj/Data-Matrix.xlsx?rlkey=tsec8csrhye54u1fdvk15ped1&st=qbxxs4cn&dl=0' ;
function getDropboxConfig () {
return {
appKey : process . env . DROPBOX_APP_KEY ,
appSecret : process . env . DROPBOX_APP_SECRET ,
refreshToken : process . env . DROPBOX_REFRESH_TOKEN ,
sharedUrl : process . env . DROPBOX_SHARED_URL || DEFAULT_DROPBOX_SHARED_URL ,
};
}
function normalizeDropboxSharedUrl ( sharedUrl ) {
const url = new URL ( sharedUrl );
url . searchParams . delete ( 'dl' );
url . searchParams . delete ( 'raw' );
url . searchParams . delete ( 'st' );
return url . toString ();
}
function buildDropboxDownloadUrl ( sharedUrl ) {
const url = new URL ( normalizeDropboxSharedUrl ( sharedUrl ));
url . searchParams . set ( 'raw' , '1' );
return url . toString ();
}
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 () {
2026-05-21 14:08:41 +02:00
const { appKey , appSecret , refreshToken } = getDropboxConfig ();
if ( ! appKey || ! appSecret || ! refreshToken ) {
throw new Error ( 'Dropbox auth env vars are not configured.' );
}
2026-04-10 11:24:28 +02:00
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' ,
2026-05-21 14:08:41 +02:00
refresh_token : refreshToken ,
client_id : appKey ,
client_secret : appSecret ,
2026-04-10 11:24:28 +02:00
})
});
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 14:08:41 +02:00
const { sharedUrl } = getDropboxConfig ();
const sharingUrlForApi = normalizeDropboxSharedUrl ( sharedUrl );
const downloadUrl = buildDropboxDownloadUrl ( sharedUrl );
2026-05-21 10:25:45 +02:00
// Try authenticated Dropbox API first — bypasses CDN cache so we always get the latest version.
2026-05-21 14:08:41 +02:00
// Falls back to the shared URL if credentials are not configured.
2026-05-21 10:25:45 +02:00
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 ) {
2026-05-21 14:08:41 +02:00
console . warn ( 'Dropbox auth unavailable, falling back to sharing link:' , authErr ? . message || String ( authErr ));
2026-05-21 10:25:45 +02:00
}
try {
if ( ! upstream ) {
2026-05-21 14:08:41 +02:00
upstream = await fetch ( downloadUrl , {
2026-05-21 10:25:45 +02:00
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-05-21 14:08:41 +02:00
res . status ( 500 ). send ( 'Proxy error: ' + ( err ? . message || String ( err )));
2026-03-27 13:26:16 +01:00
}
}