mirror of
https://github.com/multipleof4/4ev.link.git
synced 2026-01-13 23:57:55 +00:00
Refactor: Improve readability and maintainability
This commit is contained in:
@@ -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}){
|
const CORS_HEADERS = {
|
||||||
if(request.method==='OPTIONS')return new Response(null,{headers:{...H,'Access-Control-Allow-Methods':'POST','Access-Control-Allow-Headers':'Content-Type'}})
|
'Access-Control-Allow-Origin': '*',
|
||||||
const u=new URL(request.url)
|
};
|
||||||
if(u.pathname==='/api/create'&&request.method==='POST'){
|
const OPTIONS_HEADERS = {
|
||||||
try{
|
...CORS_HEADERS,
|
||||||
let{url:t,token:k}=await request.json()
|
'Access-Control-Allow-Methods': 'POST',
|
||||||
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)
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
return new Response('CAPTCHA failed',{status:403,headers:H})
|
};
|
||||||
t=n(t);if(!t)throw 0
|
const JSON_HEADERS = {
|
||||||
let l=4,s;do s=r(l++);while(await env.EV.get(s))
|
...CORS_HEADERS,
|
||||||
await env.EV.put(s,t)
|
'Content-Type': 'application/json',
|
||||||
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})}
|
|
||||||
|
/**
|
||||||
|
* 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<boolean>} 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<Response>} 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<Response|null>} 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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user