diff options
| author | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2025-06-23 23:29:19 +0100 |
|---|---|---|
| committer | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2025-06-23 23:29:19 +0100 |
| commit | b4110e89d9f06ce8b408e5e66ff61163efd45a9f (patch) | |
| tree | d1958c39b86520dbd55cf1735601e076817b5635 /source/main.js | |
| parent | 1277a5fbd9e0331d344a9c8f37fa5ef9c2dc2d71 (diff) | |
| download | zue.dev-b4110e89d9f06ce8b408e5e66ff61163efd45a9f.tar zue.dev-b4110e89d9f06ce8b408e5e66ff61163efd45a9f.tar.gz zue.dev-b4110e89d9f06ce8b408e5e66ff61163efd45a9f.tar.bz2 zue.dev-b4110e89d9f06ce8b408e5e66ff61163efd45a9f.tar.xz zue.dev-b4110e89d9f06ce8b408e5e66ff61163efd45a9f.zip | |
refactor: update main entry point to main.js and remove index.js
Diffstat (limited to 'source/main.js')
| -rw-r--r-- | source/main.js | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/source/main.js b/source/main.js new file mode 100644 index 0000000..21429f9 --- /dev/null +++ b/source/main.js @@ -0,0 +1,280 @@ +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. + The function will parse the request and return a response 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 + */ + 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(); + }, + + /* + Email event handler, this function will be called whenever an email is sent to the worker. + The function will parse the email message and forward it to a specified email address. + + @param {Message} message - the incoming email message object + @param {Environment} environment - the environment object + @param {Context} context - the context object + + @returns {void} + */ + async email(message, environment, context) { + message.forward("alex@zue.dev"); + }, + + /* + Scheduled event handler, this function will be called whenever a scheduled event is triggered. + The function will perform a task and return a response based on the task outcome. + + @param {Event} event - the incoming event object + @param {Environment} environment - the environment object + @param {Context} context - the context object + + @returns {void} + */ + async scheduled(event, environment, context) { + console.log("Scheduled event triggered!"); + }, +}; |
