| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  | import http from "node:http" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  | export interface Handler { | 
					
						
							|  |  |  |     mustMatch: string | RegExp | 
					
						
							|  |  |  |     mimetype: string | 
					
						
							|  |  |  |     addHeaders?: Record<string, string> | 
					
						
							| 
									
										
										
										
											2024-06-24 13:11:35 +02:00
										 |  |  |     handle: ( | 
					
						
							|  |  |  |         path: string, | 
					
						
							|  |  |  |         queryParams: URLSearchParams, | 
					
						
							| 
									
										
										
										
											2024-07-19 20:43:46 +02:00
										 |  |  |         req: http.IncomingMessage, | 
					
						
							|  |  |  |         body: string | undefined | 
					
						
							| 
									
										
										
										
											2024-06-24 13:11:35 +02:00
										 |  |  |     ) => Promise<string> | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ServerUtils { | 
					
						
							|  |  |  |     public static getBody(req: http.IncomingMessage): Promise<string> { | 
					
						
							|  |  |  |         return new Promise<string>((resolve) => { | 
					
						
							| 
									
										
										
										
											2024-06-24 13:11:35 +02:00
										 |  |  |             let body = "" | 
					
						
							|  |  |  |             req.on("data", (chunk) => { | 
					
						
							|  |  |  |                 body += chunk | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             req.on("end", () => { | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  |                 resolve(body) | 
					
						
							| 
									
										
										
										
											2024-06-24 13:11:35 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  | export class Server { | 
					
						
							|  |  |  |     constructor( | 
					
						
							|  |  |  |         port: number, | 
					
						
							|  |  |  |         options: { | 
					
						
							|  |  |  |             ignorePathPrefix?: string[] | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  |         handle: Handler[] | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  |     ) { | 
					
						
							|  |  |  |         handle.push({ | 
					
						
							|  |  |  |             mustMatch: "", | 
					
						
							|  |  |  |             mimetype: "text/html", | 
					
						
							|  |  |  |             handle: async () => { | 
					
						
							|  |  |  |                 return `<html><body>Supported endpoints are <ul>${handle | 
					
						
							|  |  |  |                     .filter((h) => h.mustMatch !== "") | 
					
						
							|  |  |  |                     .map((h) => { | 
					
						
							|  |  |  |                         let l = h.mustMatch | 
					
						
							|  |  |  |                         if (typeof h.mustMatch === "string") { | 
					
						
							|  |  |  |                             l = `<a href='${l}'>${l}</a>` | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         return "<li>" + l + "</li>" | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |                     .join("")}</ul></body></html>`
 | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         http.createServer(async (req: http.IncomingMessage, res) => { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const url = new URL(`http://127.0.0.1/` + req.url) | 
					
						
							|  |  |  |                 let path = url.pathname | 
					
						
							|  |  |  |                 while (path.startsWith("/")) { | 
					
						
							|  |  |  |                     path = path.substring(1) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |                 console.log( | 
					
						
							|  |  |  |                     req.method + " " + req.url, | 
					
						
							|  |  |  |                     "from:", | 
					
						
							|  |  |  |                     req.headers.origin, | 
					
						
							|  |  |  |                     new Date().toISOString(), | 
					
						
							|  |  |  |                     path | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  |                 if (options?.ignorePathPrefix) { | 
					
						
							|  |  |  |                     for (const toIgnore of options.ignorePathPrefix) { | 
					
						
							|  |  |  |                         if (path.startsWith(toIgnore)) { | 
					
						
							|  |  |  |                             path = path.substring(toIgnore.length + 1) | 
					
						
							|  |  |  |                             break | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const handler = handle.find((h) => { | 
					
						
							|  |  |  |                     if (typeof h.mustMatch === "string") { | 
					
						
							|  |  |  |                         return h.mustMatch === path | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if (path.match(h.mustMatch)) { | 
					
						
							|  |  |  |                         return true | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (handler === undefined || handler === null) { | 
					
						
							|  |  |  |                     res.writeHead(404, { "Content-Type": "text/html" }) | 
					
						
							|  |  |  |                     res.write("<html><body><p>Not found...</p></body></html>") | 
					
						
							|  |  |  |                     res.end() | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 res.setHeader( | 
					
						
							|  |  |  |                     "Access-Control-Allow-Headers", | 
					
						
							|  |  |  |                     "Origin, X-Requested-With, Content-Type, Accept" | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") | 
					
						
							|  |  |  |                 if (req.method === "OPTIONS") { | 
					
						
							|  |  |  |                     res.setHeader( | 
					
						
							|  |  |  |                         "Access-Control-Allow-Methods", | 
					
						
							|  |  |  |                         "POST, GET, OPTIONS, DELETE, UPDATE" | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                     res.writeHead(204, { "Content-Type": handler.mimetype }) | 
					
						
							|  |  |  |                     res.end() | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-07-19 20:43:46 +02:00
										 |  |  |                 let body: string | undefined = undefined | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  |                 if (req.method === "POST" || req.method === "UPDATE") { | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  |                     body = await ServerUtils.getBody(req) | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (req.method === "DELETE") { | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 try { | 
					
						
							| 
									
										
										
										
											2024-06-20 14:22:33 +02:00
										 |  |  |                     const result = await handler.handle(path, url.searchParams, req, body) | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                     if (result === undefined) { | 
					
						
							| 
									
										
										
										
											2024-04-30 15:59:19 +02:00
										 |  |  |                         res.writeHead(500) | 
					
						
							|  |  |  |                         res.write("Could not fetch this website, probably blocked by them") | 
					
						
							|  |  |  |                         res.end() | 
					
						
							|  |  |  |                         return | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     if (typeof result !== "string") { | 
					
						
							|  |  |  |                         console.error( | 
					
						
							|  |  |  |                             "Internal server error: handling", | 
					
						
							|  |  |  |                             url, | 
					
						
							|  |  |  |                             "resulted in a ", | 
					
						
							|  |  |  |                             typeof result, | 
					
						
							|  |  |  |                             " instead of a string:", | 
					
						
							|  |  |  |                             result | 
					
						
							|  |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |                     } | 
					
						
							|  |  |  |                     const extraHeaders = handler.addHeaders ?? {} | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     res.writeHead(200, { "Content-Type": handler.mimetype, ...extraHeaders }) | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                     res.write("" + result) | 
					
						
							| 
									
										
										
										
											2024-02-22 11:57:31 +01:00
										 |  |  |                     res.end() | 
					
						
							|  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     console.error("Could not handle request:", e) | 
					
						
							|  |  |  |                     res.writeHead(500) | 
					
						
							|  |  |  |                     res.write(e) | 
					
						
							|  |  |  |                     res.end() | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error("FATAL:", e) | 
					
						
							|  |  |  |                 res.end() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }).listen(port) | 
					
						
							|  |  |  |         console.log( | 
					
						
							|  |  |  |             "Server is running on port " + port, | 
					
						
							|  |  |  |             ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |