/** * Configuration and Constants */ const CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const BASE_SLUG_LENGTH = 4; 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; } } }; /** * 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(); }