local-traffic 0.0.36 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/localTraffic.js +1 -1
- package/index.ts +90 -49
- package/package.json +1 -1
package/dist/localTraffic.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e=require("http2"),t=require("http"),r=require("https"),o=require("url"),n=require("fs"),s=require("zlib"),a=require("path");var i;!function(e){e[e.ERROR=124]="ERROR",e[e.SUCCESS=35]="SUCCESS",e[e.INFO=21]="INFO",e[e.WARNING=172]="WARNING"}(i||(i={}));const l=(0,a.resolve)(process.env.HOME,".local-traffic.json"),p=(0,a.resolve)(process.cwd(),process.argv.slice(-1)[0].endsWith(".json")?process.argv.slice(-1)[0]:l),c={mapping:{},port:8080,replaceResponseBodyUrls:!1,dontUseHttp2Downstream:!1,simpleLogs:!1,websocket:!1};let d,h;const u=(e,t,r)=>{console.log(`${(e=>{const t=new Date;return`${e?"":"[36m"}${`${t.getHours()}`.padStart(2,"0")}${e?":":"[33m:[36m"}${`${t.getMinutes()}`.padStart(2,"0")}${e?":":"[33m:[36m"}${`${t.getSeconds()}`.padStart(2,"0")}${e?"":"[0m"}`})(d.simpleLogs)} ${d.simpleLogs?e.replace(/⎸/g,"|").replace(/⎹/g,"|").replace(/\u001b\[[^m]*m/g,"").replace(/↘️/g,"inbound").replace(/☎️/g,"port").replace(/↗️/g,"outbound"):t?`[48;5;${t}m⎸ ${process.stdout.isTTY&&r||""} ${e.padEnd(36)} ⎹[0m`:e}`)},m=async(e=!0)=>new Promise((t=>(0,n.readFile)(p,((r,o)=>{r&&!e&&u("config error. Using default value",i.ERROR,"❌");try{d=Object.assign({},c,JSON.parse((o||"{}").toString()))}catch(e){return u("config syntax incorrect, aborting",i.ERROR,"⛈️"),d=d||{...c},void t(d)}d.mapping[""]||u('default mapping "" not provided.',i.WARNING,"☢️"),r&&"ENOENT"===r.code&&e&&p===l?(0,n.writeFile)(p,JSON.stringify(c),(e=>{e?u("config file NOT created",i.ERROR,"⁉️"):u("config file created",i.SUCCESS,"✨"),t(d)})):t(d)})))).then((()=>{e&&(0,n.watchFile)(p,f)})),g=e=>{u(`[48;5;5m⎸ ↘️ : ${e.ssl?"HTTP/2 ":"HTTP 1.1"} [48;5;21m⎸ ☎️ : ${e.port.toString().padStart(5)} [48;5;31m⎸ ↗️ : ${e.dontUseHttp2Downstream?"HTTP 1.1":"HTTP/2 "} ⎹[0m`)},f=async()=>{const e={...d};return await m(!1),isNaN(d.port)||d.port>65535||d.port<0?(d=e,void u("port number invalid. Not refreshing",i.ERROR,"☎️")):"object"!=typeof d.mapping?(d=e,void u("mapping should be an object. Aborting",i.ERROR,"⚡")):(d.replaceResponseBodyUrls&&!e.replaceResponseBodyUrls&&u("response body url replacement",i.INFO,"✔️"),!d.replaceResponseBodyUrls&&e.replaceResponseBodyUrls&&u("response body url NO replacement",i.INFO,"✖️"),d.websocket&&!e.websocket&&u("websocket activated",i.INFO,"☄️"),!d.websocket&&e.websocket&&u("websocket deactivated",i.INFO,"☄️"),u(`${Object.keys(d.mapping).length.toString().padStart(5)} loaded mapping rules`,i.SUCCESS,"↻"),void(d.port!==e.port||JSON.stringify(d.ssl)!==JSON.stringify(e.ssl)?(await new Promise((e=>h?h.close(e):e(void 0))),S()):d.dontUseHttp2Downstream!==e.dontUseHttp2Downstream&&g(d)))},$=e=>""==e?"":(0,a.normalize)(e).replace(/\\/g,"/"),w=e=>{const t=(0,a.resolve)("/",e.hostname,...e.pathname.replace(/[?#].*$/,"").replace(/^\/+/,"").split("/"));return{error:null,data:null,hasRun:!1,run:function(){return this.hasRun?Promise.resolve():new Promise((r=>(0,n.readFile)(t,((o,s)=>{if(this.hasRun=!0,!o||"EISDIR"!==o.code)return this.error=o,this.data=s,void r(void 0);(0,n.readdir)(t,((t,o)=>{this.error=t,this.data=o,t?r(void 0):Promise.all(o.map((t=>new Promise((r=>(0,n.lstat)((0,a.resolve)(e.pathname,t),((e,o)=>r([t,o,e])))))))).then((t=>{const o=t.filter((e=>!e[2]&&e[1].isDirectory())).concat(t.filter((e=>!e[2]&&e[1].isFile())));this.data=`${b(128194,"directory",e.href)}\n <p>Directory content of <i>${e.href.replace(/\//g,"/")}</i></p>\n <ul class="list-group">\n <li class="list-group-item">📁<a href="${e.pathname.endsWith("/")?"..":"."}"><parent></a></li>\n ${o.filter((e=>!e[2])).map((t=>`<li class="list-group-item">&#x${(t[1].isDirectory()?128193:128196).toString(16)};<a href="${e.pathname.endsWith("/")?"":`${e.pathname.split("/").slice(-1)[0]}/`}${t[0]}">${t[0]}</a></li>`)).join("\n")}\n </li>\n </ul>\n </body></html>`,r(void 0)}))}))}))))},events:{},on:function(e,r){return this.events[e]=r,this.run().then((()=>{"response"===e&&this.events.response({Server:"local","Content-Type":t.endsWith(".svg")?"image/svg+xml":null},0),"data"===e&&this.data&&(this.events.data(this.data),this.events.end()),"error"===e&&this.error&&this.events.error(this.error)})),this},end:function(){return this},request:function(){return this}}},b=(e,t,r)=>`<!doctype html>\n<html lang="en">\n<head>\n<title>&#x${e.toString(16)}; local-traffic ${t} | ${r}</title>\n<link href="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css" rel="stylesheet"/>\n<script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"><\/script>\n<script src="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/js/bootstrap.bundle.min.js"><\/script>\n</head>\n<body><div class="container"><h1>&#x${e.toString(16)}; local-traffic ${t}</h1>\n<br/>`,y=(e,t,r,o)=>`${b(128163,"error",e.message)}\n<p>An error happened while trying to proxy a remote exchange</p>\n<div class="alert alert-warning" role="alert">\n ⓘ This is not an error from the downstream service.\n</div>\n<div class="alert alert-danger" role="alert">\n<pre><code>${e.stack||`<i>${e.name} : ${e.message}</i>`}${e.errno?`<br/>(code : ${e.errno})`:""}</code></pre>\n</div>\nMore information about the request :\n<table class="table">\n <tbody>\n <tr>\n <td>phase</td>\n <td>${t}</td>\n </tr>\n <tr>\n <td>requested URL</td>\n <td>${r}</td>\n </tr>\n <tr>\n <td>downstream URL</td>\n <td>${o||"<no-target-url>"}</td>\n </tr>\n </tbody>\n</table>\n</div></body></html>`,R=(e,t,r)=>{t.writeHead(e,void 0,{"content-type":"text/html","content-length":r.length}),t.end(r)},v=e=>{const t=(e.headers[":authority"]?.toString()??e.headers.host??"localhost").replace(/:.*/,""),r=e.headers[":authority"]||`${e.headers.host}${e.headers.host.match(/:[0-9]+$/)?"":80!==d.port||d.ssl?443===d.port&&d.ssl?"":`:${d.port}`:""}`,n=new o.URL(`http${d.ssl?"s":""}://${r}${e.url}`),s=n.href.substring(n.origin.length),[a,i]=Object.entries({...Object.assign({},...Object.entries(d.mapping).map((([e,t])=>({[e]:new o.URL($(t))}))))}).find((([e])=>s.match(RegExp(e))))||[];return{proxyHostname:t,proxyHostnameAndPort:r,url:n,path:s,key:a,target:i}},S=()=>{h=(d.ssl?e.createSecureServer.bind(null,{...d.ssl,allowHTTP1:!0}):t.createServer)((async(n,a)=>{if(!n.headers.host&&!n.headers[":authority"])return void R(400,a,Buffer.from(y(new Error("client must supply a 'host' header"),"proxy",new o.URL(`http${d.ssl?"s":""}://unknowndomain${n.url}`))));const{proxyHostname:i,proxyHostnameAndPort:l,url:c,path:h,key:u,target:m}=v(n);if(!m)return void R(502,a,Buffer.from(y(new Error(`No mapping found in config file ${p}`),"proxy",c)));const g=m.host.replace(RegExp(/\/+$/),""),f=`${m.href.substring("https://".length+m.host.length)}${$(h.replace(RegExp($(u)),""))}`.replace(/^\/*/,"/"),b=new o.URL(`${m.protocol}//${g}${f}`);let S=null,O=!d.dontUseHttp2Downstream;const j="file:"===m.protocol?w(b):O?await Promise.race([new Promise((t=>{const r=(0,e.connect)(b,{rejectUnauthorized:!1,protocol:m.protocol},((e,o)=>{O=O&&!!o.alpnProtocol,t(O?r:null)}));r.on("error",(e=>{S=O&&Buffer.from(y(e,"connection",c,b))}))})),new Promise((e=>setTimeout((()=>{O=!1,e(null)}),3e3)))]):null;S instanceof Buffer||(S=null);const E={...[...Object.entries(n.headers)].filter((([e])=>!["host","connection"].includes(e.toLowerCase()))).reduce(((e,[t,r])=>(e[t]=(e[t]||"")+(Array.isArray(r)?r:[r]).map((e=>e.replace(c.hostname,g))).join(", "),e)),{}),origin:m.href,referer:b.toString(),":authority":g,":method":n.method,":path":f,":scheme":m.protocol.replace(":","")},N=j&&!S&&j.request(E,{endStream:d.ssl?!(n?.stream?.readableLength??1):!n.readableLength});N&&N.on("error",(e=>{const t=-505===e.errno;S=Buffer.from(y(e,"stream"+(t?" (error -505 usually means that the downstream service does not support this http version)":""),c,b))}));const U={hostname:m.hostname,path:f,port:m.port,protocol:m.protocol,rejectUnauthorized:!1,method:n.method,headers:{...Object.assign({},...Object.entries(E).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase())).map((([e,t])=>({[e]:t})))),host:m.hostname}},x=!S&&!O&&"file:"!==m.protocol&&await new Promise((e=>{const o="https:"===m.protocol?(0,r.request)(U,e):(0,t.request)(U,e);o.on("error",(t=>{S=Buffer.from(y(t,"request",c,b)),e(null)})),n.on("data",(e=>o.write(e))),n.on("end",(()=>o.end()))}));if(S)return void R(502,a,S);S=null,d.ssl&&n.stream&&n.stream.readableLength&&N&&(n.stream.on("data",(e=>N.write(e))),n.stream.on("end",(()=>N.end()))),!d.ssl&&n.readableLength&&N&&(n.on("data",(e=>N.write(e))),n.on("end",(()=>N.end())));const{outboundResponseHeaders:H}=await new Promise((e=>N?N.on("response",(t=>{e({outboundResponseHeaders:t})})):e(!N&&x?{outboundResponseHeaders:x.headers}:{outboundResponseHeaders:{}}))),P=H.location?new o.URL(H.location.startsWith("/")?`${m.href}${H.location.replace(/^\/+/,"")}`:H.location):null,L=P?P.href.substring(P.origin.length):null,C=c.origin,T=P?`${C}${L}`:null,A=N||x,B=S??await new Promise((e=>{let t=Buffer.alloc(0);A?(A.on("data",(e=>t=Buffer.concat([t,"string"==typeof e?Buffer.from(e):e]))),A.on("end",(()=>{e(t)}))):e(t)})).then((e=>d.replaceResponseBodyUrls&&e.length?(H["content-encoding"]||"").split(",").reduce((async(e,t)=>{const r=t.trim().toLowerCase(),o="gzip"===r||"x-gzip"===r?s.gunzip:"deflate"===r?s.inflate:"br"===r?s.brotliDecompress:"identity"===r||""===r?(e,t)=>{t(null,e)}:null;if(null===o)return void R(502,a,Buffer.from(y(new Error(`${r} compression not supported by the proxy`),"stream",c,b)));const n=await e;return await new Promise((e=>o(n,((t,r)=>{if(t)return R(502,a,Buffer.from(y(t,"stream",c,b))),void e("");e(r)}))))}),Promise.resolve(e)).then((e=>{const t=e.length>1e7,r=["text/html","application/javascript","application/json"].some((e=>(H["content-type"]??"").includes(e)));return!t&&(r||!/[^\x00-\x7F]/.test(e.toString()))?d.replaceResponseBodyUrls?Object.entries(d.mapping).reduce(((e,[t,r])=>""===t||t.match(/^[-a-zA-Z0-9()@:%_\+.~#?&//=]*$/)?e.replace(new RegExp(r.replace(/^file:\/\//,"").replace(/[*+?^${}()|[\]\\]/g,"").replace(/^https/,"https?")+"/*","ig"),`https://${l}${t.replace(/\/+$/,"")}/`):e),e.toString()).split(`${l}/:`).join(`${l}:`).replace(/\?protocol=wss?%3A&hostname=[^&]+&port=[0-9]+&pathname=/g,`?protocol=ws${d.ssl?"s":""}%3A&hostname=${i}&port=${d.port}&pathname=${encodeURIComponent(u.replace(/\/+$/,""))}`):e.toString():e})).then((e=>(H["content-encoding"]||"").split(",").reduce(((e,t)=>{const r=t.trim().toLowerCase(),o="gzip"===r||"x-gzip"===r?s.gzip:"deflate"===r?s.deflate:"br"===r?s.brotliCompress:"identity"===r||""===r?(e,t)=>{t(null,e)}:null;if(null===o)throw new Error(`${r} compression not supported by the proxy`);return e.then((e=>new Promise((t=>o(e,((e,r)=>{if(e)throw e;t(r)}))))))}),Promise.resolve(Buffer.from(e))))):e)),k={...Object.entries({...H,...d.replaceResponseBodyUrls?{"content-length":`${B.byteLength}`}:{}}).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase()&&"connection"!==e.toLowerCase())).reduce(((e,[t,r])=>{const o=g.split("").map(((e,t)=>g.substring(t).startsWith(".")&&g.substring(t))).filter((e=>e)),n=[g].concat(o).reduce(((e,t)=>(Array.isArray(e)?e:[e]).map((e=>"string"==typeof e?e.replace(`Domain=${t}`,`Domain=${c.hostname}`):e))),r);return e[t]=(e[t]||[]).concat(n),e}),{}),...T?{location:[T]}:{}};try{Object.entries(k).forEach((([e,t])=>t&&a.setHeader(e,t)))}catch(e){}a.writeHead(H[":status"]||x.statusCode||200,d.ssl?void 0:x.statusMessage||"Status read from http/2",k),B?a.end(B):a.end()})).addListener("error",(e=>{"EACCES"===e.code&&u("permission denied for this port",i.ERROR,"⛔"),"EADDRINUSE"===e.code&&u("port is already used. NOT started",i.ERROR,"☠️")})).addListener("listening",(()=>{g(d)})).on("upgrade",((e,n)=>{if(!d.websocket)return void n.end("HTTP/1.1 503 Service Unavailable\r\n\r\n");const{key:s,target:a}=v(e),l=new o.URL(`${a.protocol}//${a.host}${e.url.endsWith("/_next/webpack-hmr")?e.url:e.url.replace(new RegExp(`^${s}`,"g"),"").replace(/^\/*/,"/")}`),p={hostname:l.hostname,path:l.pathname,port:l.port,protocol:l.protocol,rejectUnauthorized:!1,method:e.method,headers:e.headers,host:l.hostname},c="https:"===l.protocol?(0,r.request)(p):(0,t.request)(p);c.end(),c.on("error",(e=>{u("websocket request has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,"☄️")})),c.on("upgrade",((e,t)=>{const r=`HTTP/${e.httpVersion} ${e.statusCode} ${e.statusMessage}\r\n${Object.entries(e.headers).flatMap((([e,t])=>(Array.isArray(t)?t:[t]).map((t=>[e,t])))).map((([e,t])=>`${e}: ${t}\r\n`)).join("")}\r\n`;n.write(r),n.allowHalfOpen=!0,t.allowHalfOpen=!0,t.on("data",(e=>n.write(e))),n.on("data",(e=>t.write(e))),t.on("error",(e=>{u("downstream socket has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,"☄️")})),n.on("error",(e=>{u("upstream socket has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,"☄️")}))}))})).listen(d.port)};m().then(S);
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e=require("http2"),t=require("http"),r=require("https"),o=require("url"),n=require("fs"),s=require("zlib"),a=require("path");var i,l;!function(e){e[e.ERROR=124]="ERROR",e[e.INFO=93]="INFO",e[e.WARNING=172]="WARNING"}(i||(i={})),function(e){e.INBOUND="↘️ ",e.PORT="☎️ ",e.OUTBOUND="↗️ ",e.RULES="🔗",e.BODY_REPLACEMENT="✒️ ",e.WEBSOCKET="☄️ ",e.COLORED="✨",e.NO="⛔",e.ERROR_1="❌",e.ERROR_2="⛈️ ",e.ERROR_3="☢️ ",e.ERROR_4="⁉️ ",e.ERROR_5="⚡",e.ERROR_6="☠️ "}(l||(l={}));const p=(0,a.resolve)(process.env.HOME,".local-traffic.json"),c=(0,a.resolve)(process.cwd(),process.argv.slice(-1)[0].endsWith(".json")?process.argv.slice(-1)[0]:p),d={mapping:{},port:8080,replaceResponseBodyUrls:!1,dontUseHttp2Downstream:!1,simpleLogs:!1,websocket:!1};let h,m;const u=(e,t,r)=>{console.log(`${(e=>{const t=new Date;return`${e?"":"[36m"}${`${t.getHours()}`.padStart(2,"0")}${e?":":"[33m:[36m"}${`${t.getMinutes()}`.padStart(2,"0")}${e?":":"[33m:[36m"}${`${t.getSeconds()}`.padStart(2,"0")}${e?"":"[0m"}`})(h.simpleLogs)} ${h.simpleLogs?e.replace(/⎸/g,"|").replace(/⎹/g,"|").replace(/\u001b\[[^m]*m/g,"").replace(new RegExp(l.INBOUND,"g"),"inbound:").replace(new RegExp(l.PORT,"g"),"port:").replace(new RegExp(l.OUTBOUND,"g"),"outbound:").replace(new RegExp(l.RULES,"g"),"rules:").replace(new RegExp(l.NO,"g"),"").replace(new RegExp(l.BODY_REPLACEMENT,"g"),"body replacement").replace(new RegExp(l.WEBSOCKET,"g"),"websocket").replace(/\|+/g,"|"):t?`[48;5;${t}m⎸ ${process.stdout.isTTY&&r||""} ${e.padEnd(36)} ⎹[0m`:e}`)},g=async(e=!0)=>new Promise((t=>(0,n.readFile)(c,((r,o)=>{r&&!e&&u("config error. Using default value",i.ERROR,l.ERROR_1);try{h=Object.assign({},d,JSON.parse((o||"{}").toString()))}catch(e){return u("config syntax incorrect, aborting",i.ERROR,l.ERROR_2),h=h||{...d},void t(h)}h.mapping[""]||u('default mapping "" not provided.',i.WARNING,l.ERROR_3),r&&"ENOENT"===r.code&&e&&c===p?(0,n.writeFile)(c,JSON.stringify(d),(e=>{e?u("config file NOT created",i.ERROR,l.ERROR_4):u("config file created",i.INFO,l.COLORED),t(h)})):t(h)})))).then((()=>{e&&(0,n.watchFile)(c,f)})),R=e=>{u(`[48;5;52m⎸${l.PORT} ${e.port.toString().padStart(5)} [48;5;53m⎸${l.INBOUND} ${e.ssl?"H/2 ":"H1.1"} [48;5;54m⎸${l.OUTBOUND} ${e.dontUseHttp2Downstream?"H1.1":"H/2 "}⎹[48;5;55m⎸${l.RULES}${Object.keys(h.mapping).length.toString().padStart(3)}⎹[48;5;56m⎸${h.replaceResponseBodyUrls?l.BODY_REPLACEMENT:l.NO}⎹[48;5;57m⎸${h.websocket?l.WEBSOCKET:l.NO}⎹[48;5;93m⎸${h.simpleLogs?l.NO:l.COLORED}⎹[0m`)},f=async()=>{const e={...h};return await g(!1),isNaN(h.port)||h.port>65535||h.port<0?(h=e,void u("port number invalid. Not refreshing",i.ERROR,l.PORT)):"object"!=typeof h.mapping?(h=e,void u("mapping should be an object. Aborting",i.ERROR,l.ERROR_5)):(h.replaceResponseBodyUrls!==e.replaceResponseBodyUrls&&u(`response body url ${h.replaceResponseBodyUrls?"":"NO "}replacement`,i.INFO,l.BODY_REPLACEMENT),h.dontUseHttp2Downstream!==e.dontUseHttp2Downstream&&u(`http/2 ${h.dontUseHttp2Downstream?"de":""}activated downstream`,i.INFO,l.OUTBOUND),h.websocket!==e.websocket&&u(`websocket ${h.websocket?"":"de"}activated`,i.INFO,l.WEBSOCKET),h.simpleLogs!==e.simpleLogs&&u("simple logs "+(h.simpleLogs?"on":"off"),i.INFO,l.COLORED),Object.keys(h.mapping).join("\n")!==Object.keys(e.mapping).join("\n")&&u(`${Object.keys(h.mapping).length.toString().padStart(5)} loaded mapping rules`,i.INFO,l.RULES),h.port!==e.port&&u(`port changed from ${e.port} to ${h.port}`,i.INFO,l.PORT),h.ssl&&!e.ssl&&u("ssl configuration added",i.INFO,l.INBOUND),!h.ssl&&e.ssl&&u("ssl configuration removed",i.INFO,l.INBOUND),void(h.port!==e.port||JSON.stringify(h.ssl)!==JSON.stringify(e.ssl)?(await new Promise((e=>m?m.close(e):e(void 0))),v()):R(h)))},$=e=>""==e?"":(0,a.normalize)(e).replace(/\\/g,"/"),O=e=>{const t=(0,a.resolve)("/",e.hostname,...e.pathname.replace(/[?#].*$/,"").replace(/^\/+/,"").split("/"));return{error:null,data:null,hasRun:!1,run:function(){return this.hasRun?Promise.resolve():new Promise((r=>(0,n.readFile)(t,((o,s)=>{if(this.hasRun=!0,!o||"EISDIR"!==o.code)return this.error=o,this.data=s,void r(void 0);(0,n.readdir)(t,((t,o)=>{this.error=t,this.data=o,t?r(void 0):Promise.all(o.map((t=>new Promise((r=>(0,n.lstat)((0,a.resolve)(e.pathname,t),((e,o)=>r([t,o,e])))))))).then((t=>{const o=t.filter((e=>!e[2]&&e[1].isDirectory())).concat(t.filter((e=>!e[2]&&e[1].isFile())));this.data=`${w(128194,"directory",e.href)}\n <p>Directory content of <i>${e.href.replace(/\//g,"/")}</i></p>\n <ul class="list-group">\n <li class="list-group-item">📁<a href="${e.pathname.endsWith("/")?"..":"."}"><parent></a></li>\n ${o.filter((e=>!e[2])).map((t=>`<li class="list-group-item">&#x${(t[1].isDirectory()?128193:128196).toString(16)};<a href="${e.pathname.endsWith("/")?"":`${e.pathname.split("/").slice(-1)[0]}/`}${t[0]}">${t[0]}</a></li>`)).join("\n")}\n </li>\n </ul>\n </body></html>`,r(void 0)}))}))}))))},events:{},on:function(e,r){return this.events[e]=r,this.run().then((()=>{"response"===e&&this.events.response({Server:"local","Content-Type":t.endsWith(".svg")?"image/svg+xml":null},0),"data"===e&&this.data&&(this.events.data(this.data),this.events.end()),"error"===e&&this.error&&this.events.error(this.error)})),this},end:function(){return this},request:function(){return this}}},w=(e,t,r)=>`<!doctype html>\n<html lang="en">\n<head>\n<title>&#x${e.toString(16)}; local-traffic ${t} | ${r}</title>\n<link href="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css" rel="stylesheet"/>\n<script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"><\/script>\n<script src="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/js/bootstrap.bundle.min.js"><\/script>\n</head>\n<body><div class="container"><h1>&#x${e.toString(16)}; local-traffic ${t}</h1>\n<br/>`,E=(e,t,r,o)=>`${w(128163,"error",e.message)}\n<p>An error happened while trying to proxy a remote exchange</p>\n<div class="alert alert-warning" role="alert">\n ⓘ This is not an error from the downstream service.\n</div>\n<div class="alert alert-danger" role="alert">\n<pre><code>${e.stack||`<i>${e.name} : ${e.message}</i>`}${e.errno?`<br/>(code : ${e.errno})`:""}</code></pre>\n</div>\nMore information about the request :\n<table class="table">\n <tbody>\n <tr>\n <td>phase</td>\n <td>${t}</td>\n </tr>\n <tr>\n <td>requested URL</td>\n <td>${r}</td>\n </tr>\n <tr>\n <td>downstream URL</td>\n <td>${o||"<no-target-url>"}</td>\n </tr>\n </tbody>\n</table>\n</div></body></html>`,b=(e,t,r)=>{t.writeHead(e,void 0,{"content-type":"text/html","content-length":r.length}),t.end(r)},y=e=>{const t=(e.headers[":authority"]?.toString()??e.headers.host??"localhost").replace(/:.*/,""),r=e.headers[":authority"]||`${e.headers.host}${e.headers.host.match(/:[0-9]+$/)?"":80!==h.port||h.ssl?443===h.port&&h.ssl?"":`:${h.port}`:""}`,n=new o.URL(`http${h.ssl?"s":""}://${r}${e.url}`),s=n.href.substring(n.origin.length),[a,i]=Object.entries({...Object.assign({},...Object.entries(h.mapping).map((([e,t])=>({[e]:new o.URL($(t))}))))}).find((([e])=>s.match(RegExp(e))))||[];return{proxyHostname:t,proxyHostnameAndPort:r,url:n,path:s,key:a,target:i}},v=()=>{m=(h.ssl?e.createSecureServer.bind(null,{...h.ssl,allowHTTP1:!0}):t.createServer)((async(n,a)=>{if(!n.headers.host&&!n.headers[":authority"])return void b(400,a,Buffer.from(E(new Error("client must supply a 'host' header"),"proxy",new o.URL(`http${h.ssl?"s":""}://unknowndomain${n.url}`))));const{proxyHostname:i,proxyHostnameAndPort:l,url:p,path:d,key:m,target:u}=y(n);if(!u)return void b(502,a,Buffer.from(E(new Error(`No mapping found in config file ${c}`),"proxy",p)));const g=u.host.replace(RegExp(/\/+$/),""),R=`${u.href.substring("https://".length+u.host.length)}${$(d.replace(RegExp($(m)),""))}`.replace(/^\/*/,"/"),f=new o.URL(`${u.protocol}//${g}${R}`);let w=null,v=!h.dontUseHttp2Downstream;const N="file:"===u.protocol?O(f):v?await Promise.race([new Promise((t=>{const r=(0,e.connect)(f,{rejectUnauthorized:!1,protocol:u.protocol},((e,o)=>{v=v&&!!o.alpnProtocol,t(v?r:null)}));r.on("error",(e=>{w=v&&Buffer.from(E(e,"connection",p,f))}))})),new Promise((e=>setTimeout((()=>{v=!1,e(null)}),3e3)))]):null;w instanceof Buffer||(w=null);const U={...[...Object.entries(n.headers)].filter((([e])=>!["host","connection"].includes(e.toLowerCase()))).reduce(((e,[t,r])=>(e[t]=(e[t]||"")+(Array.isArray(r)?r:[r]).map((e=>e.replace(p.hostname,g))).join(", "),e)),{}),origin:u.href,referer:f.toString(),":authority":g,":method":n.method,":path":R,":scheme":u.protocol.replace(":","")},S=N&&!w&&N.request(U,{endStream:h.ssl?!(n?.stream?.readableLength??1):!n.readableLength});S&&S.on("error",(e=>{const t=-505===e.errno;w=Buffer.from(E(e,"stream"+(t?" (error -505 usually means that the downstream service does not support this http version)":""),p,f))}));const B={hostname:u.hostname,path:R,port:u.port,protocol:u.protocol,rejectUnauthorized:!1,method:n.method,headers:{...Object.assign({},...Object.entries(U).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase())).map((([e,t])=>({[e]:t})))),host:u.hostname}},L=!w&&!v&&"file:"!==u.protocol&&await new Promise((e=>{const o="https:"===u.protocol?(0,r.request)(B,e):(0,t.request)(B,e);o.on("error",(t=>{w=Buffer.from(E(t,"request",p,f)),e(null)})),n.on("data",(e=>o.write(e))),n.on("end",(()=>o.end()))}));if(w)return void b(502,a,w);w=null,h.ssl&&n.stream&&n.stream.readableLength&&S&&(n.stream.on("data",(e=>S.write(e))),n.stream.on("end",(()=>S.end()))),!h.ssl&&n.readableLength&&S&&(n.on("data",(e=>S.write(e))),n.on("end",(()=>S.end())));const{outboundResponseHeaders:j}=await new Promise((e=>S?S.on("response",(t=>{e({outboundResponseHeaders:t})})):e(!S&&L?{outboundResponseHeaders:L.headers}:{outboundResponseHeaders:{}}))),x=j.location?new o.URL(j.location.startsWith("/")?`${u.href}${j.location.replace(/^\/+/,"")}`:j.location):null,D=x?x.href.substring(x.origin.length):null,T=p.origin,P=x?`${T}${D}`:null,C=S||L,H=w??await new Promise((e=>{let t=Buffer.alloc(0);C?(C.on("data",(e=>t=Buffer.concat([t,"string"==typeof e?Buffer.from(e):e]))),C.on("end",(()=>{e(t)}))):e(t)})).then((e=>h.replaceResponseBodyUrls&&e.length?(j["content-encoding"]||"").split(",").reduce((async(e,t)=>{const r=t.trim().toLowerCase(),o="gzip"===r||"x-gzip"===r?s.gunzip:"deflate"===r?s.inflate:"br"===r?s.brotliDecompress:"identity"===r||""===r?(e,t)=>{t(null,e)}:null;if(null===o)return void b(502,a,Buffer.from(E(new Error(`${r} compression not supported by the proxy`),"stream",p,f)));const n=await e;return await new Promise((e=>o(n,((t,r)=>{if(t)return b(502,a,Buffer.from(E(t,"stream",p,f))),void e("");e(r)}))))}),Promise.resolve(e)).then((e=>{const t=e.length>1e7,r=["text/html","application/javascript","application/json"].some((e=>(j["content-type"]??"").includes(e)));return!t&&(r||!/[^\x00-\x7F]/.test(e.toString()))?h.replaceResponseBodyUrls?Object.entries(h.mapping).reduce(((e,[t,r])=>""===t||t.match(/^[-a-zA-Z0-9()@:%_\+.~#?&//=]*$/)?e.replace(new RegExp(r.replace(/^file:\/\//,"").replace(/[*+?^${}()|[\]\\]/g,"").replace(/^https/,"https?")+"/*","ig"),`https://${l}${t.replace(/\/+$/,"")}/`):e),e.toString()).split(`${l}/:`).join(`${l}:`).replace(/\?protocol=wss?%3A&hostname=[^&]+&port=[0-9]+&pathname=/g,`?protocol=ws${h.ssl?"s":""}%3A&hostname=${i}&port=${h.port}&pathname=${encodeURIComponent(m.replace(/\/+$/,""))}`):e.toString():e})).then((e=>(j["content-encoding"]||"").split(",").reduce(((e,t)=>{const r=t.trim().toLowerCase(),o="gzip"===r||"x-gzip"===r?s.gzip:"deflate"===r?s.deflate:"br"===r?s.brotliCompress:"identity"===r||""===r?(e,t)=>{t(null,e)}:null;if(null===o)throw new Error(`${r} compression not supported by the proxy`);return e.then((e=>new Promise((t=>o(e,((e,r)=>{if(e)throw e;t(r)}))))))}),Promise.resolve(Buffer.from(e))))):e)),I={...Object.entries({...j,...h.replaceResponseBodyUrls?{"content-length":`${H.byteLength}`}:{}}).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase()&&"connection"!==e.toLowerCase())).reduce(((e,[t,r])=>{const o=g.split("").map(((e,t)=>g.substring(t).startsWith(".")&&g.substring(t))).filter((e=>e)),n=[g].concat(o).reduce(((e,t)=>(Array.isArray(e)?e:[e]).map((e=>"string"==typeof e?e.replace(`Domain=${t}`,`Domain=${p.hostname}`):e))),r);return e[t]=(e[t]||[]).concat(n),e}),{}),...P?{location:[P]}:{}};try{Object.entries(I).forEach((([e,t])=>t&&a.setHeader(e,t)))}catch(e){}a.writeHead(j[":status"]||L.statusCode||200,h.ssl?void 0:L.statusMessage||"Status read from http/2",I),H?a.end(H):a.end()})).addListener("error",(e=>{"EACCES"===e.code&&u("permission denied for this port",i.ERROR,l.NO),"EADDRINUSE"===e.code&&u("port is already used. NOT started",i.ERROR,l.ERROR_6)})).addListener("listening",(()=>{R(h)})).on("upgrade",((e,n)=>{if(!h.websocket)return void n.end("HTTP/1.1 503 Service Unavailable\r\n\r\n");const{key:s,target:a}=y(e),p=new o.URL(`${a.protocol}//${a.host}${e.url.endsWith("/_next/webpack-hmr")?e.url:e.url.replace(new RegExp(`^${s}`,"g"),"").replace(/^\/*/,"/")}`),c={hostname:p.hostname,path:p.pathname,port:p.port,protocol:p.protocol,rejectUnauthorized:!1,method:e.method,headers:e.headers,host:p.hostname},d="https:"===p.protocol?(0,r.request)(c):(0,t.request)(c);d.end(),d.on("error",(e=>{u("websocket request has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,l.WEBSOCKET)})),d.on("upgrade",((e,t)=>{const r=`HTTP/${e.httpVersion} ${e.statusCode} ${e.statusMessage}\r\n${Object.entries(e.headers).flatMap((([e,t])=>(Array.isArray(t)?t:[t]).map((t=>[e,t])))).map((([e,t])=>`${e}: ${t}\r\n`)).join("")}\r\n`;n.write(r),n.allowHalfOpen=!0,t.allowHalfOpen=!0,t.on("data",(e=>n.write(e))),n.on("data",(e=>t.write(e))),t.on("error",(e=>{u("downstream socket has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,l.WEBSOCKET)})),n.on("error",(e=>{u("upstream socket has errored "+(e.errno?`(${e.errno})`:""),i.WARNING,l.WEBSOCKET)}))}))})).listen(h.port)};g().then(v);
|
package/index.ts
CHANGED
|
@@ -37,11 +37,27 @@ type ErrorWithErrno = NodeJS.ErrnoException;
|
|
|
37
37
|
|
|
38
38
|
enum LogLevel {
|
|
39
39
|
ERROR = 124,
|
|
40
|
-
|
|
41
|
-
INFO = 21,
|
|
40
|
+
INFO = 93,
|
|
42
41
|
WARNING = 172,
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
enum EMOJIS {
|
|
45
|
+
INBOUND = '↘️ ',
|
|
46
|
+
PORT = '☎️ ',
|
|
47
|
+
OUTBOUND = '↗️ ',
|
|
48
|
+
RULES = '🔗',
|
|
49
|
+
BODY_REPLACEMENT = '✒️ ',
|
|
50
|
+
WEBSOCKET = '☄️ ',
|
|
51
|
+
COLORED = '✨',
|
|
52
|
+
NO = '⛔',
|
|
53
|
+
ERROR_1 = '❌',
|
|
54
|
+
ERROR_2 = '⛈️ ',
|
|
55
|
+
ERROR_3 = '☢️ ',
|
|
56
|
+
ERROR_4 = '⁉️ ',
|
|
57
|
+
ERROR_5 = '⚡',
|
|
58
|
+
ERROR_6 = '☠️ ',
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
interface LocalConfiguration {
|
|
46
62
|
mapping?: { [subPath: string]: string };
|
|
47
63
|
ssl?: SecureServerOptions;
|
|
@@ -89,9 +105,14 @@ const log = (text: string, level?: LogLevel, emoji?: string) => {
|
|
|
89
105
|
.replace(/⎸/g, "|")
|
|
90
106
|
.replace(/⎹/g, "|")
|
|
91
107
|
.replace(/\u001b\[[^m]*m/g, "")
|
|
92
|
-
.replace(
|
|
93
|
-
.replace(
|
|
94
|
-
.replace(
|
|
108
|
+
.replace(new RegExp(EMOJIS.INBOUND, "g"), "inbound:")
|
|
109
|
+
.replace(new RegExp(EMOJIS.PORT, "g"), "port:")
|
|
110
|
+
.replace(new RegExp(EMOJIS.OUTBOUND, "g"), "outbound:")
|
|
111
|
+
.replace(new RegExp(EMOJIS.RULES, "g"), 'rules:')
|
|
112
|
+
.replace(new RegExp(EMOJIS.NO, "g"), '')
|
|
113
|
+
.replace(new RegExp(EMOJIS.BODY_REPLACEMENT, "g"), 'body replacement')
|
|
114
|
+
.replace(new RegExp(EMOJIS.WEBSOCKET, "g"), 'websocket')
|
|
115
|
+
.replace(/\|+/g, '|')
|
|
95
116
|
: level
|
|
96
117
|
? `\u001b[48;5;${level}m⎸ ${
|
|
97
118
|
!process.stdout.isTTY ? "" : emoji || ""
|
|
@@ -100,11 +121,12 @@ const log = (text: string, level?: LogLevel, emoji?: string) => {
|
|
|
100
121
|
}`
|
|
101
122
|
);
|
|
102
123
|
};
|
|
124
|
+
|
|
103
125
|
const load = async (firstTime: boolean = true) =>
|
|
104
126
|
new Promise((resolve) =>
|
|
105
127
|
readFile(filename, (error, data) => {
|
|
106
128
|
if (error && !firstTime) {
|
|
107
|
-
log("config error. Using default value", LogLevel.ERROR,
|
|
129
|
+
log("config error. Using default value", LogLevel.ERROR, EMOJIS.ERROR_1);
|
|
108
130
|
}
|
|
109
131
|
try {
|
|
110
132
|
config = Object.assign(
|
|
@@ -113,13 +135,13 @@ const load = async (firstTime: boolean = true) =>
|
|
|
113
135
|
JSON.parse((data || "{}").toString())
|
|
114
136
|
);
|
|
115
137
|
} catch (e) {
|
|
116
|
-
log("config syntax incorrect, aborting", LogLevel.ERROR,
|
|
138
|
+
log("config syntax incorrect, aborting", LogLevel.ERROR, EMOJIS.ERROR_2);
|
|
117
139
|
config = config || { ...defaultConfig };
|
|
118
140
|
resolve(config);
|
|
119
141
|
return;
|
|
120
142
|
}
|
|
121
143
|
if (!config.mapping[""]) {
|
|
122
|
-
log('default mapping "" not provided.', LogLevel.WARNING,
|
|
144
|
+
log('default mapping "" not provided.', LogLevel.WARNING, EMOJIS.ERROR_3);
|
|
123
145
|
}
|
|
124
146
|
if (
|
|
125
147
|
error &&
|
|
@@ -129,8 +151,8 @@ const load = async (firstTime: boolean = true) =>
|
|
|
129
151
|
) {
|
|
130
152
|
writeFile(filename, JSON.stringify(defaultConfig), (fileWriteErr) => {
|
|
131
153
|
if (fileWriteErr)
|
|
132
|
-
log("config file NOT created", LogLevel.ERROR,
|
|
133
|
-
else log("config file created", LogLevel.
|
|
154
|
+
log("config file NOT created", LogLevel.ERROR, EMOJIS.ERROR_4);
|
|
155
|
+
else log("config file created", LogLevel.INFO, EMOJIS.COLORED);
|
|
134
156
|
resolve(config);
|
|
135
157
|
});
|
|
136
158
|
} else resolve(config);
|
|
@@ -139,15 +161,21 @@ const load = async (firstTime: boolean = true) =>
|
|
|
139
161
|
if (firstTime) watchFile(filename, onWatch);
|
|
140
162
|
});
|
|
141
163
|
|
|
142
|
-
const
|
|
164
|
+
const quickStatus = (thisConfig: LocalConfiguration) => {
|
|
143
165
|
log(
|
|
144
|
-
`\u001b[48;5;
|
|
145
|
-
thisConfig.ssl ? "HTTP/2 " : "HTTP 1.1"
|
|
146
|
-
} \u001b[48;5;21m⎸ ☎️ : ${thisConfig.port
|
|
166
|
+
`\u001b[48;5;52m⎸${EMOJIS.PORT} ${thisConfig.port
|
|
147
167
|
.toString()
|
|
148
|
-
.padStart(5)} \u001b[48;5;
|
|
149
|
-
thisConfig.
|
|
150
|
-
}
|
|
168
|
+
.padStart(5)} \u001b[48;5;53m⎸${EMOJIS.INBOUND} ${
|
|
169
|
+
thisConfig.ssl ? "H/2 " : "H1.1"
|
|
170
|
+
} \u001b[48;5;54m⎸${EMOJIS.OUTBOUND} ${
|
|
171
|
+
thisConfig.dontUseHttp2Downstream ? "H1.1" : "H/2 "
|
|
172
|
+
}⎹\u001b[48;5;55m⎸${EMOJIS.RULES}${Object.keys(config.mapping)
|
|
173
|
+
.length.toString()
|
|
174
|
+
.padStart(3)}⎹\u001b[48;5;56m⎸${
|
|
175
|
+
config.replaceResponseBodyUrls ?
|
|
176
|
+
EMOJIS.BODY_REPLACEMENT : EMOJIS.NO}⎹\u001b[48;5;57m⎸${
|
|
177
|
+
config.websocket ? EMOJIS.WEBSOCKET : EMOJIS.NO}⎹\u001b[48;5;93m⎸${
|
|
178
|
+
!config.simpleLogs ? EMOJIS.COLORED : EMOJIS.NO}⎹\u001b[0m`
|
|
151
179
|
);
|
|
152
180
|
};
|
|
153
181
|
|
|
@@ -156,45 +184,59 @@ const onWatch = async () => {
|
|
|
156
184
|
await load(false);
|
|
157
185
|
if (isNaN(config.port) || config.port > 65535 || config.port < 0) {
|
|
158
186
|
config = previousConfig;
|
|
159
|
-
log("port number invalid. Not refreshing", LogLevel.ERROR,
|
|
187
|
+
log("port number invalid. Not refreshing", LogLevel.ERROR, EMOJIS.PORT);
|
|
160
188
|
return;
|
|
161
189
|
}
|
|
162
190
|
if (typeof config.mapping !== "object") {
|
|
163
191
|
config = previousConfig;
|
|
164
|
-
log("mapping should be an object. Aborting", LogLevel.ERROR,
|
|
192
|
+
log("mapping should be an object. Aborting", LogLevel.ERROR, EMOJIS.ERROR_5);
|
|
165
193
|
return;
|
|
166
194
|
}
|
|
167
195
|
if (
|
|
168
|
-
config.replaceResponseBodyUrls
|
|
169
|
-
!previousConfig.replaceResponseBodyUrls
|
|
196
|
+
config.replaceResponseBodyUrls !== previousConfig.replaceResponseBodyUrls
|
|
170
197
|
) {
|
|
171
|
-
log(
|
|
198
|
+
log(`response body url ${
|
|
199
|
+
!config.replaceResponseBodyUrls ? 'NO ' : ''
|
|
200
|
+
}replacement`, LogLevel.INFO, EMOJIS.BODY_REPLACEMENT);
|
|
172
201
|
}
|
|
173
202
|
if (
|
|
174
|
-
|
|
175
|
-
previousConfig.replaceResponseBodyUrls
|
|
203
|
+
config.dontUseHttp2Downstream !== previousConfig.dontUseHttp2Downstream
|
|
176
204
|
) {
|
|
177
|
-
log(
|
|
205
|
+
log(`http/2 ${
|
|
206
|
+
config.dontUseHttp2Downstream ? 'de' : ''}activated downstream`, LogLevel.INFO, EMOJIS.OUTBOUND);
|
|
178
207
|
}
|
|
179
208
|
if (
|
|
180
|
-
config.websocket
|
|
181
|
-
!previousConfig.websocket
|
|
209
|
+
config.websocket !== previousConfig.websocket
|
|
182
210
|
) {
|
|
183
|
-
log(
|
|
211
|
+
log(`websocket ${
|
|
212
|
+
!config.websocket ? 'de' : ''}activated`, LogLevel.INFO, EMOJIS.WEBSOCKET);
|
|
184
213
|
}
|
|
185
214
|
if (
|
|
186
|
-
|
|
187
|
-
previousConfig.websocket
|
|
215
|
+
config.simpleLogs !== previousConfig.simpleLogs
|
|
188
216
|
) {
|
|
189
|
-
log(
|
|
217
|
+
log(`simple logs ${
|
|
218
|
+
!config.simpleLogs ? 'off' : 'on'}`, LogLevel.INFO, EMOJIS.COLORED);
|
|
219
|
+
}
|
|
220
|
+
if (Object.keys(config.mapping).join('\n') !== Object.keys(previousConfig.mapping).join('\n')) {
|
|
221
|
+
log(
|
|
222
|
+
`${Object.keys(config.mapping)
|
|
223
|
+
.length.toString()
|
|
224
|
+
.padStart(5)} loaded mapping rules`,
|
|
225
|
+
LogLevel.INFO,
|
|
226
|
+
EMOJIS.RULES
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (
|
|
230
|
+
config.port !== previousConfig.port
|
|
231
|
+
) {
|
|
232
|
+
log(`port changed from ${previousConfig.port} to ${config.port}`, LogLevel.INFO, EMOJIS.PORT);
|
|
233
|
+
}
|
|
234
|
+
if (config.ssl && !previousConfig.ssl) {
|
|
235
|
+
log(`ssl configuration added`, LogLevel.INFO, EMOJIS.INBOUND);
|
|
236
|
+
}
|
|
237
|
+
if (!config.ssl && previousConfig.ssl) {
|
|
238
|
+
log(`ssl configuration removed`, LogLevel.INFO, EMOJIS.INBOUND);
|
|
190
239
|
}
|
|
191
|
-
log(
|
|
192
|
-
`${Object.keys(config.mapping)
|
|
193
|
-
.length.toString()
|
|
194
|
-
.padStart(5)} loaded mapping rules`,
|
|
195
|
-
LogLevel.SUCCESS,
|
|
196
|
-
"↻"
|
|
197
|
-
);
|
|
198
240
|
if (
|
|
199
241
|
config.port !== previousConfig.port ||
|
|
200
242
|
JSON.stringify(config.ssl) !== JSON.stringify(previousConfig.ssl)
|
|
@@ -203,11 +245,7 @@ const onWatch = async () => {
|
|
|
203
245
|
!server ? resolve(void 0) : server.close(resolve)
|
|
204
246
|
);
|
|
205
247
|
start();
|
|
206
|
-
} else
|
|
207
|
-
config.dontUseHttp2Downstream !== previousConfig.dontUseHttp2Downstream
|
|
208
|
-
) {
|
|
209
|
-
logProtocols(config);
|
|
210
|
-
}
|
|
248
|
+
} else quickStatus(config);
|
|
211
249
|
};
|
|
212
250
|
|
|
213
251
|
const unixNorm = (path: string) =>
|
|
@@ -901,12 +939,12 @@ const start = () => {
|
|
|
901
939
|
) as Server)
|
|
902
940
|
.addListener("error", (err: Error) => {
|
|
903
941
|
if ((err as ErrorWithErrno).code === "EACCES")
|
|
904
|
-
log(`permission denied for this port`, LogLevel.ERROR,
|
|
942
|
+
log(`permission denied for this port`, LogLevel.ERROR, EMOJIS.NO);
|
|
905
943
|
if ((err as ErrorWithErrno).code === "EADDRINUSE")
|
|
906
|
-
log(`port is already used. NOT started`, LogLevel.ERROR,
|
|
944
|
+
log(`port is already used. NOT started`, LogLevel.ERROR, EMOJIS.ERROR_6);
|
|
907
945
|
})
|
|
908
946
|
.addListener("listening", () => {
|
|
909
|
-
|
|
947
|
+
quickStatus(config);
|
|
910
948
|
})
|
|
911
949
|
.on("upgrade", (request: IncomingMessage, upstreamSocket: Duplex) => {
|
|
912
950
|
if (!config.websocket) {
|
|
@@ -937,7 +975,8 @@ const start = () => {
|
|
|
937
975
|
downstreamRequest.on('error', (error) => {
|
|
938
976
|
log(`websocket request has errored ${
|
|
939
977
|
(error as ErrorWithErrno).errno ?
|
|
940
|
-
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
978
|
+
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
979
|
+
LogLevel.WARNING, EMOJIS.WEBSOCKET)
|
|
941
980
|
});
|
|
942
981
|
downstreamRequest.on('upgrade', (response, downstreamSocket) => {
|
|
943
982
|
const upgradeResponse = `HTTP/${response.httpVersion} ${response.statusCode} ${
|
|
@@ -954,12 +993,14 @@ const start = () => {
|
|
|
954
993
|
downstreamSocket.on('error', (error) => {
|
|
955
994
|
log(`downstream socket has errored ${
|
|
956
995
|
(error as ErrorWithErrno).errno ?
|
|
957
|
-
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
996
|
+
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
997
|
+
LogLevel.WARNING, EMOJIS.WEBSOCKET)
|
|
958
998
|
})
|
|
959
999
|
upstreamSocket.on('error', (error) => {
|
|
960
1000
|
log(`upstream socket has errored ${
|
|
961
1001
|
(error as ErrorWithErrno).errno ?
|
|
962
|
-
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
1002
|
+
`(${(error as ErrorWithErrno).errno})` : ''}`,
|
|
1003
|
+
LogLevel.WARNING, EMOJIS.WEBSOCKET)
|
|
963
1004
|
})
|
|
964
1005
|
});
|
|
965
1006
|
})
|