local-traffic 0.0.25 → 0.0.29
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 +32 -26
- package/package.json +4 -4
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"),n=require("url"),o=require("fs"),s=require("zlib"),i=require("path");var a;!function(e){e[e.ERROR=124]="ERROR",e[e.SUCCESS=35]="SUCCESS",e[e.INFO=21]="INFO",e[e.WARNING=172]="WARNING"}(a||(a={}));const l=i.resolve(process.env.HOME,".local-traffic.json"),p=i.resolve(__dirname,process.argv.slice(-1)[0].endsWith(".json")?process.argv.slice(-1)[0]:l),c={mapping:{},port:8080,replaceResponseBodyUrls:!1,dontUseHttp2Downstream:!1,simpleLogs:!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=>o.readFile(p,((r,n)=>{r&&!e&&u("config error. Using default value",a.ERROR,"❌");try{d=Object.assign({},c,JSON.parse((n||"{}").toString()))}catch(e){return u("config syntax incorrect, aborting",a.ERROR,"⛈️"),d=d||{...c},void t(d)}d.mapping[""]||u('default mapping "" not provided.',a.WARNING,"☢️"),r&&"ENOENT"===r.code&&e&&p===l?o.writeFile(p,JSON.stringify(c),(e=>{e?u("config file NOT created",a.ERROR,"⁉️"):u("config file created",a.SUCCESS,"✨"),t(d)})):t(d)})))).then((()=>{e&&o.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",a.ERROR,"☎️")):"object"!=typeof d.mapping?(d=e,void u("mapping should be an object. Aborting",a.ERROR,"⚡")):(d.replaceResponseBodyUrls&&!e.replaceResponseBodyUrls&&u("response body url replacement",a.INFO,"✔️"),!d.replaceResponseBodyUrls&&e.replaceResponseBodyUrls&&u("response body url NO replacement",a.INFO,"✖️"),u(`${Object.keys(d.mapping).length.toString().padStart(5)} loaded mapping rules`,a.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))),v()):d.dontUseHttp2Downstream!==e.dontUseHttp2Downstream&&g(d)))},$=e=>""==e?"":i.normalize(e).replace(/\\/g,"/"),b=e=>{const t=i.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=>o.readFile(t,((n,s)=>{if(this.hasRun=!0,!n||"EISDIR"!==n.code)return this.error=n,this.data=s,void r(void 0);o.readdir(t,((t,n)=>{this.error=t,this.data=n,t?r(void 0):Promise.all(n.map((t=>new Promise((r=>o.lstat(i.resolve(e.pathname,t),((e,n)=>r([t,n,e])))))))).then((t=>{const n=t.filter((e=>!e[2]&&e[1].isDirectory())).concat(t.filter((e=>!e[2]&&e[1].isFile())));this.data=`${y(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 ${n.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,t){return this.events[e]=t,this.run().then((()=>{"response"===e&&this.events.response({Server:"local"},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}}},y=(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/>`,R=(e,t,r,n)=>`${y(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>${n||"<no-target-url>"}</td>\n </tr>\n </tbody>\n</table>\n</div></body></html>`,w=(e,t,r)=>{t.writeHead(e,void 0,{"content-type":"text/html","content-length":r.length}),t.end(r)},v=()=>{h=(d.ssl?e.createSecureServer.bind(null,{...d.ssl,allowHTTP1:!0}):t.createServer)((async(o,i)=>{if(!o.headers.host&&!o.headers[":authority"])return void w(400,i,Buffer.from(R(new Error("client must supply a 'host' header"),"proxy",new n.URL(`http${d.ssl?"s":""}://unknowndomain${o.url}`))));const a=o.headers[":authority"]||`${o.headers.host}${o.headers.host.match(/:[0-9]+$/)?"":80!==d.port||d.ssl?443===d.port&&d.ssl?"":`:${d.port}`:""}`,l=new n.URL(`http${d.ssl?"s":""}://${a}${o.url}`),c=l.href.substring(l.origin.length),[h,u]=Object.entries({...Object.assign({},...Object.entries(d.mapping).map((([e,t])=>({[e]:new n.URL($(t))}))))}).find((([e])=>c.match(RegExp(e))))||[];if(!u)return void w(502,i,Buffer.from(R(new Error(`No mapping found in config file ${p}`),"proxy",l)));const m=u.host.replace(RegExp(/\/+$/),""),g=`${u.href.substring("https://".length+u.host.length)}${$(c.replace(RegExp($(h)),""))}`.replace(/^\/*/,"/"),f=new n.URL(`${u.protocol}//${m}${g}`);let y=null,v=!d.dontUseHttp2Downstream;const S="file:"===u.protocol?b(f):v?await Promise.race([new Promise((t=>{const r=e.connect(f,{rejectUnauthorized:!1,protocol:u.protocol},((e,n)=>{v=v&&!!n.alpnProtocol,t(v?r:null)}));r.on("error",(e=>{y=v&&Buffer.from(R(e,"connection",l,f))}))})),new Promise((e=>setTimeout((()=>{v=!1,e(null)}),3e3)))]):null;!v&&y&&(y=null);const O={...[...Object.entries(o.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(l.hostname,m))).join(", "),e)),{}),origin:u.href,referer:f.toString(),":authority":m,":method":o.method,":path":g,":scheme":u.protocol.replace(":","")},E=S&&!y&&S.request(O,{endStream:d.ssl?!o.stream.readableLength:!o.readableLength});E&&E.on("error",(e=>{const t=-505===e.errno;y=Buffer.from(R(e,"streaming"+(t?" (error -505 usually means that the downstream service does not support this http version)":""),l,f))}));const j={hostname:u.hostname,path:g,port:u.port,protocol:u.protocol,rejectUnauthorized:!1,method:o.method,headers:{...Object.assign({},...Object.entries(O).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase())).map((([e,t])=>({[e]:t})))),host:u.hostname}},U=!y&&!v&&"file:"!==u.protocol&&await new Promise((e=>{const n="https:"===u.protocol?r.request(j,e):t.request(j,e);n.on("error",(t=>{y=Buffer.from(R(t,"request",l,f)),e(null)})),o.on("data",(e=>n.write(e))),o.on("end",(()=>n.end()))}));if(y)return void w(502,i,y);d.ssl&&o.stream&&o.stream.readableLength&&E&&(E.setEncoding("utf8"),o.stream.on("data",(e=>E.write(e))),o.stream.on("end",(()=>E.end()))),!d.ssl&&o.readableLength&&E&&(E.setEncoding("utf8"),o.on("data",(e=>E.write(e))),o.on("end",(()=>E.end())));const{outboundResponseHeaders:N}=await new Promise((e=>E?E.on("response",(t=>{e({outboundResponseHeaders:t})})):e(!E&&U?{outboundResponseHeaders:U.headers}:{outboundResponseHeaders:{}}))),L=N.location?new n.URL(N.location.startsWith("/")?`${u.href}${N.location.replace(/^\/+/,"")}`:N.location):null,P=L?L.href.substring(L.origin.length):null,x=l.origin,B=L?`${x}${P}`:null,C=E||U,H=y||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=>d.replaceResponseBodyUrls&&e.length?(N["content-encoding"]||"").split(",").reduce(((e,t)=>{const r=t.trim().toLowerCase(),n="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!==n)return e.then((e=>new Promise((t=>n(e,((e,r)=>{if(e)return w(502,i,Buffer.from(R(e,"stream",l,f))),void t("");t(r)}))))));w(502,i,Buffer.from(R(new Error(`${r} compression not supported by the proxy`),"stream",l,f)))}),Promise.resolve(e)).then((e=>d.replaceResponseBodyUrls?Object.entries(d.mapping).reduce(((e,[t,r])=>t.match(/^[-a-zA-Z0-9()@:%_\+.~#?&//=]*$/)?e.replace(new RegExp(r.replace(/^file:\/\//,"").replace(/[*+?^${}()|[\]\\]/g,""),"ig"),`https://${a}${t.replace(/\/+$/,"")}/`):e),e.toString()).split(`${a}/:`).join(`${a}:`):e.toString())).then((e=>(N["content-encoding"]||"").split(",").reduce(((e,t)=>{const r=t.trim().toLowerCase(),n="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===n)throw new Error(`${r} compression not supported by the proxy`);return e.then((e=>new Promise((t=>n(e,((e,r)=>{if(e)throw e;t(r)}))))))}),Promise.resolve(Buffer.from(e))))):e)),T={...Object.entries({...N,...d.replaceResponseBodyUrls?{"content-length":`${H.byteLength}`}:{}}).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase()&&"connection"!==e.toLowerCase())).reduce(((e,[t,r])=>{const n=m.split("").map(((e,t)=>m.substring(t).startsWith(".")&&m.substring(t))).filter((e=>e)),o=[m].concat(n).reduce(((e,t)=>(Array.isArray(e)?e:[e]).map((e=>"string"==typeof e?e.replace(`Domain=${t}`,`Domain=${l.hostname}`):e))),r);return e[t]=(e[t]||[]).concat(o),e}),{}),...B?{location:[B]}:{}};i.writeHead(N[":status"]||U.statusCode||200,d.ssl?void 0:U.statusMessage||"Status read from http/2",T),H?i.end(H):i.end()})).addListener("error",(e=>{"EACCES"===e.code&&u("permission denied for this port",a.ERROR,"⛔"),"EADDRINUSE"===e.code&&u("port is already used. NOT started",a.ERROR,"☠️")})).addListener("listening",(()=>{g(d)})).listen(d.port)};m().then(v);
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e=require("http2"),t=require("http"),r=require("https"),n=require("url"),o=require("fs"),s=require("zlib"),i=require("path");var a;!function(e){e[e.ERROR=124]="ERROR",e[e.SUCCESS=35]="SUCCESS",e[e.INFO=21]="INFO",e[e.WARNING=172]="WARNING"}(a||(a={}));const l=(0,i.resolve)(process.env.HOME,".local-traffic.json"),p=(0,i.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};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,o.readFile)(p,((r,n)=>{r&&!e&&u("config error. Using default value",a.ERROR,"❌");try{d=Object.assign({},c,JSON.parse((n||"{}").toString()))}catch(e){return u("config syntax incorrect, aborting",a.ERROR,"⛈️"),d=d||{...c},void t(d)}d.mapping[""]||u('default mapping "" not provided.',a.WARNING,"☢️"),r&&"ENOENT"===r.code&&e&&p===l?(0,o.writeFile)(p,JSON.stringify(c),(e=>{e?u("config file NOT created",a.ERROR,"⁉️"):u("config file created",a.SUCCESS,"✨"),t(d)})):t(d)})))).then((()=>{e&&(0,o.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",a.ERROR,"☎️")):"object"!=typeof d.mapping?(d=e,void u("mapping should be an object. Aborting",a.ERROR,"⚡")):(d.replaceResponseBodyUrls&&!e.replaceResponseBodyUrls&&u("response body url replacement",a.INFO,"✔️"),!d.replaceResponseBodyUrls&&e.replaceResponseBodyUrls&&u("response body url NO replacement",a.INFO,"✖️"),u(`${Object.keys(d.mapping).length.toString().padStart(5)} loaded mapping rules`,a.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))),v()):d.dontUseHttp2Downstream!==e.dontUseHttp2Downstream&&g(d)))},$=e=>""==e?"":(0,i.normalize)(e).replace(/\\/g,"/"),b=e=>{const t=(0,i.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,o.readFile)(t,((n,s)=>{if(this.hasRun=!0,!n||"EISDIR"!==n.code)return this.error=n,this.data=s,void r(void 0);(0,o.readdir)(t,((t,n)=>{this.error=t,this.data=n,t?r(void 0):Promise.all(n.map((t=>new Promise((r=>(0,o.lstat)((0,i.resolve)(e.pathname,t),((e,n)=>r([t,n,e])))))))).then((t=>{const n=t.filter((e=>!e[2]&&e[1].isDirectory())).concat(t.filter((e=>!e[2]&&e[1].isFile())));this.data=`${y(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 ${n.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}}},y=(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/>`,w=(e,t,r,n)=>`${y(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>${n||"<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=()=>{h=(d.ssl?e.createSecureServer.bind(null,{...d.ssl,allowHTTP1:!0}):t.createServer)((async(o,i)=>{if(!o.headers.host&&!o.headers[":authority"])return void R(400,i,Buffer.from(w(new Error("client must supply a 'host' header"),"proxy",new n.URL(`http${d.ssl?"s":""}://unknowndomain${o.url}`))));const a=o.headers[":authority"]||`${o.headers.host}${o.headers.host.match(/:[0-9]+$/)?"":80!==d.port||d.ssl?443===d.port&&d.ssl?"":`:${d.port}`:""}`,l=new n.URL(`http${d.ssl?"s":""}://${a}${o.url}`),c=l.href.substring(l.origin.length),[h,u]=Object.entries({...Object.assign({},...Object.entries(d.mapping).map((([e,t])=>({[e]:new n.URL($(t))}))))}).find((([e])=>c.match(RegExp(e))))||[];if(!u)return void R(502,i,Buffer.from(w(new Error(`No mapping found in config file ${p}`),"proxy",l)));const m=u.host.replace(RegExp(/\/+$/),""),g=`${u.href.substring("https://".length+u.host.length)}${$(c.replace(RegExp($(h)),""))}`.replace(/^\/*/,"/"),f=new n.URL(`${u.protocol}//${m}${g}`);let y=null,v=!d.dontUseHttp2Downstream;const S="file:"===u.protocol?b(f):v?await Promise.race([new Promise((t=>{const r=(0,e.connect)(f,{rejectUnauthorized:!1,protocol:u.protocol},((e,n)=>{v=v&&!!n.alpnProtocol,t(v?r:null)}));r.on("error",(e=>{y=v&&Buffer.from(w(e,"connection",l,f))}))})),new Promise((e=>setTimeout((()=>{v=!1,e(null)}),3e3)))]):null;!v&&y&&(y=null);const O={...[...Object.entries(o.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(l.hostname,m))).join(", "),e)),{}),origin:u.href,referer:f.toString(),":authority":m,":method":o.method,":path":g,":scheme":u.protocol.replace(":","")},E=S&&!y&&S.request(O,{endStream:d.ssl?!(o?.stream?.readableLength??1):!o.readableLength});E&&E.on("error",(e=>{const t=-505===e.errno;y=Buffer.from(w(e,"stream"+(t?" (error -505 usually means that the downstream service does not support this http version)":""),l,f))}));const j={hostname:u.hostname,path:g,port:u.port,protocol:u.protocol,rejectUnauthorized:!1,method:o.method,headers:{...Object.assign({},...Object.entries(O).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase())).map((([e,t])=>({[e]:t})))),host:u.hostname}},U=!y&&!v&&"file:"!==u.protocol&&await new Promise((e=>{const n="https:"===u.protocol?(0,r.request)(j,e):(0,t.request)(j,e);n.on("error",(t=>{y=Buffer.from(w(t,"request",l,f)),e(null)})),o.on("data",(e=>n.write(e))),o.on("end",(()=>n.end()))}));if(y)return void R(502,i,y);d.ssl&&o.stream&&o.stream.readableLength&&E&&(o.stream.on("data",(e=>E.write(e))),o.stream.on("end",(()=>E.end()))),!d.ssl&&o.readableLength&&E&&(o.on("data",(e=>E.write(e))),o.on("end",(()=>E.end())));const{outboundResponseHeaders:N}=await new Promise((e=>E?E.on("response",(t=>{e({outboundResponseHeaders:t})})):e(!E&&U?{outboundResponseHeaders:U.headers}:{outboundResponseHeaders:{}}))),L=N.location?new n.URL(N.location.startsWith("/")?`${u.href}${N.location.replace(/^\/+/,"")}`:N.location):null,P=L?L.href.substring(L.origin.length):null,x=l.origin,C=L?`${x}${P}`:null,B=E||U,H=y??await new Promise((e=>{let t=Buffer.alloc(0);B?(B.on("data",(e=>t=Buffer.concat([t,"string"==typeof e?Buffer.from(e):e]))),B.on("end",(()=>{e(t)}))):e(t)})).then((e=>d.replaceResponseBodyUrls&&e.length?(N["content-encoding"]||"").split(",").reduce((async(e,t)=>{const r=t.trim().toLowerCase(),n="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===n)return void R(502,i,Buffer.from(w(new Error(`${r} compression not supported by the proxy`),"stream",l,f)));const o=await e;return await new Promise((e=>n(o,((t,r)=>{if(t)return R(502,i,Buffer.from(w(t,"stream",l,f))),void e("");e(r)}))))}),Promise.resolve(e)).then((e=>d.replaceResponseBodyUrls?Object.entries(d.mapping).reduce(((e,[t,r])=>t.match(/^[-a-zA-Z0-9()@:%_\+.~#?&//=]*$/)?e.replace(new RegExp(r.replace(/^file:\/\//,"").replace(/[*+?^${}()|[\]\\]/g,""),"ig"),`https://${a}${t.replace(/\/+$/,"")}/`):e),e.toString()).split(`${a}/:`).join(`${a}:`):e.toString())).then((e=>(N["content-encoding"]||"").split(",").reduce(((e,t)=>{const r=t.trim().toLowerCase(),n="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===n)throw new Error(`${r} compression not supported by the proxy`);return e.then((e=>new Promise((t=>n(e,((e,r)=>{if(e)throw e;t(r)}))))))}),Promise.resolve(Buffer.from(e))))):e)),T={...Object.entries({...N,...d.replaceResponseBodyUrls?{"content-length":`${H.byteLength}`}:{}}).filter((([e])=>!e.startsWith(":")&&"transfer-encoding"!==e.toLowerCase()&&"connection"!==e.toLowerCase())).reduce(((e,[t,r])=>{const n=m.split("").map(((e,t)=>m.substring(t).startsWith(".")&&m.substring(t))).filter((e=>e)),o=[m].concat(n).reduce(((e,t)=>(Array.isArray(e)?e:[e]).map((e=>"string"==typeof e?e.replace(`Domain=${t}`,`Domain=${l.hostname}`):e))),r);return e[t]=(e[t]||[]).concat(o),e}),{}),...C?{location:[C]}:{}};try{Object.entries(T).forEach((([e,t])=>t&&i.setHeader(e,t)))}catch(e){}i.writeHead(N[":status"]||U.statusCode||200,d.ssl?void 0:U.statusMessage||"Status read from http/2",T),H?i.end(H):i.end()})).addListener("error",(e=>{"EACCES"===e.code&&u("permission denied for this port",a.ERROR,"⛔"),"EADDRINUSE"===e.code&&u("port is already used. NOT started",a.ERROR,"☠️")})).addListener("listening",(()=>{g(d)})).listen(d.port)};m().then(v);
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
OutgoingHttpHeaders,
|
|
10
10
|
SecureClientSessionOptions,
|
|
11
11
|
SecureServerOptions,
|
|
12
|
+
ClientHttp2Stream,
|
|
12
13
|
} from "http2";
|
|
13
14
|
import {
|
|
14
15
|
request as httpRequest,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
brotliDecompress,
|
|
31
32
|
} from "zlib";
|
|
32
33
|
import { resolve, normalize } from "path";
|
|
34
|
+
import type { Duplex } from "stream";
|
|
33
35
|
|
|
34
36
|
enum LogLevel {
|
|
35
37
|
ERROR = 124,
|
|
@@ -49,7 +51,7 @@ interface LocalConfiguration {
|
|
|
49
51
|
|
|
50
52
|
const userHomeConfigFile = resolve(process.env.HOME, ".local-traffic.json");
|
|
51
53
|
const filename = resolve(
|
|
52
|
-
|
|
54
|
+
process.cwd(),
|
|
53
55
|
process.argv.slice(-1)[0].endsWith(".json")
|
|
54
56
|
? process.argv.slice(-1)[0]
|
|
55
57
|
: userHomeConfigFile
|
|
@@ -291,7 +293,8 @@ const fileRequest = (url: URL): ClientHttp2Session => {
|
|
|
291
293
|
this.events[name] = action;
|
|
292
294
|
this.run().then(() => {
|
|
293
295
|
if (name === "response")
|
|
294
|
-
this.events["response"]({ Server: "local"
|
|
296
|
+
this.events["response"]({ Server: "local",
|
|
297
|
+
'Content-Type': file.endsWith('.svg') ? 'image/svg+xml' : null}, 0);
|
|
295
298
|
if (name === "data" && this.data) {
|
|
296
299
|
this.events["data"](this.data);
|
|
297
300
|
this.events["end"]();
|
|
@@ -515,7 +518,7 @@ const start = () => {
|
|
|
515
518
|
!error &&
|
|
516
519
|
outboundRequest.request(outboundHeaders, {
|
|
517
520
|
endStream: config.ssl
|
|
518
|
-
? !(inboundRequest as Http2ServerRequest)
|
|
521
|
+
? !((inboundRequest as Http2ServerRequest)?.stream?.readableLength ?? true)
|
|
519
522
|
: !(inboundRequest as IncomingMessage).readableLength,
|
|
520
523
|
});
|
|
521
524
|
|
|
@@ -527,7 +530,7 @@ const start = () => {
|
|
|
527
530
|
error = Buffer.from(
|
|
528
531
|
errorPage(
|
|
529
532
|
thrown,
|
|
530
|
-
"
|
|
533
|
+
"stream" +
|
|
531
534
|
(httpVersionSupported
|
|
532
535
|
? " (error -505 usually means that the downstream service " +
|
|
533
536
|
"does not support this http version)"
|
|
@@ -591,7 +594,6 @@ const start = () => {
|
|
|
591
594
|
(inboundRequest as Http2ServerRequest).stream.readableLength &&
|
|
592
595
|
outboundExchange
|
|
593
596
|
) {
|
|
594
|
-
outboundExchange.setEncoding("utf8");
|
|
595
597
|
(inboundRequest as Http2ServerRequest).stream.on("data", (chunk) =>
|
|
596
598
|
outboundExchange.write(chunk)
|
|
597
599
|
);
|
|
@@ -605,7 +607,6 @@ const start = () => {
|
|
|
605
607
|
(inboundRequest as IncomingMessage).readableLength &&
|
|
606
608
|
outboundExchange
|
|
607
609
|
) {
|
|
608
|
-
outboundExchange.setEncoding("utf8");
|
|
609
610
|
(inboundRequest as IncomingMessage).on("data", (chunk) =>
|
|
610
611
|
outboundExchange.write(chunk)
|
|
611
612
|
);
|
|
@@ -650,14 +651,14 @@ const start = () => {
|
|
|
650
651
|
// phase : response body
|
|
651
652
|
const payloadSource = outboundExchange || outboundHttp1Response;
|
|
652
653
|
const payload =
|
|
653
|
-
error
|
|
654
|
+
error ??
|
|
654
655
|
(await new Promise((resolve) => {
|
|
655
656
|
let partialBody = Buffer.alloc(0);
|
|
656
657
|
if (!payloadSource) {
|
|
657
658
|
resolve(partialBody);
|
|
658
659
|
return;
|
|
659
660
|
}
|
|
660
|
-
(payloadSource as
|
|
661
|
+
(payloadSource as ClientHttp2Stream | Duplex).on(
|
|
661
662
|
"data",
|
|
662
663
|
(chunk: Buffer | string) =>
|
|
663
664
|
(partialBody = Buffer.concat([
|
|
@@ -666,7 +667,7 @@ const start = () => {
|
|
|
666
667
|
? Buffer.from(chunk as string)
|
|
667
668
|
: (chunk as Buffer),
|
|
668
669
|
]))
|
|
669
|
-
|
|
670
|
+
);
|
|
670
671
|
(payloadSource as any).on("end", () => {
|
|
671
672
|
resolve(partialBody);
|
|
672
673
|
});
|
|
@@ -676,7 +677,7 @@ const start = () => {
|
|
|
676
677
|
|
|
677
678
|
return (outboundResponseHeaders["content-encoding"] || "")
|
|
678
679
|
.split(",")
|
|
679
|
-
.reduce((buffer: Promise<Buffer>, formatNotTrimed: string) => {
|
|
680
|
+
.reduce(async (buffer: Promise<Buffer>, formatNotTrimed: string) => {
|
|
680
681
|
const format = formatNotTrimed.trim().toLowerCase();
|
|
681
682
|
const method =
|
|
682
683
|
format === "gzip" || format === "x-gzip"
|
|
@@ -711,22 +712,20 @@ const start = () => {
|
|
|
711
712
|
return;
|
|
712
713
|
}
|
|
713
714
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
});
|
|
729
|
-
})
|
|
715
|
+
const openedBuffer = await buffer;
|
|
716
|
+
return await new Promise((resolve) =>
|
|
717
|
+
method(openedBuffer, (err_1, data_1) => {
|
|
718
|
+
if (err_1) {
|
|
719
|
+
send(
|
|
720
|
+
502,
|
|
721
|
+
inboundResponse,
|
|
722
|
+
Buffer.from(errorPage(err_1, "stream", url, targetUrl))
|
|
723
|
+
);
|
|
724
|
+
resolve("");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
resolve(data_1);
|
|
728
|
+
})
|
|
730
729
|
);
|
|
731
730
|
}, Promise.resolve(payloadBuffer))
|
|
732
731
|
.then((uncompressedBuffer: Buffer) =>
|
|
@@ -835,6 +834,13 @@ const start = () => {
|
|
|
835
834
|
}, {}),
|
|
836
835
|
...(newTargetUrl ? { location: [newTargetUrl] } : {}),
|
|
837
836
|
};
|
|
837
|
+
try {
|
|
838
|
+
Object.entries(responseHeaders).forEach(([headerName, headerValue]) =>
|
|
839
|
+
headerValue && inboundResponse.setHeader(headerName, headerValue as string)
|
|
840
|
+
);
|
|
841
|
+
} catch(e) {
|
|
842
|
+
// ERR_HTTP2_HEADERS_SENT
|
|
843
|
+
}
|
|
838
844
|
inboundResponse.writeHead(
|
|
839
845
|
outboundResponseHeaders[":status"] ||
|
|
840
846
|
outboundHttp1Response.statusCode ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-traffic",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"private": false,
|
|
6
6
|
"keywords": [
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"build": "yarn typescript && yarn terser && yarn chmod"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/node": "^
|
|
25
|
-
"terser": "^5.
|
|
26
|
-
"typescript": "^4.
|
|
24
|
+
"@types/node": "^17.0.12",
|
|
25
|
+
"terser": "^5.10.0",
|
|
26
|
+
"typescript": "^4.5.5"
|
|
27
27
|
},
|
|
28
28
|
"bin": {
|
|
29
29
|
"local-traffic": "./dist/localTraffic.js"
|