├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── build ├── clock.d.ts ├── clock.js ├── clock.js.map ├── index.d.ts ├── index.js └── index.js.map ├── examples └── example.js ├── package-lock.json ├── package.json ├── src ├── clock.ts └── index.ts └── tsconfig.json /.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 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Certificates 107 | *.pem 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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 | # WebWebWebs 2 | The lightest alternative to ExpressJS with HTTPS. The Comfiest Way to make web APIs and static file servers with automated SSL Certificates! 3 | 4 | **WebWebWebs** lets you create a web server with APIs ***SUPER EASILY*** in just a few lines of code while also automatically getting free SSL certificates via [Let's Encrypt](https://www.letsencrypt.org) and renewing them. 5 | 6 | Just set your **domain** and **e-mail** address, and a SSL certificate will be retrieved and automatically renewed 30 days before expiration with **ZERO DOWNTIME** without a restart of your server or connections. 7 | 8 | *If you just need a web server without SSL certificates, check out the regular, zero-dependency [WebWebWeb](https://www.github.com/instafluff/WebWebWeb)!* 9 | 10 | ## Instafluff ## 11 | > *Like these projects? The best way to support my open-source projects is by becoming a Comfy Sponsor on GitHub!* 12 | 13 | > https://github.com/sponsors/instafluff 14 | 15 | > *Come and hang out with us at the Comfiest Corner on Twitch!* 16 | 17 | > https://twitch.tv/instafluff 18 | 19 | ## Requirements & Notes ## 20 | 21 | ### PORT 80 ### 22 | Port 80 must be open and available so that ACME challenges can be successfully completed. 23 | 24 | ### Domain Name ### 25 | The specified domain must point to the server running with **WebWebWebs**. Ensure you have created an A Record on your DNS to your server. 26 | 27 | ### No Response Error from Xfinity/Comcast ### 28 | If you are running your server from Xfinity as your ISP and the ACME challenges are failing, you may need to turn off Advanced Security network settings on your account. Read [here](https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security) for instructions on how to turn this setting off. 29 | 30 | ## Instructions ## 31 | 32 | 1. Install `webwebwebs` 33 | ``` 34 | npm install webwebwebs --save 35 | ``` 36 | 37 | 2. Start the server on a port (e.g. 443 for HTTPS). 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 38 | ```javascript 39 | var ComfyWeb = require( "webwebwebs" ); 40 | ComfyWeb.Run( 443, { 41 | domain: "webwebwebs.instafluff.tv", 42 | email: "waa@instafluff.tv" 43 | } ); 44 | ``` 45 | 46 | 3. (Optional) Add APIs 47 | ```javascript 48 | var ComfyWeb = require( "webwebwebs" ); 49 | ComfyWeb.APIs[ "/" ] = ( qs, body, opts ) => { 50 | return { "test": "example!" }; 51 | }; 52 | ComfyWeb.Run( 443, { 53 | domain: "webwebweb.instafluff.tv", 54 | email: "waa@instafluff.tv" 55 | } ); 56 | ``` 57 | 58 | ### Options ### 59 | 60 | The `Run()` function in **WebWebWebs** accepts several optional parameters: 61 | - useCORS (default: true) 62 | - test (default: false) 63 | 64 | ## Testing Certificates (Staging) ## 65 | To use the **Staging** environment to test certificates with [Let's Encrypt](https://www.letsencrypt.org), enable the `test` parameter. 66 | ```javascript 67 | var ComfyWeb = require( "webwebwebs" ); 68 | ComfyWeb.Run( 443, { 69 | test: true, 70 | domain: "webwebweb.instafluff.tv", 71 | email: "waa@instafluff.tv" 72 | } ); 73 | ``` 74 | 75 | ## Handling POST/PUT/DELETE requests ## 76 | 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. 77 | ```javascript 78 | var ComfyWeb = require( "webwebwebs" ); 79 | ComfyWeb.APIs[ "/account" ] = ( qs, body, opts ) => { 80 | switch( opts.req.method ) { 81 | case "GET": 82 | return { "account": "test" }; 83 | case "POST": 84 | return JSON.parse( body ); 85 | case "PUT": 86 | return { "status": "updated" }; 87 | case "DELETE": 88 | return {}; 89 | } 90 | }; 91 | ComfyWeb.Run( 443, { 92 | domain: "webwebweb.instafluff.tv", 93 | email: "waa@instafluff.tv" 94 | } ); 95 | ``` 96 | 97 | ## Reading Request Headers ## 98 | The request object is passed in to the API handler. You can check for header values in `opts.req.headers`. 99 | ```javascript 100 | var ComfyWeb = require( "webwebwebs" ); 101 | ComfyWeb.APIs[ "/" ] = ( qs, body, opts ) => { 102 | return opts.req.headers; 103 | }; 104 | ComfyWeb.Run( 443, { 105 | domain: "webwebweb.instafluff.tv", 106 | email: "waa@instafluff.tv" 107 | } ); 108 | ``` 109 | 110 | ## Enabling CORS ## 111 | Actually, CORS is enabled by default. To disable CORS, set the `useCORS` parameter: 112 | ```javascript 113 | var ComfyWeb = require( "webwebwebs" ); 114 | ComfyWeb.Run( 443, { 115 | useCORS: false, 116 | domain: "webwebweb.instafluff.tv", 117 | email: "waa@instafluff.tv" 118 | } ); 119 | ``` 120 | -------------------------------------------------------------------------------- /build/clock.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instafluff/WebWebWebs/b28898b5fc00e89d95541283111d41e7b182c2cb/build/clock.d.ts -------------------------------------------------------------------------------- /build/clock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //# sourceMappingURL=clock.js.map -------------------------------------------------------------------------------- /build/clock.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /build/index.d.ts: -------------------------------------------------------------------------------- 1 | import comfyWeb from "webwebweb"; 2 | export = comfyWeb; 3 | -------------------------------------------------------------------------------- /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 fs_1 = __importDefault(require("fs")); 15 | const acme_client_1 = __importDefault(require("acme-client")); 16 | const webwebweb_1 = __importDefault(require("webwebweb")); 17 | const debug = console.log; 18 | function isCertificateValidWithin30Days(file) { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | if (fs_1.default.existsSync(file)) { 21 | let cert = fs_1.default.readFileSync(file); 22 | let expiration = (new Date((yield acme_client_1.default.forge.readCertificateInfo(cert)).notAfter)).valueOf(); 23 | let time = (new Date()).valueOf(); 24 | if (expiration > time + 60000 * 60 * 24 * 30) { 25 | return true; 26 | } 27 | } 28 | return false; 29 | }); 30 | } 31 | function createACMEChallenge(authz, challenge, keyAuthorization) { 32 | return __awaiter(this, void 0, void 0, function* () { 33 | if (challenge.type === "http-01") { 34 | debug(`Creating ACME Challenge for ${authz.identifier.value}`); 35 | webwebweb_1.default.APIs[`.well-known/acme-challenge/${challenge.token}`] = (qs, body, opts) => { 36 | return keyAuthorization; 37 | }; 38 | } 39 | }); 40 | } 41 | function removeACMEChallenge(authz, challenge, keyAuthorization) { 42 | return __awaiter(this, void 0, void 0, function* () { 43 | if (challenge.type === "http-01") { 44 | debug(`Removing ACME Challenge for ${authz.identifier.value}`); 45 | delete webwebweb_1.default.APIs[`.well-known/acme-challenge/${challenge.token}`]; 46 | } 47 | }); 48 | } 49 | function certificateRefresh(domain, email, environment = "production") { 50 | return __awaiter(this, void 0, void 0, function* () { 51 | if (yield isCertificateValidWithin30Days(`${domain}_${environment}_cert.pem`)) { 52 | return false; 53 | } 54 | debug("Requesting New SSL Certificate..."); 55 | const client = new acme_client_1.default.Client({ 56 | directoryUrl: environment === "production" ? acme_client_1.default.directory.letsencrypt.production : acme_client_1.default.directory.letsencrypt.staging, 57 | accountKey: yield acme_client_1.default.forge.createPrivateKey(), 58 | }); 59 | const [key, csr] = yield acme_client_1.default.forge.createCsr({ 60 | commonName: domain, 61 | }); 62 | const cert = yield client.auto({ 63 | csr: csr, 64 | email: email, 65 | termsOfServiceAgreed: true, 66 | challengeCreateFn: createACMEChallenge, 67 | challengeRemoveFn: removeACMEChallenge, 68 | }); 69 | fs_1.default.writeFileSync(`${domain}_${environment}_chain.pem`, csr.toString(), "utf8"); 70 | fs_1.default.writeFileSync(`${domain}_${environment}_privkey.pem`, key.toString(), "utf8"); 71 | fs_1.default.writeFileSync(`${domain}_${environment}_cert.pem`, cert.toString(), "utf8"); 72 | debug("Saved SSL Certificate."); 73 | return true; 74 | }); 75 | } 76 | let comfyServer; 77 | const proxyStart = webwebweb_1.default.Run; 78 | webwebweb_1.default.Run = (port, opts = { useCORS: true }) => __awaiter(void 0, void 0, void 0, function* () { 79 | if (opts.domain) { 80 | proxyStart(80, {}); 81 | opts.email = opts.email || `support@${opts.domain}`; 82 | const env = opts.test ? "staging" : "production"; 83 | opts.CertificateChain = `${opts.domain}_${env}_chain.pem`; 84 | opts.PrivateKey = `${opts.domain}_${env}_privkey.pem`; 85 | opts.Certificate = `${opts.domain}_${env}_cert.pem`; 86 | setInterval(() => __awaiter(void 0, void 0, void 0, function* () { 87 | // Check every hour 88 | let refreshed = yield certificateRefresh(opts.domain, opts.email, env); 89 | if (refreshed) { 90 | if (comfyServer) { 91 | debug("Updating certs"); 92 | comfyServer.setSecureContext({ 93 | ca: fs_1.default.readFileSync(`${opts.domain}_${env}_chain.pem`, "utf8"), 94 | key: fs_1.default.readFileSync(`${opts.domain}_${env}_privkey.pem`, "utf8"), 95 | cert: fs_1.default.readFileSync(`${opts.domain}_${env}_cert.pem`, "utf8"), 96 | }); 97 | } 98 | else { 99 | debug("Starting server"); 100 | comfyServer = yield proxyStart(port, opts); 101 | console.log("Server yayayay", comfyServer.setSecureContext); 102 | } 103 | } 104 | }), 1000 * 60 * 60); // Every Hour 105 | yield certificateRefresh(opts.domain, opts.email, env); 106 | if (fs_1.default.existsSync(opts.Certificate)) { 107 | comfyServer = yield proxyStart(port, opts); 108 | return comfyServer; 109 | } 110 | else { 111 | console.warn("WARNING: No Certificate"); 112 | return proxyStart(port, opts); 113 | } 114 | } 115 | else { 116 | console.warn("WARNING: Domain not defined in options"); 117 | return proxyStart(port, opts); 118 | } 119 | }); 120 | webwebweb_1.default.default = webwebweb_1.default; // Make this a default export as well to support ES6 import syntax 121 | module.exports = webwebweb_1.default; 122 | //# 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/example.js: -------------------------------------------------------------------------------- 1 | var ComfyWeb = require( "../build/index" ); 2 | 3 | ComfyWeb.APIs[ "/" ] = ( qs, body ) => { 4 | console.log( qs ); 5 | console.log( body ); 6 | return { "test": "example!" }; 7 | }; 8 | 9 | ComfyWeb.APIs[ "color" ] = ( qs ) => { 10 | console.log( qs ); 11 | return { "color": "RED" }; 12 | }; 13 | 14 | ComfyWeb.APIs[ "string" ] = ( qs ) => { 15 | console.log( qs ); 16 | return "test string"; 17 | }; 18 | 19 | ComfyWeb.APIs[ "array" ] = ( qs ) => { 20 | console.log( qs ); 21 | return [ "one", "2", "three" ]; 22 | }; 23 | 24 | ComfyWeb.Run( 443, { 25 | domain: "noplacelikehome.instafluff.tv", 26 | email: "waa@instafluff.tv", 27 | } ); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webwebwebs", 3 | "version": "1.1.1", 4 | "description": "Automated TLS/SSL Certificates with WebWebWeb, 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/WebWebWebs.git" 16 | }, 17 | "keywords": [ 18 | "Instafluff", 19 | "Comfy", 20 | "TLS", 21 | "SSL", 22 | "http", 23 | "web", 24 | "api", 25 | "static", 26 | "file", 27 | "server", 28 | "webwebweb", 29 | "letsencrypt", 30 | "https" 31 | ], 32 | "author": "Instafluff", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/instafluff/WebWebWebs/issues" 36 | }, 37 | "homepage": "https://github.com/instafluff/WebWebWebs#readme", 38 | "dependencies": { 39 | "acme-client": "^4.1.2", 40 | "webwebweb": "^1.6.1" 41 | }, 42 | "devDependencies": { 43 | "@types/jest": "^29.2.5", 44 | "@types/node": "^20.2.5", 45 | "@typescript-eslint/eslint-plugin": "^5.48.0", 46 | "@typescript-eslint/parser": "^5.48.0", 47 | "eslint": "^8.31.0", 48 | "eslint-config-comfycase": "^0.1.2", 49 | "jest": "^29.3.1", 50 | "rimraf": "^3.0.2", 51 | "typescript": "^4.9.4" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/clock.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instafluff/WebWebWebs/b28898b5fc00e89d95541283111d41e7b182c2cb/src/clock.ts -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import acme from "acme-client"; 3 | import comfyWeb from "webwebweb"; 4 | 5 | const debug = console.log; 6 | 7 | async function isCertificateValidWithin30Days( file: string ): Promise { 8 | if( fs.existsSync( file ) ) { 9 | let cert = fs.readFileSync( file ); 10 | let expiration = ( new Date( ( await acme.forge.readCertificateInfo( cert ) ).notAfter ) ).valueOf(); 11 | let time = ( new Date() ).valueOf(); 12 | if( expiration > time + 60000 * 60 * 24 * 30 ) { 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | 19 | async function createACMEChallenge( authz: any, challenge: any, keyAuthorization: any ) { 20 | if( challenge.type === "http-01" ) { 21 | debug( `Creating ACME Challenge for ${authz.identifier.value}` ); 22 | comfyWeb.APIs[ `.well-known/acme-challenge/${challenge.token}` ] = ( qs: any, body: any, opts: any ) => { 23 | return keyAuthorization; 24 | }; 25 | } 26 | } 27 | 28 | async function removeACMEChallenge( authz: any, challenge: any, keyAuthorization: any ) { 29 | if( challenge.type === "http-01" ) { 30 | debug( `Removing ACME Challenge for ${authz.identifier.value}` ); 31 | delete comfyWeb.APIs[ `.well-known/acme-challenge/${challenge.token}` ]; 32 | } 33 | } 34 | 35 | async function certificateRefresh( domain: string, email: string, environment: string = "production" ): Promise { 36 | if( await isCertificateValidWithin30Days( `${domain}_${environment}_cert.pem` ) ) { 37 | return false; 38 | } 39 | 40 | debug( "Requesting New SSL Certificate..." ); 41 | const client = new acme.Client( { 42 | directoryUrl: environment === "production" ? acme.directory.letsencrypt.production : acme.directory.letsencrypt.staging, 43 | accountKey: await acme.forge.createPrivateKey(), 44 | } ); 45 | 46 | const [ key, csr ] = await acme.forge.createCsr( { 47 | commonName: domain, 48 | } ); 49 | 50 | const cert = await client.auto( { 51 | csr: csr, 52 | email: email, 53 | termsOfServiceAgreed: true, 54 | challengeCreateFn: createACMEChallenge, 55 | challengeRemoveFn: removeACMEChallenge, 56 | } ); 57 | 58 | fs.writeFileSync( `${domain}_${environment}_chain.pem`, csr.toString(), "utf8" ); 59 | fs.writeFileSync( `${domain}_${environment}_privkey.pem`, key.toString(), "utf8" ); 60 | fs.writeFileSync( `${domain}_${environment}_cert.pem`, cert.toString(), "utf8" ); 61 | debug( "Saved SSL Certificate." ); 62 | return true; 63 | } 64 | 65 | let comfyServer: any; 66 | const proxyStart = comfyWeb.Run; 67 | comfyWeb.Run = async ( port: number, opts: { 68 | domain?: string, 69 | email?: string, 70 | test?: boolean, 71 | useCORS?: boolean, 72 | Certificate?: string, 73 | PrivateKey?: string, 74 | CertificateChain?: string, 75 | Directory?: string 76 | } = { useCORS: true } ) => { 77 | if( opts.domain ) { 78 | proxyStart( 80, {} ); 79 | opts.email = opts.email || `support@${opts.domain}`; 80 | const env = opts.test ? "staging" : "production"; 81 | opts.CertificateChain = `${opts.domain}_${env}_chain.pem`; 82 | opts.PrivateKey = `${opts.domain}_${env}_privkey.pem`; 83 | opts.Certificate = `${opts.domain}_${env}_cert.pem`; 84 | setInterval( async () => { 85 | // Check every hour 86 | let refreshed = await certificateRefresh( opts.domain as string, opts.email as string, env ); 87 | if( refreshed ) { 88 | if( comfyServer ) { 89 | debug( "Updating certs" ); 90 | comfyServer.setSecureContext( { 91 | ca: fs.readFileSync( `${opts.domain}_${env}_chain.pem`, "utf8" ), 92 | key: fs.readFileSync( `${opts.domain}_${env}_privkey.pem`, "utf8" ), 93 | cert: fs.readFileSync( `${opts.domain}_${env}_cert.pem`, "utf8" ), 94 | } ); 95 | } 96 | else { 97 | debug( "Starting server" ); 98 | comfyServer = await proxyStart( port, opts ); 99 | console.log( "Server yayayay", comfyServer.setSecureContext ); 100 | } 101 | } 102 | }, 1000 * 60 * 60 ); // Every Hour 103 | await certificateRefresh( opts.domain, opts.email, env ); 104 | if( fs.existsSync( opts.Certificate ) ) { 105 | comfyServer = await proxyStart( port, opts ); 106 | return comfyServer; 107 | } 108 | else { 109 | console.warn( "WARNING: No Certificate" ); 110 | return proxyStart( port, opts ); 111 | } 112 | } 113 | else { 114 | console.warn( "WARNING: Domain not defined in options" ); 115 | return proxyStart( port, opts ); 116 | } 117 | }; 118 | 119 | comfyWeb.default = comfyWeb; // Make this a default export as well to support ES6 import syntax 120 | export = comfyWeb; 121 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------