From 760344a88b7c627e17e1e9ce9e1f37aca46c2f7e Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Fri, 26 Sep 2025 21:27:43 -0700 Subject: [PATCH] Refactor: Improve readability and maintainability --- functions/[[path]].js | 164 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 17 deletions(-) diff --git a/functions/[[path]].js b/functions/[[path]].js index 499a262..e89246c 100644 --- a/functions/[[path]].js +++ b/functions/[[path]].js @@ -1,20 +1,150 @@ -const C='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',H={'Access-Control-Allow-Origin':'*'},r=l=>[...Array(l)].map(_=>C[Math.random()*C.length|0]).join(''),n=u=>{try{return new URL(u).href}catch(_){try{return new URL('https://'+u).href}catch(_){}}} +/** + * Configuration and Constants + */ +const CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; +const BASE_SLUG_LENGTH = 4; -export async function onRequest({request,env,next}){ - if(request.method==='OPTIONS')return new Response(null,{headers:{...H,'Access-Control-Allow-Methods':'POST','Access-Control-Allow-Headers':'Content-Type'}}) - const u=new URL(request.url) - if(u.pathname==='/api/create'&&request.method==='POST'){ - try{ - let{url:t,token:k}=await request.json() - if(!k||!(await(await fetch('https://www.google.com/recaptcha/api/siteverify',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`secret=${env.RECAPCHA_KEY}&response=${k}`})).json()).success) - return new Response('CAPTCHA failed',{status:403,headers:H}) - t=n(t);if(!t)throw 0 - let l=4,s;do s=r(l++);while(await env.EV.get(s)) - await env.EV.put(s,t) - return new Response(JSON.stringify({slug:s,target:t,shortUrl:`${u.origin}/${s}`}),{headers:{'Content-Type':'application/json',...H}}) - }catch(_){return new Response('Invalid request',{status:400,headers:H})} +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', +}; +const OPTIONS_HEADERS = { + ...CORS_HEADERS, + 'Access-Control-Allow-Methods': 'POST', + 'Access-Control-Allow-Headers': 'Content-Type', +}; +const JSON_HEADERS = { + ...CORS_HEADERS, + 'Content-Type': 'application/json', +}; + +/** + * Generates a random string of a given length from the character set. + * @param {number} length The desired length of the slug. + * @returns {string} The generated slug. + */ +const generateSlug = (length) => { + return [...Array(length)] + .map(() => CHARSET[Math.floor(Math.random() * CHARSET.length)]) + .join(''); +}; + +/** + * Tries to parse a string into a valid URL, prepending 'https://' if necessary. + * @param {string} urlString The string to normalize. + * @returns {string|undefined} The normalized URL as a string, or undefined if invalid. + */ +const normalizeUrl = (urlString) => { + try { + return new URL(urlString).href; + } catch { + try { + return new URL('https://' + urlString).href; + } catch { + return undefined; + } } - const p=u.pathname.slice(1) - if(p){const t=await env.EV.get(p);if(t)return Response.redirect(t,302)} - return next() +}; + +/** + * Verifies the user's reCAPTCHA token with Google's API. + * @param {string} token The reCAPTCHA token from the client. + * @param {string} secretKey The reCAPTCHA secret key. + * @returns {Promise} A promise that resolves to true if the token is valid. + */ +async function verifyRecaptcha(token, secretKey) { + if (!token || !secretKey) { + return false; + } + const response = await fetch('https://www.google.com/recaptcha/api/siteverify', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `secret=${secretKey}&response=${token}`, + }); + const data = await response.json(); + return data.success; +} + +/** + * Handles the creation of a new shortened link. + * @param {Request} request The incoming request object. + * @param {object} env The environment bindings. + * @returns {Promise} A promise that resolves to a Response object. + */ +async function handleCreateRequest(request, env) { + try { + const { url: targetUrl, token } = await request.json(); + + if (!await verifyRecaptcha(token, env.RECAPCHA_KEY)) { + return new Response('CAPTCHA verification failed.', { status: 403, headers: CORS_HEADERS }); + } + + const normalizedTarget = normalizeUrl(targetUrl); + if (!normalizedTarget) { + return new Response('Invalid URL provided.', { status: 400, headers: CORS_HEADERS }); + } + + let slugLength = BASE_SLUG_LENGTH; + let slug; + // Keep generating slugs until a unique one is found. + // If a collision occurs, increase the slug length and try again. + while (true) { + slug = generateSlug(slugLength); + if (!(await env.EV.get(slug))) { + break; // Found a unique slug. + } + slugLength++; // Collision detected, increase length for the next attempt. + } + + await env.EV.put(slug, normalizedTarget); + + const { origin } = new URL(request.url); + const responsePayload = { + slug, + target: normalizedTarget, + shortUrl: `${origin}/${slug}`, + }; + + return new Response(JSON.stringify(responsePayload), { headers: JSON_HEADERS }); + } catch { + return new Response('Invalid request payload.', { status: 400, headers: CORS_HEADERS }); + } +} + +/** + * Handles redirecting a short URL to its target URL. + * @param {string} pathname The pathname from the request URL. + * @param {object} env The environment bindings. + * @returns {Promise} A redirect Response or null if the slug is not found. + */ +async function handleRedirectRequest(pathname, env) { + const slug = pathname.slice(1); + if (slug) { + const targetUrl = await env.EV.get(slug); + if (targetUrl) { + return Response.redirect(targetUrl, 302); + } + } + return null; +} + +/** + * Main request handler for the Cloudflare Worker. + */ +export async function onRequest({ request, env, next }) { + if (request.method === 'OPTIONS') { + return new Response(null, { headers: OPTIONS_HEADERS }); + } + + const url = new URL(request.url); + + if (url.pathname === '/api/create' && request.method === 'POST') { + return handleCreateRequest(request, env); + } + + const redirectResponse = await handleRedirectRequest(url.pathname, env); + if (redirectResponse) { + return redirectResponse; + } + + return next(); }