node-network-devtools
Version:
Inspecting Node.js's Network with Chrome DevTools
6 lines (5 loc) • 12.6 kB
JavaScript
;var B=t=>{throw TypeError(t)};var O=(t,e,s)=>e.has(t)||B("Cannot "+s);var D=(t,e,s)=>(O(t,e,"read from private field"),s?s.call(t):e.get(t)),C=(t,e,s)=>e.has(t)?B("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),v=(t,e,s,r)=>(O(t,e,"write to private field"),r?r.call(t,s):e.set(t,s),s);const N=require("ws"),x=require("open"),d=require("./common-B2WLGzKJ.js"),k=require("url"),H=require("iconv-lite"),E=require("node:zlib"),y=require("fs"),L=require("path"),J=(t,e)=>{try{return JSON.parse(t)}catch{return e}};class z{constructor(){this.timestamp=0,this.startTime=Date.now(),this.listeners=[]}getTimestamp(){return this.updateTimestamp(),this.timestamp}updateTimestamp(){this.timestamp=(Date.now()-this.startTime)/1e3}on(e){this.listeners.push(e)}}class A extends z{constructor(e){super(),this.browser=null;const{port:s,autoOpenDevtool:r=!0,onConnect:i,onClose:a}=e;this.port=s,this.server=new N.Server({port:s});const{server:u}=this;u.on("listening",()=>{d.log(`devtool server is listening on port ${s}`),r&&this.open()}),this.socket=new Promise(l=>{u.on("connection",n=>{i?.(),this.socket.then(o=>{o[0]=n}),d.log("devtool connected"),n.on("message",o=>{const c=JSON.parse(o.toString());this.listeners.forEach(p=>p(null,c))}),n.on("close",()=>{d.log("devtool closed"),a?.()}),n.on("error",o=>{this.listeners.forEach(c=>c(o))}),l([n])})})}async open(){const e=`devtools://devtools/bundled/inspector.html?ws=127.0.0.1:${this.port}`;try{if(d.IS_DEV_MODE){d.log(`In dev mode, open chrome devtool manually: ${e}`);return}const s=await x(e,{app:{name:x.apps.chrome,arguments:[process.platform!=="darwin"?`--remote-debugging-port=${d.REMOTE_DEBUGGER_PORT}`:""]},wait:!0});e:if(process.platform!=="darwin"){const r=await new Promise(n=>{let o=0,c=setInterval(async()=>{o>5&&(clearInterval(c),n([]));try{o++,n((await fetch(`http://127.0.0.1:${d.REMOTE_DEBUGGER_PORT}/json`)).json()),clearInterval(c)}catch{}},500)}),[i]=r;if(!i)break e;const{id:a,webSocketDebuggerUrl:u}=i,l=new N.WebSocket(u);l.on("open",()=>{const n={id:a,method:"Page.navigate",params:{url:e}};l.send(JSON.stringify(n)),l.close()})}d.log("opened in chrome or click here to open chrome devtool: ",e),this.browser=s;return}catch{console.warn("Open devtools failed, but don't worry, you can open it in browser(Chrome or Edge) manually: "+e)}}close(){this.server.close(),this.browser&&this.browser.kill()}async send(e){const[s]=await this.socket;return s.send(JSON.stringify(e))}}var T,S;class V{constructor(e){C(this,T);C(this,S);this.listeners={},v(this,T,[]),this.options=e;const{serverPort:s,autoOpenDevtool:r}=e;this.devtool=new A({port:s,autoOpenDevtool:r,onConnect:()=>{this.listeners.onConnect?.forEach(a=>a({data:null,id:"onConnect"}))}}),this.devtool.on((i,a)=>{if(i){d.log(i);return}const u=this.listeners[a.method];u&&u.forEach(l=>{l({data:a.params,id:a.id})})}),this.server=this.initServer()}loadPlugins(e){v(this,T,e),v(this,S,new Map),e.forEach(s=>{const r=s({devtool:this.devtool,core:this,plugins:D(this,T)});D(this,S).set(s.id,r)})}usePlugin(e){return D(this,S)?D(this,S).get(e):null}on(e,s){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(s),()=>{this.listeners[e].delete(s)}}close(){this.server.close(),this.devtool.close()}initServer(){const e=new N.Server({port:this.options.port||d.PORT});return e.on("connection",s=>{s.on("message",r=>{const a=JSON.parse(r.toString());switch(a.type){default:{const u=this.listeners[a.type];if(!u)break;u.forEach(l=>{l({data:a.data})})}break}})}),e.on("listening",()=>{process.send&&process.send(d.READY_MESSAGE)}),e}}T=new WeakMap,S=new WeakMap;let I=null;const K=(t,e)=>{I={devtool:e,core:t}},Y=()=>I=null,P=(t,e)=>Object.assign(i=>{K(i.core,i.devtool);const a=e(i);return Y(),a},{id:t}),g=(t,e)=>{if(!I)return;const s=I,{core:r}=s;return r.on(t,e)},Q=t=>{if(!I)return;const{core:e}=I;return e.on("onConnect",t)},X=P("debugger",({devtool:t,core:e})=>{const s=e.usePlugin("network");g("Debugger.getScriptSource",({id:i,data:a})=>{if(!i)return;const{scriptId:u}=a,l=s.resourceService.getScriptSource(u);t.send({id:i,method:"Debugger.getScriptSourceResponse",result:{scriptSource:l}})});const r=s.resourceService.getLocalScriptList();Q(()=>{r.forEach(i=>{t.send({method:"Debugger.scriptParsed",params:i})})})});class q{constructor(e){this.headers={...e||{}},Object.keys(this.headers).forEach(s=>{this.headers[s]=String(this.headers[s])})}getHeader(e){const s=e.toLowerCase(),r=Object.keys(this.headers);for(const i of r)if(i.toLowerCase()===s)return this.headers[i]}getData(){return this.headers}valueOf(){return this.headers}}class Z{constructor(e){this.req=e}decodeBody(){const{req:e}=this,r=new q(e.responseHeaders).getHeader("content-type")||"text/plain; charset=utf-8",i=r.match(/charset=([^;]+)/),a=i?i[1]:"utf-8",u=!/text|json|xml/.test(r);return{body:(()=>{if(!(e.responseData===void 0||e.responseData===null))return u?e.responseData.toString("base64"):Buffer.isBuffer(e.responseData)?H.decode(e.responseData,a):typeof e.responseData=="object"&&"type"in e.responseData&&e.responseData.type==="Buffer"&&"data"in e.responseData&&Array.isArray(e.responseData.data)?H.decode(Buffer.from(e.responseData.data),a):e.responseData})(),base64Encoded:u}}}function ee(t){switch(t.split(".").pop()?.toLowerCase()){case"js":case"mjs":case"cjs":return"JavaScript";case"wasm":return"WebAssembly";default:return"Unknown"}}class te{constructor(){this.urlToScriptId=new Map,this.scriptIdToUrl=new Map}addMapping(e,s){this.urlToScriptId.set(e,s),this.scriptIdToUrl.set(s,e)}getUrlByScriptId(e){return this.scriptIdToUrl.get(e)}getScriptIdByUrl(e){return this.urlToScriptId.get(e)}}class se{constructor(){this.scriptMap=new te,this.scriptIdCounter=0}getScriptIdByUrl(e){return this.scriptMap.getScriptIdByUrl(e)}getUrlByScriptId(e){return this.scriptMap.getUrlByScriptId(e)}getScriptSource(e){const s=this.scriptMap.getUrlByScriptId(e);if(!s)return console.error(`No file path found for script ID: ${e}`),null;const r=k.fileURLToPath(s);try{return y.readFileSync(r,"utf-8")}catch(i){return console.error("Error reading file:",i),null}}readLastLine(e,s,r=1){const i=s.size,a=Math.min(1024,i);let u=i-a,l=Buffer.alloc(a),n=[];const o=y.openSync(e,"r");for(;n.length<r&&u>=0;)y.readSync(o,l,0,a,u),n=l.toString("utf8").split(`
`).concat(n),u-=a,u<0&&(u=0,l=Buffer.alloc(i-u));return y.closeSync(o),n.slice(-r).join(`
`)}traverseDirToMap(e,s=["node_modules"]){const r=[],i=[e];let a=this.scriptIdCounter;for(;i.length>0;){const u=i.pop(),l=y.readdirSync(u);for(const n of l){if(s.includes(n))continue;const o=L.join(u,n),c=y.statSync(o);if(c.isDirectory())i.push(o);else{const p=L.resolve(o),m=k.pathToFileURL(p),h=`${++a}`;let f="";if(/\.(js|ts)$/.test(p)){const b=this.readLastLine(o,c,2).match(/sourceMappingURL=(.+)$/m)?.[1]??"";f=b?b.startsWith("data:")?b:k.pathToFileURL(L.join(u,b)).href:""}const w=m.href,G=ee(w);r.push({url:w,scriptLanguage:G,embedderName:m.href,scriptId:h,sourceMapURL:f,hasSourceURL:!!f}),this.scriptMap.addMapping(w,h)}}}return this.scriptIdCounter+=r.length,r}getLocalScriptList(){const e=this.traverseDirToMap(process.cwd()),s=this.traverseDirToMap(d.__dirname);return[...e,...s]}}const j="517.528",_="517.529",re=t=>t.split(";")[0]||"text/plain",ne=P("network",({devtool:t,core:e})=>{const s={},r=new se,i=n=>s[n],a=n=>{s[n.id]=n},u=n=>{s[n.id]=n,n.requestEndTime=n.requestEndTime||Date.now(),t.updateTimestamp();const c=new q(n.responseHeaders).getHeader("content-type")||"text/plain; charset=utf-8",p=/text\/event-stream/.test(c)?"EventSource":/image/.test(c)?"Image":/javascript/.test(c)?"Script":/css/.test(c)?"Stylesheet":/html/.test(c)?"Document":"Other";t.send({method:"Network.responseReceived",params:{requestId:n.id,frameId:j,loaderId:_,timestamp:t.timestamp,type:p,response:{url:n.url,status:n.responseStatusCode,statusText:n.responseStatusCode===200?"OK":"",headers:n.responseHeaders,connectionReused:!1,encodedDataLength:n.responseInfo.encodedDataLength,charset:"utf-8",mimeType:re(c)}}}),t.updateTimestamp(),t.send({method:"Network.dataReceived",params:{requestId:n.id,timestamp:t.timestamp,dataLength:n.responseInfo.dataLength,encodedDataLength:n.responseInfo.encodedDataLength}}),t.updateTimestamp(),t.send({method:"Network.loadingFinished",params:{requestId:n.id,timestamp:t.timestamp,encodedDataLength:n.responseInfo.encodedDataLength}})},l=(n,o)=>{const c=[E.gunzip,E.inflate,E.brotliDecompress];let p=0;const m=()=>{if(p>=c.length){o(n);return}const h=c[p];p+=1,h(n,(f,w)=>{f?m():o(w)})};m()};return g("Network.getResponseBody",({data:n,id:o})=>{const c=i(n.requestId);if(!o||!c){console.error("request is not found");return}const p=new Z(c).decodeBody();t.send({id:o,result:p})}),g("initRequest",({data:n})=>{const o=new d.RequestDetail(n);o.initiator&&o.initiator.stack.callFrames.forEach(c=>{const p=k.pathToFileURL(c.url),m=r.getScriptIdByUrl(p.href)??r.getScriptIdByUrl(c.url);m&&(c.scriptId=m)}),s[o.id]=o}),g("registerRequest",({data:n})=>{const o=new d.RequestDetail(n);s[o.id]=o,o.initiator&&o.initiator.stack.callFrames.forEach(p=>{const m=k.pathToFileURL(p.url),h=r.getScriptIdByUrl(m.href)??r.getScriptIdByUrl(p.url);h&&(p.scriptId=h)}),t.updateTimestamp();const c=new q(o.requestHeaders);return c.getHeader("content-type"),t.send({method:"Network.requestWillBeSent",params:{requestId:o.id,frameId:j,loaderId:_,request:{url:o.url,method:o.method,headers:c.getData(),initialPriority:"High",mixedContentType:"none",...o.requestData?{postData:o.requestData.toString()}:{}},timestamp:t.timestamp,wallTime:o.requestStartTime,initiator:o.initiator,type:o.isWebSocket()?"WebSocket":"Fetch"}})}),g("endRequest",({data:n})=>{const o=new d.RequestDetail(n);u(o)}),g("responseData",({data:n})=>{const{id:o,rawData:c,statusCode:p,headers:m}=n,h=i(o),f=Buffer.from(c);h&&(h.responseInfo.encodedDataLength=f.length,l(f,w=>{h.responseData=w,h.responseInfo.dataLength=w.length,h.responseStatusCode=p,h.responseHeaders=new q(m).getData(),a(h),u(h)}))}),g("eventSourceMessage",({data:n})=>{const{requestId:o,eventName:c,eventId:p,data:m}=n;i(o)&&(t.updateTimestamp(),t.send({method:"Network.eventSourceMessageReceived",params:{requestId:o,timestamp:t.timestamp,eventName:c,eventId:p,data:m}}))}),{getRequest:i,resourceService:r}}),oe=P("websocket",({devtool:t,core:e})=>{const s=e.usePlugin("network");g("Network.webSocketCreated",async({data:{response:r,requestId:i}})=>{if(!i)return;const a=s.getRequest(i);if(!a)return;const u=d.parseRawHeaders(r.rawHeaders);await t.send({method:"Network.webSocketCreated",params:{url:a.url,initiator:a.initiator,requestId:a.id}}),await t.send({method:"Network.webSocketWillSendHandshakeRequest",params:{wallTime:Date.now(),timestamp:t.getTimestamp(),requestId:a.id,request:{headers:a.requestHeaders}}}),await t.send({method:"Network.webSocketHandshakeResponseReceived",params:{requestId:a.id,response:{headers:u,headersText:d.formatHeadersToHeaderText(`HTTP/${r.httpVersion} ${r.statusCode} ${r.statusMessage}\r
`,u),status:r.statusCode,statusText:r.statusMessage,requestHeadersText:d.formatHeadersToHeaderText(`${a.method} ${a.url} HTTP/${r.httpVersion}\r
`,a.requestHeaders),requestHeaders:d.stringifyNestedObj(a.requestHeaders)},timestamp:t.getTimestamp()}})}),g("Network.webSocketFrameSent",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketFrameSent",params:{requestId:r.requestId,response:r.response,timestamp:d.getTimestamp()}})}),g("Network.webSocketFrameReceived",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketFrameReceived",params:{requestId:r.requestId,response:r.response,timestamp:d.getTimestamp()}})}),g("Network.webSocketClosed",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketClosed",params:{requestId:r.requestId,timestamp:d.getTimestamp()}})})}),ie=P("health",({devtool:t})=>{const e=()=>{process.exit(0)};let s=setTimeout(e,1e4);g("healthcheck",()=>{clearTimeout(s),s=setTimeout(e,1e4)})}),ae=t=>{t.loadPlugins([ie,ne,X,oe])},M=J(process.env.NETWORK_OPTIONS||"{}",{}),F=()=>{const t=new V({serverPort:M.serverPort||d.SERVER_PORT,port:M.port||d.PORT,autoOpenDevtool:M.autoOpenDevtool});return ae(t),t};let $=F(),U=0;const ce=5,ue=30*1e3,W=()=>{setTimeout(()=>{if(U++,U>=ce){console.error("Restart limit reached"),R();return}$.close(),$=F()},10+Math.random()*100)};setInterval(()=>{U=0},ue);const R=()=>{process.exit(0)};process.on("exit",R);process.on("SIGINT",R);process.on("SIGTERM",R);process.on("beforeExit",R);process.on("uncaughtException",t=>{console.error("uncaughtException: ",t),W()});process.on("unhandledRejection",t=>{console.error("unhandledRejection: ",t),W()});