From 87dd7a5aa1eaba102ba488599d29449f06072f9d Mon Sep 17 00:00:00 2001 From: "Alex Pooley (@zuedev)" Date: Mon, 23 Jun 2025 23:48:24 +0100 Subject: refactor: simplify request handling by removing Router class and using switch-case structure --- source/main.js | 452 +++++++++++++++++++++++++++------------------------------ 1 file changed, 216 insertions(+), 236 deletions(-) diff --git a/source/main.js b/source/main.js index 21429f9..7bde481 100644 --- a/source/main.js +++ b/source/main.js @@ -1,113 +1,5 @@ import puppeteer from "@cloudflare/puppeteer"; -/* - Router class to handle routing of requests based on the request path. - - @param {Request} request - the incoming request object - @param {Environment} environment - the environment object - @param {Context} context - the context object - - @returns {Response} a new Response object -*/ -class Router { - constructor(request, environment, context) { - this.request = request; - this.environment = environment; - this.context = context; - this.routes = new Map(); // Use Map for faster lookups - this.parameters = {}; // Store extracted route parameters - } - - /* - Add a new route to the router. - - @param {string} path - the path to match - @param {function} handler - the handler function to call if the path matches - - @returns {Router} the router object - */ - add(path, handler) { - if (typeof path !== "string") { - throw new TypeError("Path must be a string"); - } - if (typeof handler !== "function") { - throw new TypeError("Handler must be a function"); - } - - const normalizedPath = path.replace(/\/+$/, ""); // Normalize path - const pathRegex = new RegExp( - "^" + - normalizedPath - .replace(/:[^/]+/g, "([^/]+)") // Convert :param to regex group - .replace(/\//g, "\\/") + - "$" - ); - this.routes.set(pathRegex, { handler, originalPath: normalizedPath }); - return this; - } - - /* - Helper function to respond with a JSON object. - - @param {object} body - the JSON object to respond with - - @returns {Response} a new Response object - */ - respond(body, status = 200) { - return new Response(JSON.stringify(body), { - status, - headers: { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - }, - }); - } - - /* - Route the request to the appropriate handler based on the request path. - - @returns {Response} a new Response object - */ - route() { - try { - const url = new URL(this.request.url); - const normalizedPath = url.pathname.replace(/\/+$/, ""); // Remove trailing slashes - - for (const [pathRegex, { handler, originalPath }] of this.routes) { - const match = normalizedPath.match(pathRegex); - if (match) { - // Extract parameters - const paramNames = [...originalPath.matchAll(/:([^/]+)/g)].map( - (m) => m[1] - ); - this.parameters = paramNames.reduce((params, name, index) => { - params[name] = match[index + 1]; - return params; - }, {}); - - return handler(this.request, this.environment, this.context); - } - } - - // Return 404 if route not found - return this.respond( - { - error: `Route not found: ${normalizedPath}`, - }, - 404 - ); - } catch (error) { - // Handle unexpected errors - return this.respond( - { - error: `Internal Server Error: ${error.message}`, - }, - 500 - ); - } - } -} - export default { /* Fetch event handler, this function will be called whenever a request is made to the worker. @@ -120,134 +12,222 @@ export default { @returns {Response} a new Response object */ async fetch(request, environment, context) { - const router = new Router(request, environment, context); - - router.add("/", () => { - return router.respond({ - message: "Hello, World! :3", - }); - }); - - router.add("/status", (request) => { - const url = new URL(request.url); - const service = url.searchParams.get("service"); - - const acceptedServices = [ - "dns", - "load-balancer", - "cdn", - "functions", - "mysql-cluster", - "mongodb-cluster", - "redis-cluster", - "elasticsearch-cluster", - "git-connector", - "job-runners", - "container-registry", - "kubernetes-cluster", - "bare-metal-servers", - "game-server-api", - "anti-ddos-protection", - "anti-cheat-api", - ]; - - if (acceptedServices.includes(service)) - return router.respond({ - status: "ok", - }); - - return router.respond({ - error: `service not found`, - }); - }); - - router.add("/96/twitch/streaming", async (request) => { - /* - Checks if a twitch channel is live by fetching the "live" preview image of the channel, - if the image is fetched successfully, then the channel is live, otherwise it's offline. - - @param {string} channel - the twitch channel name - @returns {boolean} true if the channel is live, false otherwise - */ - async function isTwitchChannelLive(channel) { - // construct preview image url with channel name - const livePreviewUrl = `https://static-cdn.jtvnw.net/previews-ttv/live_user_${channel}-320x180.jpg`; - - // fetch the preview image, don't follow redirects - const response = await fetch(livePreviewUrl, { - redirect: "manual", - }); - - // check if the image was fetched successfully - return response.ok; - } - - const url = new URL(request.url); - const channel = url.searchParams.get("channel"); - - if (!channel) - return router.respond({ - error: `channel not provided`, - }); - - const whitelist = [ - "zuedev", - ...["vtsweets", "bunnibana", "yayjaybae", "justawoney", "tygiwygi"], - ]; - - if (!whitelist.includes(channel)) - return router.respond({ - error: `channel not whitelisted`, - }); - - const channelLive = await isTwitchChannelLive(channel); - - if (channelLive) - return router.respond({ - status: "live", - }); - - return router.respond({ - status: "offline", - }); - }); - - router.add("/browser-rendering/screenshot", async (request) => { - const { searchParams } = new URL(request.url); - const url = searchParams.get("url"); - const type = searchParams.get("type") || "webp"; - const fullPage = searchParams.get("fullPage") !== null; - const width = parseInt(searchParams.get("width"), 10) || 1920; - const height = parseInt(searchParams.get("height"), 10) || 1080; - - if (!url) - return router.respond({ - error: "URL parameter is required", - }); - - if (!/^https?:\/\//.test(url)) - return router.respond({ - error: "Invalid URL format", - }); - - const browser = await puppeteer.launch(environment.MYBROWSER); - const page = await browser.newPage(); - await page.setViewport({ width, height }); - await page.goto(url); - const screenshot = await page.screenshot({ - type, - fullPage, - }); - await browser.close(); - - return new Response(screenshot, { - headers: { - "content-type": `image/${type}`, - }, - }); - }); - - return router.route(); + const { pathname } = new URL(request.url); + + switch (pathname) { + case "/": + return (() => { + return new Response( + JSON.stringify({ + message: "Hello, World! :3", + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/status": + return (() => { + const url = new URL(request.url); + const service = url.searchParams.get("service"); + + const acceptedServices = [ + "dns", + "load-balancer", + "cdn", + "functions", + "mysql-cluster", + "mongodb-cluster", + "redis-cluster", + "elasticsearch-cluster", + "git-connector", + "job-runners", + "container-registry", + "kubernetes-cluster", + "bare-metal-servers", + "game-server-api", + "anti-ddos-protection", + "anti-cheat-api", + ]; + + if (acceptedServices.includes(service)) + return new Response( + JSON.stringify({ + status: "ok", + service, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + return new Response( + JSON.stringify({ + error: `service not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/96/twitch/streaming": + return (async () => { + /* + Checks if a twitch channel is live by fetching the "live" preview image of the channel, + if the image is fetched successfully, then the channel is live, otherwise it's offline. + + @param {string} channel - the twitch channel name + @returns {boolean} true if the channel is live, false otherwise + */ + async function isTwitchChannelLive(channel) { + // construct preview image url with channel name + const livePreviewUrl = `https://static-cdn.jtvnw.net/previews-ttv/live_user_${channel}-320x180.jpg`; + + // fetch the preview image, don't follow redirects + const response = await fetch(livePreviewUrl, { + redirect: "manual", + }); + + // check if the image was fetched successfully + return response.ok; + } + + const url = new URL(request.url); + const channel = url.searchParams.get("channel"); + + if (!channel) + return new Response( + JSON.stringify({ + error: `channel not provided`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const whitelist = [ + "zuedev", + ...["vtsweets", "bunnibana", "yayjaybae", "justawoney", "tygiwygi"], + ]; + + if (!whitelist.includes(channel)) + return new Response( + JSON.stringify({ + error: `channel not whitelisted`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const channelLive = await isTwitchChannelLive(channel); + + if (channelLive) + return new Response( + JSON.stringify({ + status: "live", + channel, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + return new Response( + JSON.stringify({ + status: "offline", + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/browser-rendering/screenshot": + return (async () => { + const { searchParams } = new URL(request.url); + const url = searchParams.get("url"); + const type = searchParams.get("type") || "webp"; + const fullPage = searchParams.get("fullPage") !== null; + const width = parseInt(searchParams.get("width"), 10) || 1920; + const height = parseInt(searchParams.get("height"), 10) || 1080; + + if (!url) + return new Response( + JSON.stringify({ + error: "URL parameter is required", + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + if (!/^https?:\/\//.test(url)) + return new Response( + JSON.stringify({ + error: "Invalid URL format", + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const browser = await puppeteer.launch(environment.MYBROWSER); + const page = await browser.newPage(); + await page.setViewport({ width, height }); + await page.goto(url); + const screenshot = await page.screenshot({ + type, + fullPage, + }); + await browser.close(); + + return new Response(screenshot, { + headers: { + "content-type": `image/${type}`, + }, + }); + })(); + break; + default: + return new Response( + JSON.stringify({ + error: `path not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + } }, /* -- cgit v1.2.3