├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── build ├── index.d.ts ├── index.js └── index.js.map ├── examples ├── dir │ └── Banner.png └── example.js ├── package-lock.json ├── package.json ├── src └── index.ts ├── tsconfig.json └── webstep.mp3 /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*.ts] 3 | indent_style = tab 4 | indent_size = 4 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "commonjs": true, 6 | "es2021": true 7 | }, 8 | "extends": "comfycase", 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "@typescript-eslint" 16 | ], 17 | "rules": { 18 | "no-unused-vars": "off", 19 | "@typescript-eslint/no-unused-vars": [ 20 | "warn" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Instafluff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebWebWeb 2 | The zero-dependency alternative to ExpressJS. We built the Comfiest Way to make web APIs and static file servers live on Twitch for Coding Cafe! 3 | 4 | **WebWebWeb** lets you create a web server with APIs ***SUPER EASILY*** in just a few lines of code. 5 | 6 | *If you need a secure web server with SSL certificates from [Let's Encrypt](https://www.letsencrypt.org), check out [WebWebWebs](https://www.github.com/instafluff/WebWebWebs) which will automatically retrieve and renew them for you while maintaining the simplicity of WebWebWeb!* 7 | 8 | ## Instafluff ## 9 | > *Like these projects? The best way to support my open-source projects is by becoming a Comfy Sponsor on GitHub!* 10 | 11 | > https://github.com/sponsors/instafluff 12 | 13 | > *Come and hang out with us at the Comfiest Corner on Twitch!* 14 | 15 | > https://twitch.tv/instafluff 16 | 17 | ## Instructions ## 18 | 19 | 1. Install `webwebweb` 20 | ``` 21 | npm install webwebweb --save 22 | ``` 23 | 24 | 2. Start the server on a port (e.g. 8099). Any HTML pages (e.g. index.html) can be placed in the root directory `/` and static files (e.g. images, scripts, and other HTML pages) can go into `/web` or `/public` and it will be served automagically in `http://locahost:8099/web` or `http://localhost:8099/public` 25 | ```javascript 26 | require( "webwebweb" ).Run( 8099 ); 27 | ``` 28 | 29 | 3. (Optional) Add APIs 30 | ```javascript 31 | var ComfyWeb = require( "webwebweb" ); 32 | ComfyWeb.APIs[ "/" ] = ( qs, body, opts ) => { 33 | return { "test": "example!" }; 34 | }; 35 | ComfyWeb.Run( 8099 ); 36 | ``` 37 | 38 | ### Options ### 39 | 40 | The `Run()` function in **WebWebWeb** accepts several optional parameters: 41 | - useCORS (default: true) 42 | - Certificate 43 | - PrivateKey 44 | - CertificateChain 45 | 46 | ## Handling POST/PUT/DELETE requests ## 47 | All request methods are sent to the API handler. You can check the `opts.req.method` value to response accordingly and parse the body object for data. 48 | ```javascript 49 | var ComfyWeb = require( "webwebweb" ); 50 | ComfyWeb.APIs[ "/account" ] = ( qs, body, opts ) => { 51 | switch( opts.req.method ) { 52 | case "GET": 53 | return { "account": "test" }; 54 | case "POST": 55 | return JSON.parse( body ); 56 | case "PUT": 57 | return { "status": "updated" }; 58 | case "DELETE": 59 | return {}; 60 | } 61 | }; 62 | ComfyWeb.Run( 8099 ); 63 | ``` 64 | 65 | ## Reading Request Headers ## 66 | The request object is passed in to the API handler. You can check for header values in `opts.req.headers`. 67 | ```javascript 68 | var ComfyWeb = require( "webwebweb" ); 69 | ComfyWeb.APIs[ "/" ] = ( qs, body, opts ) => { 70 | return opts.req.headers; 71 | }; 72 | ComfyWeb.Run( 8099 ); 73 | ``` 74 | 75 | ## Enabling CORS ## 76 | Actually, CORS is enabled by default. To disable CORS, set the `useCORS` parameter: 77 | ```javascript 78 | var ComfyWeb = require( "webwebweb" ); 79 | ComfyWeb.Run( 8099, { 80 | useCORS: false 81 | } ); 82 | ``` 83 | 84 | ## Using SSL Certificates ## 85 | To add TLS support, pass in the paths to your Certificate, Key, and Certificate Chain files: 86 | ```javascript 87 | var ComfyWeb = require( "webwebweb" ); 88 | ComfyWeb.Run( 8099, { 89 | Certificate: "cert.pem", 90 | PrivateKey: "key.pem", 91 | CertificateChain: "chain.pem" 92 | } ); 93 | ``` 94 | 95 | ## Credits ## 96 | Thank you to all the participants of this project! 97 | 98 | **MacABearMan, Instafriend, That_MS_Gamer, Instafluff, ChatTranslator, sethorizer, simrose4u, Gilokk0, RIKACHET, UltraHal1, SaltPrincessGretchen, Ella_Fint, DutchGamer46, AntiViGames, aj2017, SoundOfGaming, DEAD_P1XL, smilesandtea, MerlinLeWizard, my_sweet_clementine, rockysenpai24, tabetaicooking, sparky_pugwash, violettepanda, TheSkiDragon, radiocaf, LinkoNetwork, jawibae, ElysiaGriffin, DarrnyH, jellydance, DevMerlin, marss112, roberttables, tiger_k1ng, LilyHazel, Psychosys82, BungalowGlow, Stay_Hydrated_Bot, pookiepew, Copperbeardy, TheHugoDahl, wil_bennett, WolvesGamingDen, FuriousFur, SausageCam, Kyoslilmonster, EndlessMoonfall, JD_Hirsch, guthron, shinageeexpress, JMSWRNR, schmiel_show, KitAnnLIVE, space_butts, lukepistachio, pipskidoodle, Kara_Kim, SIeepyMia, itsmechrisg, tapemoose, XandyCTz, Thrennenne, kollecz, Hytheria, YoursTrulyGreed** 99 | -------------------------------------------------------------------------------- /build/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | import http from "http"; 5 | import https from "https"; 6 | declare const comfyWeb: { 7 | APIs: { 8 | [key: string]: (qs: any, body: Buffer | null, context: { 9 | req: http.IncomingMessage; 10 | res: http.ServerResponse; 11 | params: string[]; 12 | }) => Promise | any; 13 | }; 14 | Files: { 15 | [key: string]: (qs: any, body: Buffer | null, context: { 16 | req: http.IncomingMessage; 17 | res: http.ServerResponse; 18 | }) => Promise | any; 19 | }; 20 | Settings: { 21 | Directory?: string; 22 | }; 23 | Run: (port: number, { useCORS, Certificate, PrivateKey, CertificateChain, Directory }: { 24 | useCORS?: boolean; 25 | Certificate?: string; 26 | PrivateKey?: string; 27 | CertificateChain?: string; 28 | Directory?: string; 29 | }) => Promise; 30 | default: any; 31 | }; 32 | export = comfyWeb; 33 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | const url_1 = require("url"); 15 | const promises_1 = __importDefault(require("fs/promises")); 16 | const path_1 = __importDefault(require("path")); 17 | const querystring_1 = __importDefault(require("querystring")); 18 | // Static File Serve Code based on https://adrianmejia.com/building-a-node-js-static-file-server-files-over-http-using-es6/ 19 | const mimeType = { 20 | ".ico": "image/x-icon", 21 | ".html": "text/html", 22 | ".js": "text/javascript", 23 | ".json": "application/json", 24 | ".css": "text/css", 25 | ".png": "image/png", 26 | ".jpg": "image/jpeg", 27 | ".wav": "audio/wav", 28 | ".mp3": "audio/mpeg", 29 | ".svg": "image/svg+xml", 30 | ".pdf": "application/pdf", 31 | ".doc": "application/msword", 32 | ".eot": "appliaction/vnd.ms-fontobject", 33 | ".ttf": "aplication/font-sfnt", 34 | }; 35 | function sanitizePath(filePath) { 36 | return path_1.default.normalize(filePath).replace(/^(\.\.[\/\\])+/, ""); 37 | } 38 | function getMatchingRoute(routes, urlPath) { 39 | let pathParts = urlPath.split("/"); 40 | let params = []; 41 | let route = routes.find(route => { 42 | if (urlPath !== route) { 43 | // Check if any patterns match the urlPath 44 | let routeParts = route.split("/"); 45 | if (pathParts.length !== routeParts.length) { 46 | return false; 47 | } 48 | params = []; 49 | for (let p = 0; p < pathParts.length; p++) { 50 | if (routeParts[p] === "*") { 51 | params.push(pathParts[p]); // Push the parameter 52 | } 53 | else if (pathParts[p] !== routeParts[p]) { 54 | return false; 55 | } 56 | } 57 | } 58 | // We matched! 59 | return true; 60 | }); 61 | if (route) { 62 | return { 63 | route: route, 64 | params: params, 65 | }; 66 | } 67 | return null; 68 | } 69 | function serveFile(pathname, res) { 70 | return __awaiter(this, void 0, void 0, function* () { 71 | try { 72 | yield promises_1.default.access(pathname); 73 | const stat = yield promises_1.default.stat(pathname); 74 | if (stat.isDirectory()) { 75 | pathname += "/index.html"; 76 | } 77 | const data = yield promises_1.default.readFile(pathname); 78 | const ext = path_1.default.parse(pathname).ext; 79 | if (!res.getHeader("Content-Type")) { 80 | res.setHeader("Content-type", mimeType[ext] || "text/plain"); 81 | } 82 | res.end(data); 83 | } 84 | catch (err) { 85 | if (err.code === "ENOENT") { 86 | res.statusCode = 404; 87 | res.end(`File ${pathname} not found!`); 88 | } 89 | else { 90 | res.statusCode = 500; 91 | res.end(`Error getting the file: ${err}.`); 92 | } 93 | } 94 | }); 95 | } 96 | function readPostData(req) { 97 | return new Promise((resolve, reject) => { 98 | let body = null; 99 | req.on("data", chunk => { 100 | if (body === null) { 101 | body = chunk; 102 | } 103 | else { 104 | body = Buffer.concat([body, chunk]); 105 | } 106 | }); 107 | req.on("end", () => resolve(body)); 108 | req.on("error", (err) => reject(err)); 109 | }); 110 | } 111 | let isCORSEnabled = true; 112 | function webHandler(req, res) { 113 | return __awaiter(this, void 0, void 0, function* () { 114 | if (isCORSEnabled) { 115 | // Handle CORS 116 | res.setHeader("Access-Control-Allow-Origin", "*"); 117 | res.setHeader("Access-Control-Request-Method", "*"); 118 | res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE"); 119 | res.setHeader("Access-Control-Allow-Headers", "*"); 120 | if (req.method === "OPTIONS") { 121 | res.writeHead(204); 122 | res.end(); 123 | return; 124 | } 125 | } 126 | try { 127 | const rawUrl = req.url; 128 | const parsedUrl = new url_1.URL(rawUrl, `http://${req.headers.host}`); 129 | const qs = querystring_1.default.decode(parsedUrl.searchParams.toString()); 130 | // Check for default file paths 131 | if (rawUrl.startsWith("/web") || rawUrl.startsWith("/public")) { 132 | const sanitizedPath = sanitizePath(parsedUrl.pathname); 133 | let pathname = path_1.default.join(path_1.default.resolve("."), sanitizedPath); 134 | yield serveFile(pathname, res); 135 | return; 136 | } 137 | const urlPath = comfyWeb.APIs[parsedUrl.pathname] ? parsedUrl.pathname : parsedUrl.pathname.substring(1); 138 | // Find matching API route 139 | const apiRoute = comfyWeb.APIs[urlPath] ? { route: urlPath, params: [] } : getMatchingRoute(Object.keys(comfyWeb.APIs), urlPath); 140 | if (comfyWeb.APIs && apiRoute) { 141 | // Handle API Request 142 | const body = req.method === "POST" ? yield readPostData(req) : null; 143 | const result = yield comfyWeb.APIs[apiRoute.route](qs, body, { req, res, params: apiRoute.params }); 144 | if (!res.getHeader("Content-Type")) { 145 | res.setHeader("Content-type", Array.isArray(result) || typeof result === "object" ? "application/json" : "text/plain"); 146 | } 147 | res.end(typeof result === "object" ? JSON.stringify(result) : result); 148 | } 149 | else { 150 | const fileRoute = comfyWeb.Files[urlPath] ? { route: urlPath, params: [] } : getMatchingRoute(Object.keys(comfyWeb.Files), urlPath); 151 | // Handle File/Default Request 152 | const sanitizedPath = sanitizePath(parsedUrl.pathname); 153 | // Check for index.html and default file paths 154 | const possibleFilePaths = [ 155 | path_1.default.join(comfyWeb.Settings.Directory || ".", sanitizedPath), 156 | path_1.default.join(".", "web", sanitizedPath), 157 | path_1.default.join(".", "public", sanitizedPath), 158 | path_1.default.join(comfyWeb.Settings.Directory || ".", sanitizedPath, "index.html"), 159 | path_1.default.join(".", "web", sanitizedPath, "index.html"), 160 | path_1.default.join(".", "public", sanitizedPath, "index.html"), 161 | ]; 162 | if (sanitizedPath.endsWith(".html") || sanitizedPath.endsWith(".css")) { 163 | // Add root path as an additional possible path 164 | possibleFilePaths.push(path_1.default.join(".", sanitizedPath)); 165 | } 166 | for (const possiblePath of possibleFilePaths) { 167 | try { 168 | yield promises_1.default.access(possiblePath); 169 | if (comfyWeb.Files && fileRoute) { 170 | yield comfyWeb.Files[fileRoute.route](qs, null, { req, res }); 171 | } 172 | yield serveFile(possiblePath, res); 173 | return; 174 | } 175 | catch (err) { 176 | // Skip if file doesn't exist, otherwise throw an error 177 | if (err.code !== "ENOENT") { 178 | throw err; 179 | } 180 | } 181 | } 182 | // No file found 183 | res.statusCode = 404; 184 | res.end(`File not found`); 185 | } 186 | } 187 | catch (err) { 188 | console.error("Web Request Error:", req.url, err); 189 | res.statusCode = 500; 190 | res.end(`Error`); 191 | } 192 | }); 193 | } 194 | function startServer(port, { useCORS, Certificate, PrivateKey, CertificateChain, Directory } = { useCORS: true }) { 195 | return __awaiter(this, void 0, void 0, function* () { 196 | let server; 197 | isCORSEnabled = !!useCORS; 198 | if (Certificate && PrivateKey) { 199 | const privateKey = yield promises_1.default.readFile(PrivateKey, "utf8"); 200 | const certificate = yield promises_1.default.readFile(Certificate, "utf8"); 201 | const ca = CertificateChain ? yield promises_1.default.readFile(CertificateChain, "utf8") : undefined; 202 | const credentials = { 203 | key: privateKey, 204 | cert: certificate, 205 | ca: ca, 206 | }; 207 | server = require("https").createServer(credentials, webHandler); 208 | } 209 | else { 210 | server = require("http").createServer(webHandler); 211 | } 212 | comfyWeb.Settings.Directory = Directory; 213 | server.listen(port, (err) => { 214 | if (err) { 215 | return console.error("WebWebWeb could not start:", err); 216 | } 217 | console.log(`WebWebWeb is running on ${port}`); 218 | }); 219 | return server; 220 | }); 221 | } 222 | const comfyWeb = { 223 | APIs: {}, 224 | Files: {}, 225 | Settings: {}, 226 | Run: startServer, 227 | default: undefined, 228 | }; 229 | comfyWeb.default = comfyWeb; // Make this a default export as well to support ES6 import syntax 230 | module.exports = comfyWeb; 231 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /build/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /examples/dir/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instafluff/WebWebWeb/8177fe037e8261cea3de7b8bde0e52274534b860/examples/dir/Banner.png -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | // const ComfyWeb = require( "../index" ); 2 | const ComfyWeb = require( "../build/index" ); 3 | console.log( ComfyWeb ); 4 | 5 | ComfyWeb.APIs[ "/" ] = ( qs, body ) => { 6 | console.log( qs ); 7 | console.log( body ); 8 | return { "test": "example!" }; 9 | }; 10 | 11 | ComfyWeb.APIs[ "color" ] = ( qs ) => { 12 | console.log( qs ); 13 | return { "color": "RED" }; 14 | }; 15 | 16 | ComfyWeb.APIs[ "string" ] = ( qs ) => { 17 | console.log( qs ); 18 | return "test string"; 19 | }; 20 | 21 | ComfyWeb.APIs[ "array" ] = ( qs ) => { 22 | console.log( qs ); 23 | return [ "one", "2", "three" ]; 24 | }; 25 | 26 | ComfyWeb.APIs[ "route/*" ] = ( qs, body, opts ) => { 27 | console.log( opts.params ); 28 | return opts.params; 29 | }; 30 | 31 | function sleep( ms ) { 32 | return new Promise( resolve => setTimeout( resolve, ms ) ); 33 | } 34 | 35 | ComfyWeb.APIs[ "async" ] = async ( qs ) => { 36 | console.log( qs ); 37 | await sleep( 1000 ); 38 | return "complete"; 39 | }; 40 | 41 | ComfyWeb.Run( 8099, { Directory: "dir" } ); 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webwebweb", 3 | "version": "1.6.1", 4 | "description": "The Comfiest Way to make web APIs and static file servers", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "npm run build && node build/index.js", 9 | "build": "tsc", 10 | "clean": "rimraf ./build/", 11 | "lint": "eslint . --ext .js,.ts" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/instafluff/WebWebWeb.git" 16 | }, 17 | "keywords": [ 18 | "Instafluff", 19 | "Comfy", 20 | "web", 21 | "api", 22 | "static", 23 | "file", 24 | "http", 25 | "server" 26 | ], 27 | "author": "Instafluff", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/instafluff/WebWebWeb/issues" 31 | }, 32 | "homepage": "https://github.com/instafluff/WebWebWeb#readme", 33 | "devDependencies": { 34 | "@types/jest": "^29.2.5", 35 | "@types/node": "^20.2.5", 36 | "@typescript-eslint/eslint-plugin": "^5.48.0", 37 | "@typescript-eslint/parser": "^5.48.0", 38 | "eslint": "^8.31.0", 39 | "eslint-config-comfycase": "^0.1.2", 40 | "jest": "^29.3.1", 41 | "rimraf": "^3.0.2", 42 | "typescript": "^4.9.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { URL } from "url"; 2 | import fs from "fs/promises"; 3 | import path from "path"; 4 | import querystring from "querystring"; 5 | import http from "http"; 6 | import https from "https"; 7 | 8 | // Static File Serve Code based on https://adrianmejia.com/building-a-node-js-static-file-server-files-over-http-using-es6/ 9 | const mimeType: { [key: string]: string } = { 10 | ".ico": "image/x-icon", 11 | ".html": "text/html", 12 | ".js": "text/javascript", 13 | ".json": "application/json", 14 | ".css": "text/css", 15 | ".png": "image/png", 16 | ".jpg": "image/jpeg", 17 | ".wav": "audio/wav", 18 | ".mp3": "audio/mpeg", 19 | ".svg": "image/svg+xml", 20 | ".pdf": "application/pdf", 21 | ".doc": "application/msword", 22 | ".eot": "appliaction/vnd.ms-fontobject", 23 | ".ttf": "aplication/font-sfnt", 24 | }; 25 | 26 | function sanitizePath( filePath: string ): string { 27 | return path.normalize( filePath ).replace( /^(\.\.[\/\\])+/, "" ); 28 | } 29 | 30 | function getMatchingRoute( routes: string[], urlPath: string ) { 31 | let pathParts = urlPath.split( "/" ); 32 | let params: string[] = []; 33 | let route = routes.find( route => { 34 | if( urlPath !== route ) { 35 | // Check if any patterns match the urlPath 36 | let routeParts = route.split( "/" ); 37 | if( pathParts.length !== routeParts.length ) { 38 | return false; 39 | } 40 | params = []; 41 | for( let p = 0; p < pathParts.length; p++ ) { 42 | if( routeParts[ p ] === "*" ) { 43 | params.push( pathParts[ p ] ); // Push the parameter 44 | } 45 | else if( pathParts[ p ] !== routeParts[ p ] ) { 46 | return false; 47 | } 48 | } 49 | } 50 | // We matched! 51 | return true; 52 | } ); 53 | if( route ) { 54 | return { 55 | route: route, 56 | params: params, 57 | }; 58 | } 59 | return null; 60 | } 61 | 62 | async function serveFile( pathname: string, res: http.ServerResponse ) { 63 | try { 64 | await fs.access( pathname ); 65 | const stat = await fs.stat( pathname ); 66 | if( stat.isDirectory() ) { 67 | pathname += "/index.html"; 68 | } 69 | const data = await fs.readFile( pathname ); 70 | const ext = path.parse( pathname ).ext; 71 | if( !res.getHeader( "Content-Type" ) ) { 72 | res.setHeader( "Content-type", mimeType[ ext ] || "text/plain" ); 73 | } 74 | res.end( data ); 75 | } 76 | catch( err: any ) { 77 | if( err.code === "ENOENT" ) { 78 | res.statusCode = 404; 79 | res.end( `File ${pathname} not found!` ); 80 | } 81 | else { 82 | res.statusCode = 500; 83 | res.end( `Error getting the file: ${err}.` ); 84 | } 85 | } 86 | } 87 | 88 | function readPostData( req: http.IncomingMessage ): Promise { 89 | return new Promise( ( resolve, reject ) => { 90 | let body: Buffer | null = null; 91 | req.on( "data", chunk => { 92 | if( body === null ) { 93 | body = chunk; 94 | } 95 | else { 96 | body = Buffer.concat( [ body, chunk ] ); 97 | } 98 | } ); 99 | req.on( "end", () => resolve( body! ) ); 100 | req.on( "error", ( err ) => reject( err ) ); 101 | } ); 102 | } 103 | 104 | let isCORSEnabled: boolean = true; 105 | 106 | async function webHandler( req: http.IncomingMessage, res: http.ServerResponse ) { 107 | if( isCORSEnabled ) { 108 | // Handle CORS 109 | res.setHeader( "Access-Control-Allow-Origin", "*" ); 110 | res.setHeader( "Access-Control-Request-Method", "*" ); 111 | res.setHeader( "Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE" ); 112 | res.setHeader( "Access-Control-Allow-Headers", "*" ); 113 | if( req.method === "OPTIONS" ) { 114 | res.writeHead( 204 ); 115 | res.end(); 116 | return; 117 | } 118 | } 119 | 120 | try { 121 | const rawUrl = req.url as string; 122 | const parsedUrl = new URL( rawUrl, `http://${req.headers.host}` ); 123 | const qs = querystring.decode( parsedUrl.searchParams.toString() ); 124 | 125 | // Check for default file paths 126 | if( rawUrl.startsWith( "/web" ) || rawUrl.startsWith( "/public" ) ) { 127 | const sanitizedPath = sanitizePath( parsedUrl.pathname ); 128 | let pathname = path.join( path.resolve( "." ), sanitizedPath ); 129 | await serveFile( pathname, res ); 130 | return; 131 | } 132 | 133 | const urlPath = comfyWeb.APIs[ parsedUrl.pathname ] ? parsedUrl.pathname : parsedUrl.pathname.substring( 1 ); 134 | // Find matching API route 135 | const apiRoute = comfyWeb.APIs[ urlPath ] ? { route: urlPath, params: [] } : getMatchingRoute( Object.keys( comfyWeb.APIs ), urlPath ); 136 | 137 | if( comfyWeb.APIs && apiRoute ) { 138 | // Handle API Request 139 | const body = req.method === "POST" ? await readPostData( req ) : null; 140 | const result = await comfyWeb.APIs[ apiRoute.route ]( qs, body, { req, res, params: apiRoute.params } ); 141 | if( !res.getHeader( "Content-Type" ) ) { 142 | res.setHeader( "Content-type", Array.isArray( result ) || typeof result === "object" ? "application/json" : "text/plain" ); 143 | } 144 | res.end( typeof result === "object" ? JSON.stringify( result ) : result ); 145 | } 146 | else { 147 | const fileRoute = comfyWeb.Files[ urlPath ] ? { route: urlPath, params: [] } : getMatchingRoute( Object.keys( comfyWeb.Files ), urlPath ); 148 | // Handle File/Default Request 149 | const sanitizedPath = sanitizePath( parsedUrl.pathname ); 150 | // Check for index.html and default file paths 151 | const possibleFilePaths = [ 152 | path.join( comfyWeb.Settings.Directory || ".", sanitizedPath ), 153 | path.join( ".", "web", sanitizedPath ), 154 | path.join( ".", "public", sanitizedPath ), 155 | path.join( comfyWeb.Settings.Directory || ".", sanitizedPath, "index.html" ), 156 | path.join( ".", "web", sanitizedPath, "index.html" ), 157 | path.join( ".", "public", sanitizedPath, "index.html" ), 158 | ]; 159 | if( sanitizedPath.endsWith( ".html" ) || sanitizedPath.endsWith( ".css" ) ) { 160 | // Add root path as an additional possible path 161 | possibleFilePaths.push( path.join( ".", sanitizedPath ) ); 162 | } 163 | for( const possiblePath of possibleFilePaths ) { 164 | try { 165 | await fs.access( possiblePath ); 166 | if( comfyWeb.Files && fileRoute ) { 167 | await comfyWeb.Files[ fileRoute.route ]( qs, null, { req, res } ); 168 | } 169 | await serveFile( possiblePath, res ); 170 | return; 171 | } 172 | catch( err: any ) { 173 | // Skip if file doesn't exist, otherwise throw an error 174 | if( err.code !== "ENOENT" ) { 175 | throw err; 176 | } 177 | } 178 | } 179 | // No file found 180 | res.statusCode = 404; 181 | res.end( `File not found` ); 182 | } 183 | } 184 | catch( err ) { 185 | console.error( "Web Request Error:", req.url, err ); 186 | res.statusCode = 500; 187 | res.end( `Error` ); 188 | } 189 | } 190 | 191 | async function startServer( port: number, { useCORS, Certificate, PrivateKey, CertificateChain, Directory }: { useCORS?: boolean, Certificate?: string, PrivateKey?: string, CertificateChain?: string, Directory?: string } = { useCORS: true } ) { 192 | let server; 193 | isCORSEnabled = !!useCORS; 194 | if( Certificate && PrivateKey ) { 195 | const privateKey = await fs.readFile( PrivateKey, "utf8" ); 196 | const certificate = await fs.readFile( Certificate, "utf8" ); 197 | const ca = CertificateChain ? await fs.readFile( CertificateChain, "utf8" ) : undefined; 198 | const credentials = { 199 | key: privateKey, 200 | cert: certificate, 201 | ca: ca, 202 | }; 203 | server = require( "https" ).createServer( credentials, webHandler ); 204 | } 205 | else { 206 | server = require( "http" ).createServer( webHandler ); 207 | } 208 | comfyWeb.Settings.Directory = Directory; 209 | 210 | server.listen( port, ( err: any ) => { 211 | if( err ) { 212 | return console.error( "WebWebWeb could not start:", err ); 213 | } 214 | console.log( `WebWebWeb is running on ${port}` ); 215 | } ); 216 | 217 | return server; 218 | } 219 | 220 | const comfyWeb: { 221 | APIs: { [ key: string ] : ( qs: any, body: Buffer | null, context: { req: http.IncomingMessage, res: http.ServerResponse, params: string[] } ) => Promise | any }, 222 | Files: { [ key: string ] : ( qs: any, body: Buffer | null, context: { req: http.IncomingMessage, res: http.ServerResponse } ) => Promise | any }, 223 | Settings: { Directory?: string }, 224 | Run: ( port: number, { useCORS, Certificate, PrivateKey, CertificateChain, Directory }: { useCORS?: boolean, Certificate?: string, PrivateKey?: string, CertificateChain?: string, Directory?: string } ) => Promise, 225 | default: any, 226 | } = { 227 | APIs: {}, 228 | Files: {}, 229 | Settings: {}, 230 | Run: startServer, 231 | default: undefined, 232 | }; 233 | 234 | comfyWeb.default = comfyWeb; // Make this a default export as well to support ES6 import syntax 235 | export = comfyWeb; 236 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "sourceMap": true, 5 | "declaration": true, 6 | "strict": true, 7 | "module": "commonjs", 8 | "target": "ES6", 9 | "lib": [ 10 | "ES6", 11 | "DOM" 12 | ], 13 | "allowJs": true, 14 | "moduleResolution": "node", 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true, 17 | "allowSyntheticDefaultImports": true, 18 | "experimentalDecorators": true 19 | }, 20 | "include": [ 21 | "./src/**/*" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /webstep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instafluff/WebWebWeb/8177fe037e8261cea3de7b8bde0e52274534b860/webstep.mp3 --------------------------------------------------------------------------------