UNPKG

node-network-devtools

Version:

Inspecting Node.js's Network with Chrome DevTools

6 lines (5 loc) 12.4 kB
"use strict";var N=t=>{throw TypeError(t)};var H=(t,e,s)=>e.has(t)||N("Cannot "+s);var D=(t,e,s)=>(H(t,e,"read from private field"),s?s.call(t):e.get(t)),C=(t,e,s)=>e.has(t)?N("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),q=(t,e,s,r)=>(H(t,e,"write to private field"),r?r.call(t,s):e.set(t,s),s);const U=require("ws"),M=require("open"),d=require("./common-BlGFHnVB.js"),k=require("url"),x=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 U.Server({port:s});const{server:c}=this;c.on("listening",()=>{d.log(`devtool server is listening on port ${s}`),r&&this.open()}),this.socket=new Promise(p=>{c.on("connection",n=>{i?.(),this.socket.then(o=>{o[0]=n}),d.log("devtool connected"),n.on("message",o=>{const u=JSON.parse(o.toString());this.listeners.forEach(l=>l(null,u))}),n.on("close",()=>{d.log("devtool closed"),a?.()}),n.on("error",o=>{this.listeners.forEach(u=>u(o))}),p([n])})})}async open(){const e=`devtools://devtools/bundled/inspector.html?ws=localhost:${this.port}`;try{if(d.IS_DEV_MODE){d.log(`In dev mode, open chrome devtool manually: ${e}`);return}const s=await M(e,{app:{name:M.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,u=setInterval(async()=>{o>5&&(clearInterval(u),n([]));try{o++,n((await fetch(`http://localhost:${d.REMOTE_DEBUGGER_PORT}/json`)).json()),clearInterval(u)}catch{}},500)}),[i]=r;if(!i)break e;const{id:a,webSocketDebuggerUrl:c}=i,p=new U.WebSocket(c);p.on("open",()=>{const n={id:a,method:"Page.navigate",params:{url:e}};p.send(JSON.stringify(n)),p.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={},q(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 c=this.listeners[a.method];c&&c.forEach(p=>{p({data:a.params,id:a.id})})}),this.server=this.initServer()}loadPlugins(e){q(this,T,e),q(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 U.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 c=this.listeners[a.type];if(!c)break;c.forEach(p=>{p({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}),f=(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");f("Debugger.getScriptSource",({id:i,data:a})=>{if(!i)return;const{scriptId:c}=a,p=s.resourceService.getScriptSource(c);t.send({id:i,method:"Debugger.getScriptSourceResponse",result:{scriptSource:p}})});const r=s.resourceService.getLocalScriptList();Q(()=>{r.forEach(i=>{t.send({method:"Debugger.scriptParsed",params:i})})})});class v{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 v(e.responseHeaders).getHeader("content-type")||"text/plain; charset=utf-8",i=r.match(/charset=([^;]+)/),a=i?i[1]:"utf-8",c=!/text|json|xml/.test(r);return{body:(()=>{if(!(e.responseData===void 0||e.responseData===null))return c?e.responseData.toString("base64"):Buffer.isBuffer(e.responseData)?x.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)?x.decode(Buffer.from(e.responseData.data),a):e.responseData})(),base64Encoded:c}}}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 c=i-a,p=Buffer.alloc(a),n=[];const o=y.openSync(e,"r");for(;n.length<r&&c>=0;)y.readSync(o,p,0,a,c),n=p.toString("utf8").split(` `).concat(n),c-=a,c<0&&(c=0,p=Buffer.alloc(i-c));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 c=i.pop(),p=y.readdirSync(c);for(const n of p){if(s.includes(n))continue;const o=L.join(c,n),u=y.statSync(o);if(u.isDirectory())i.push(o);else{const l=L.resolve(o),m=k.pathToFileURL(l),h=`${++a}`;let g="";if(/\.(js|ts)$/.test(l)){const R=this.readLastLine(o,u,2).match(/sourceMappingURL=(.+)$/m)?.[1]??"";g=R?R.startsWith("data:")?R:k.pathToFileURL(L.join(c,R)).href:""}const w=m.href,G=ee(w);r.push({url:w,scriptLanguage:G,embedderName:m.href,scriptId:h,sourceMapURL:g,hasSourceURL:!!g}),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},c=n=>{n.requestEndTime=n.requestEndTime||Date.now(),t.updateTimestamp();const u=new v(n.responseHeaders).getHeader("content-type")||"text/plain; charset=utf-8",l=/image/.test(u)?"Image":/javascript/.test(u)?"Script":/css/.test(u)?"Stylesheet":/html/.test(u)?"Document":"Other";t.send({method:"Network.responseReceived",params:{requestId:n.id,frameId:j,loaderId:_,timestamp:t.timestamp,type:l,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(u)}}}),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}})},p=(n,o)=>{const u=[E.gunzip,E.inflate,E.brotliDecompress];let l=0;const m=()=>{if(l>=u.length){o(n);return}const h=u[l];l+=1,h(n,(g,w)=>{g?m():o(w)})};m()};return f("Network.getResponseBody",({data:n,id:o})=>{const u=i(n.requestId);if(!o||!u){console.error("request is not found");return}const l=new Z(u).decodeBody();t.send({id:o,result:l})}),f("initRequest",({data:n})=>{const o=new d.RequestDetail(n);o.initiator&&o.initiator.stack.callFrames.forEach(u=>{const l=k.pathToFileURL(u.url),m=r.getScriptIdByUrl(l.href)??r.getScriptIdByUrl(u.url);m&&(u.scriptId=m)}),s[o.id]=o}),f("registerRequest",({data:n})=>{const o=new d.RequestDetail(n);s[o.id]=o,o.initiator&&o.initiator.stack.callFrames.forEach(m=>{const h=k.pathToFileURL(m.url),g=r.getScriptIdByUrl(h.href)??r.getScriptIdByUrl(m.url);g&&(m.scriptId=g)}),t.updateTimestamp();const u=new v(o.requestHeaders),l=u.getHeader("content-type");return t.send({method:"Network.requestWillBeSent",params:{requestId:o.id,frameId:j,loaderId:_,request:{url:o.url,method:o.method,headers:u.getData(),initialPriority:"High",mixedContentType:"none",...o.requestData?{postData:l?.includes("application/json")?JSON.stringify(o.requestData):o.requestData}:{}},timestamp:t.timestamp,wallTime:o.requestStartTime,initiator:o.initiator,type:o.isWebSocket()?"WebSocket":"Fetch"}})}),f("endRequest",({data:n})=>{const o=new d.RequestDetail(n);c(o)}),f("responseData",({data:n})=>{const{id:o,rawData:u,statusCode:l,headers:m}=n,h=i(o),g=Buffer.from(u);h&&(h.responseInfo.encodedDataLength=g.length,p(g,w=>{h.responseData=w,h.responseInfo.dataLength=w.length,h.responseStatusCode=l,h.responseHeaders=new v(m).getData(),a(h),c(h)}))}),{getRequest:i,resourceService:r}}),oe=P("websocket",({devtool:t,core:e})=>{const s=e.usePlugin("network");f("Network.webSocketCreated",async({data:{response:r,requestId:i}})=>{if(!i)return;const a=s.getRequest(i);if(!a)return;const c=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:c,headersText:d.formatHeadersToHeaderText(`HTTP/${r.httpVersion} ${r.statusCode} ${r.statusMessage}\r `,c),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()}})}),f("Network.webSocketFrameSent",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketFrameSent",params:{requestId:r.requestId,response:r.response,timestamp:d.getTimestamp()}})}),f("Network.webSocketFrameReceived",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketFrameReceived",params:{requestId:r.requestId,response:r.response,timestamp:d.getTimestamp()}})}),f("Network.webSocketClosed",async({data:r})=>{r.requestId&&await t.send({method:"Network.webSocketClosed",params:{requestId:r.requestId,timestamp:d.getTimestamp()}})})}),ie=P("health",()=>{const t=()=>{process.exit(0)};let e=setTimeout(t,5e3);f("healthcheck",()=>{clearTimeout(e),e=setTimeout(t,5e3)})}),ae=t=>{t.loadPlugins([ne,X,oe,ie])},O=J(process.env.NETWORK_OPTIONS||"{}",{}),F=()=>{const t=new V({serverPort:O.serverPort||d.SERVER_PORT,port:O.port||d.PORT,autoOpenDevtool:O.autoOpenDevtool});return ae(t),t};let $=F(),B=0;const ce=5,ue=30*1e3,W=()=>{setTimeout(()=>{if(B++,B>=ce){console.error("Restart limit reached"),b();return}$.close(),$=F()},10+Math.random()*100)};setInterval(()=>{B=0},ue);const b=()=>{process.exit(0)};process.on("exit",b);process.on("SIGINT",b);process.on("SIGTERM",b);process.on("beforeExit",b);process.on("uncaughtException",t=>{console.error("uncaughtException: ",t),W()});process.on("unhandledRejection",t=>{console.error("unhandledRejection: ",t),W()});