UNPKG

node-network-devtools

Version:

Inspecting Node.js's Network with Chrome DevTools

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