UNPKG

pcap-element

Version:

A reusable Web Component for PCAP file preview

2 lines (1 loc) 36.6 kB
class t{static formatBytes(t){const e=["B","KB","MB","GB"];if(t<0)return`0.0 ${e[0]}`;let n=t,s=0;for(;n>=1024&&s<e.length-1;)n/=1024,s++;return`${n.toFixed(1)} ${e[s]}`}static formatMacAddress(t){if(!(t instanceof Uint8Array))throw new TypeError("Input must be a Uint8Array");if(6!==t.length)throw new Error("MAC address must be exactly 6 bytes long");return Array.from(t).map(t=>t.toString(16).padStart(2,"0")).join(":").toUpperCase()}static formatIpAddress(t){if(!(t instanceof Uint8Array))throw new TypeError("Input must be a Uint8Array");if(4!==t.length&&16!==t.length)throw new RangeError("IP address must be either 4 bytes (IPv4) or 16 bytes (IPv6)");return Array.from(t).join(".")}static arrayToHexString(t){return Array.from(t).map(t=>t.toString(16).padStart(2,"0")).join(" ")}static readUint32(t,e){if(e<0||e+3>=t.length)throw new RangeError("Offset is out of bounds.");return new DataView(t.buffer,t.byteOffset,t.byteLength).getUint32(e,!0)}}const e=["FIN","SYN","RST","PSH","ACK","URG"];function n(t,e){return Object.entries(t).sort(([,t],[,e])=>e-t).slice(0,e).map(([t,e])=>({address:t,count:e}))}class s{static parsePcapFile(e){const s=new Uint8Array(e);if(s.length<this.PCAP_HEADER_SIZE)throw new Error("PCAP文件无效:文件头过小");this.validatePcapHeader(s);const a=[];let r=this.PCAP_HEADER_SIZE;for(;r<=s.length-this.PACKET_HEADER_SIZE;){const t=this.parsePacket(s,r);if(!t)break;a.push(t);const e=t.length;if(r+=this.PACKET_HEADER_SIZE+e,0===e)break}const i=function(t){if(!t.length)return{packets:t,summary:{totalPackets:0,totalBytes:0,protocols:{},topSources:[],topDestinations:[]},fullHex:""};const e={},s={},a={};let r=0;t.forEach(t=>{const n=t.protocol||"Unknown",i=t.source||"Unknown",o=t.destination||"Unknown";e[n]=(e[n]||0)+1,s[i]=(s[i]||0)+1,a[o]=(a[o]||0)+1,r+=t.length});const i=n(s,3),o=n(a,3);return{packets:t,summary:{totalPackets:t.length,totalBytes:r,protocols:e,topSources:i,topDestinations:o},fullHex:""}}(a);return{...i,fullHex:t.arrayToHexString(s)}}static validatePcapHeader(t){const e=new DataView(t.buffer,t.byteOffset,4),n=e.getUint32(0,!1),s=e.getUint32(0,!0),a=[2712847316,3569595041,2712812621,1295823521];if(!a.includes(n)&&!a.includes(s))throw new Error(`PCAP文件无效:magic number不正确(BE: ${n.toString(16)}, LE: ${s.toString(16)})`)}static parsePacket(e,n){if(n<0||n>e.length)return null;const s=t.readUint32(e,n),a=t.readUint32(e,n+4),r=t.readUint32(e,n+8),i=n+this.PACKET_HEADER_SIZE;if(0===r||i>e.length||r>e.length||i+r>e.length||i+r>Number.MAX_SAFE_INTEGER)return null;const o=e.slice(i,i+r);return this.parseEthernetPacket(o,s,a)}static parseEthernetPacket(e,n,s){if(e.length<this.ETHERNET_HEADER_SIZE)return null;const a=Math.min(Math.max(s,0),999999),r=t.formatMacAddress(e.slice(6,12)),i=t.formatMacAddress(e.slice(0,6)),o=e[12]<<8|e[13],l="0x"+o.toString(16).padStart(4,"0");let c,d,p,h,g,u,m,f,x,y,v="Unknown",w=i,b=r,k=[];if(33024===o||34984===o)return null;if(2048===o){const t=this.ETHERNET_HEADER_SIZE;if(e.length>=t+this.IP_HEADER_MIN_SIZE){const n=e.subarray(t),s=n[9];d=n[8],p=n[4]<<8|n[5],h="0x"+(n[10]<<8|n[11]).toString(16).padStart(4,"0");const a=this.parseIpPacket(n);if(a&&(v=a.protocol,w=a.source,b=a.destination,c=a.port,k=a.flags??[]),6===s&&n.length>=this.IP_HEADER_MIN_SIZE+this.TCP_HEADER_SIZE){const t=n.slice(this.IP_HEADER_MIN_SIZE);g=(t[4]<<24|t[5]<<16|t[6]<<8|t[7])>>>0,u=(t[8]<<24|t[9]<<16|t[10]<<8|t[11])>>>0,m=t[14]<<8|t[15],f="0x"+(t[16]<<8|t[17]).toString(16).padStart(4,"0")}else if(17===s&&n.length>=this.IP_HEADER_MIN_SIZE+8){const t=n.slice(this.IP_HEADER_MIN_SIZE);x=t[4]<<8|t[5],y="0x"+(t[6]<<8|t[7]).toString(16).padStart(4,"0")}}}else 2054===o?v="ARP":34525===o&&(v="IPv6");return{timestamp:n+a/1e6,length:e.length,data:t.arrayToHexString(e),source:w,destination:b,protocol:v,port:c,flags:k,srcMac:r,dstMac:i,etherType:l,ipTtl:d,ipId:p,ipChecksum:h,tcpSeq:g,tcpAck:u,tcpWin:m,tcpChecksum:f,udpLen:x,udpChecksum:y}}static parseIpPacket(e){if(!e||e.length<20)return null;if(4!==(e[0]>>4&15))return null;const n=e[9],s=t.formatIpAddress(e.slice(12,16)),a=t.formatIpAddress(e.slice(16,20));let r,i="Unknown",o=[];switch(n){case 1:i="ICMP";break;case 6:i="TCP";const t=this.IP_HEADER_MIN_SIZE,s=t+this.TCP_HEADER_SIZE;if(e.length>=s){const n=e.slice(t),s=n[0]<<8|n[1],a=n[2]<<8|n[3];r=s,n.length>13&&(o=this.parseTcpFlags(n[13])),25===a&&(i="SMTP")}break;case 17:i="UDP";const a=this.IP_HEADER_MIN_SIZE,l=a+8;if(e.length>=l){const t=e.slice(a);r=t[0]<<8|t[1]}break;default:i=`IP(${n})`}return{protocol:i,source:s,destination:a,port:r,flags:o}}static parseTcpFlags(t){if("number"!=typeof t||!Number.isFinite(t)||t<0||t>255)return[];const n=63&t,s=[];for(let t=0;t<e.length;t++)n&1<<t&&s.push(e[t]);return s}}s.PCAP_HEADER_SIZE=24,s.PACKET_HEADER_SIZE=16,s.ETHERNET_HEADER_SIZE=14,s.IP_HEADER_MIN_SIZE=20,s.TCP_HEADER_SIZE=20;var a={"zh-cn":{loading:"加载PCAP数据中...",errorNoSrc:"未提供src属性",errorLoadFailed:"加载PCAP文件失败",summary:"PCAP摘要",totalPackets:"总数据包数",totalBytes:"总字节数",protocolTypes:"协议种类",protocolDistribution:"协议分布",topSources:"活跃源地址",topDestinations:"活跃目的地址",packetList:"数据包列表",packet:"数据包",sourceAddress:"源地址",destinationAddress:"目的地址",length:"长度",protocol:"协议",bytes:"字节",showHex:"显示16进制",showParsed:"显示解析",formatToggle:"格式切换",hexViewTitle:"原始16进制视图",noPackets:"无数据包",srcMac:"源MAC地址",dstMac:"目的MAC地址",etherType:"以太网类型",ipTtl:"IP TTL",ipId:"IP标识符",ipChecksum:"IP校验和",tcpSeq:"TCP序列号",tcpAck:"TCP确认号",tcpWin:"TCP窗口",tcpChecksum:"TCP校验和",udpLen:"UDP长度",udpChecksum:"UDP校验和",port:"端口号",flags:"标志位",fullscreen:"全屏",exitFullscreen:"恢复",buttonLoading:"切换中..."},"en-us":{loading:"Loading PCAP data...",errorNoSrc:"No src attribute provided",errorLoadFailed:"Failed to load PCAP file",summary:"PCAP Summary",totalPackets:"Total Packets",totalBytes:"Total Bytes",protocolTypes:"Protocol Types",protocolDistribution:"Protocol Distribution",topSources:"Top Sources",topDestinations:"Top Destinations",packetList:"Packet List",packet:"Packet",sourceAddress:"Source",destinationAddress:"Destination",length:"Length",protocol:"Protocol",bytes:"bytes",showHex:"Show Hex",showParsed:"Show Parsed",formatToggle:"Format Toggle",hexViewTitle:"Raw Hex View",noPackets:"No packets",srcMac:"Src MAC",dstMac:"Dst MAC",etherType:"EtherType",ipTtl:"IP TTL",ipId:"IP ID",ipChecksum:"IP Checksum",tcpSeq:"TCP Seq",tcpAck:"TCP Ack",tcpWin:"TCP Win",tcpChecksum:"TCP Checksum",udpLen:"UDP Len",udpChecksum:"UDP Checksum",port:"Port",flags:"Flags",fullscreen:"Fullscreen",exitFullscreen:"Exit Fullscreen",buttonLoading:"Switching..."}};class r{constructor(){this.languages=null,this.currentLang="zh-cn"}static getInstance(){return r.instance||(r.instance=new r),r.instance}async loadLanguages(){return this.languages||(this.languages=a),this.languages}setLanguage(t){this.currentLang="en-us"===t?"en-us":"zh-cn"}getCurrentLanguage(){return this.currentLang}async getText(t){return(await this.loadLanguages())[this.currentLang][t]}async getAllTexts(){return(await this.loadLanguages())[this.currentLang]}}class i{constructor(){this.i18nManager=r.getInstance()}escapeHtml(t){return t.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t]||t))}async renderPcapData(t,e="parsed",n=!0){const s=await this.i18nManager.getAllTexts(),a=this.renderSummary(t,s);if("hex"===e)return a+this.renderFullHex(t,s,n);return a+this.renderPackets(t,s,e)}renderSummary(t,e){const n=t=>t&&0!==t.length?t.slice(0,3).map(t=>`${this.escapeHtml(t.address)} (${t.count})`).join(", "):"",s=t.summary.totalPackets??0,a=this.formatBytes(t.summary.totalBytes??0),r=(i=t.summary.protocols)?Object.keys(i).length:0;var i;const o=Object.entries(t.summary.protocols??{}).map(([t,e])=>`${this.escapeHtml(t)}: ${e}`).join(", "),l=n(t.summary.topSources),c=n(t.summary.topDestinations);return`\n <div class="summary">\n <h3>${this.escapeHtml(e.summary)}</h3>\n <div class="summary-grid">\n <div class="summary-item">\n <div class="label">${this.escapeHtml(e.totalPackets)}</div>\n <div class="value">${s}</div>\n </div>\n <div class="summary-item">\n <div class="label">${this.escapeHtml(e.totalBytes)}</div>\n <div class="value">${a}</div>\n </div>\n <div class="summary-item">\n <div class="label">${this.escapeHtml(e.protocolTypes)}</div>\n <div class="value">${r}</div>\n </div>\n </div>\n <div class="summary-details">\n <span class="detail-item"><strong>${this.escapeHtml(e.protocolDistribution)}:</strong> ${o}</span>\n ${l?`<span class="detail-item"><strong>${this.escapeHtml(e.topSources)}:</strong> ${l}</span>`:""}\n ${c?`<span class="detail-item"><strong>${this.escapeHtml(e.topDestinations)}:</strong> ${c}</span>`:""}\n </div>\n </div>\n `}renderPackets(t,e,n="parsed"){const s=e.packet;return t.packets&&0!==t.packets.length?`\n <div class="packets">\n <h3>${this.escapeHtml(e.packetList)} (${t.packets.length})</h3>\n ${t.packets.map((a,r)=>{const i=a.protocol.toLowerCase(),o=this.formatTimestamp(a.timestamp),l=r%2==0,c="hex"===n?this.renderHexPacket(a,e):this.renderParsedPacket(a,e);return`\n <div class="packet ${l?"even":"odd"}">\n <div class="packet-header">\n <div class="packet-title">\n <span><strong>${s} ${r+1}</strong></span>\n <span class="protocol-badge ${i}">${a.protocol}</span>\n </div>\n <span>${o}</span>\n </div>\n ${c}\n ${r<t.packets.length-1?'<div class="packet-divider"></div>':""}\n </div>\n `}).join("")}\n </div>\n `:`<div class="packets empty">${this.escapeHtml(e.packetList)}: ${this.escapeHtml(e.noPackets)}</div>`}renderParsedPacket(t,e){let n=`\n <div class="packet-info">\n <div><strong>${e.sourceAddress}:</strong> ${this.escapeHtml(t.source)}</div>\n <div><strong>${e.destinationAddress}:</strong> ${this.escapeHtml(t.destination)}</div>\n <div><strong>${e.length}:</strong> ${t.length} ${e.bytes}</div>\n <div><strong>${e.protocol}:</strong> ${t.protocol}</div>\n ${void 0!==t.port?`<div><strong>${e.port}:</strong> ${t.port}</div>`:""}\n ${t.flags&&t.flags.length?`<div><strong>${e.flags}:</strong> ${t.flags.join(", ")}</div>`:""}\n </div>\n `;const s=[];return t.srcMac&&s.push(`<div><strong>${e.srcMac}:</strong> ${t.srcMac}</div>`),t.dstMac&&s.push(`<div><strong>${e.dstMac}:</strong> ${t.dstMac}</div>`),t.etherType&&s.push(`<div><strong>${e.etherType}:</strong> ${t.etherType}</div>`),void 0!==t.ipTtl&&s.push(`<div><strong>${e.ipTtl}:</strong> ${t.ipTtl}</div>`),void 0!==t.ipId&&s.push(`<div><strong>${e.ipId}:</strong> ${t.ipId}</div>`),t.ipChecksum&&s.push(`<div><strong>${e.ipChecksum}:</strong> ${t.ipChecksum}</div>`),void 0!==t.tcpSeq&&s.push(`<div><strong>${e.tcpSeq}:</strong> ${t.tcpSeq}</div>`),void 0!==t.tcpAck&&s.push(`<div><strong>${e.tcpAck}:</strong> ${t.tcpAck}</div>`),void 0!==t.tcpWin&&s.push(`<div><strong>${e.tcpWin}:</strong> ${t.tcpWin}</div>`),t.tcpChecksum&&s.push(`<div><strong>${e.tcpChecksum}:</strong> ${t.tcpChecksum}</div>`),void 0!==t.udpLen&&s.push(`<div><strong>${e.udpLen}:</strong> ${t.udpLen}</div>`),t.udpChecksum&&s.push(`<div><strong>${e.udpChecksum}:</strong> ${t.udpChecksum}</div>`),s.length&&(n+=`<div class="packet-info">${s.join("")}</div>`),n+=`<div class="packet-data">${this.escapeHtml(t.data)}</div>`,n}renderHexPacket(t,e){const n=this.formatHexData(t.data);return`\n <div class="packet-info">\n <div><strong>${e.length}:</strong> ${t.length} ${e.bytes}</div>\n </div>\n <div class="hex-data">\n <pre>${n}</pre>\n </div>\n `}renderFullHex(t,e,n=!0){if(!t.fullHex)return"";const s=t.fullHex.split(" ").filter(t=>t.trim()),a=Math.ceil(s.length/16),r=Math.floor(32760/22);if(n&&a>6e3){const t=[];for(let e=0;e<s.length;e+=16)t.push(s.slice(e,e+16));const n=t.slice(0,2500),a=t.slice(-2500),i=t.slice(2500,-2500);let o=`<pre>${this.formatHexLinesToHtml(n,0)}</pre>`,l=0,c=2500;for(let t=0;t<i.length;t+=r){const e=i.slice(t,t+r),n=`hex-canvas-${Math.random().toString(36).slice(2)}-${l}`;"undefined"!=typeof window&&(window.__pcapHexCanvasData__=window.__pcapHexCanvasData__||{},window.__pcapHexCanvasData__[n]={lines:e,canvasWidth:0,canvasHeight:0,lineStart:c}),o+=`<canvas id="${n}"></canvas>`,l++,c+=e.length}return o+=`<pre>${this.formatHexLinesToHtml(a,t.length-2500)}</pre>`,`\n <div class="packets">\n <h3>${this.escapeHtml(e.hexViewTitle)}</h3>\n <div class="hex-data" style="overflow-x:auto;">\n ${o}\n </div>\n </div>\n `}if(n&&a>5e3){const t=[];for(let e=0;e<s.length;e+=16)t.push(s.slice(e,e+16));let n="",a=0,i=0;for(let e=0;e<t.length;e+=r){const s=t.slice(e,e+r),o=`hex-canvas-${Math.random().toString(36).slice(2)}-${a}`;"undefined"!=typeof window&&(window.__pcapHexCanvasData__=window.__pcapHexCanvasData__||{},window.__pcapHexCanvasData__[o]={lines:s,canvasWidth:0,canvasHeight:0,lineStart:i}),n+=`<canvas id="${o}"></canvas>`,a++,i+=s.length}return`\n <div class="packets">\n <h3>${this.escapeHtml(e.hexViewTitle)}</h3>\n <div class="hex-data" style="overflow-x:auto;">\n ${n}\n </div>\n </div>\n `}{const n=this.formatHexData(t.fullHex);return`\n <div class="packets">\n <h3>${this.escapeHtml(e.hexViewTitle)}</h3>\n <div class="hex-data">\n <pre>${n}</pre>\n </div>\n </div>\n `}}formatHexLinesToHtml(t,e){return t.map((t,n)=>{const s=(n+e).toString(16).padStart(8,"0");let a="";for(let e=0;e<t.length;e++)e>0&&e%8==0&&(a+=" "),a+=t[e].padStart(2,"0")+" ";a+=" ".repeat(16-t.length);let r=t.map(t=>{const e=parseInt(t,16),n=e>=32&&e<=126?String.fromCharCode(e):".";return this.escapeHtml(n)}).join("");return t.length<16&&(r+=" ".repeat(16-t.length)),`<div class="hex-line"><span class="hex-offset">${s}:</span><span class="hex-bytes">${a}</span><span class="hex-ascii">| ${r} |</span></div>`}).join("")}formatHexDataCanvasChunk(t,e){const n=22*t.length+10;return{html:`<canvas id="${e}" width="638" height="${n}" style="display:block;"></canvas>`,canvasWidth:638,canvasHeight:n}}formatHexData(t){const e=t.split(" ").filter(t=>t.trim()),n=[];for(let t=0;t<e.length;t+=16){const s=e.slice(t,t+16),a=t.toString(16).padStart(8,"0");let r="";for(let t=0;t<s.length;t++)t>0&&t%8==0&&(r+=" "),r+=s[t].padStart(2,"0")+" ";r+=" ".repeat(16-s.length);let i=s.map(t=>{const e=parseInt(t,16),n=e>=32&&e<=126?String.fromCharCode(e):".";return this.escapeHtml(n)}).join("");s.length<16&&(i+=" ".repeat(16-s.length)),n.push(`<div class="hex-line">\n <span class="hex-offset">${a}:</span>\n <span class="hex-bytes">${r}</span>\n <span class="hex-ascii">| ${i} |</span>\n </div>`)}return n.join("")}formatBytes(e){return t.formatBytes(e)}formatTimestamp(t){const e=new Date(1e3*t),n=(t,e=2)=>t.toString().padStart(e,"0");return`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())} ${n(e.getHours())}:${n(e.getMinutes())}:${n(e.getSeconds())}.${n(e.getMilliseconds(),3)}`}}class o{static async draw(t,e,n=0){const s=window,a=document;s.fonts&&s.fonts.ready?await s.fonts.ready:a.fonts&&a.fonts.ready&&await a.fonts.ready;const r="JetBrains Mono, Consolas, Monaco, Courier New, monospace",i=t.parentElement,o=window.devicePixelRatio||1;!i||i.clientWidth;const l=document.createElement("canvas").getContext("2d");let c=24,d=16,p=200,h=16,g=80;l&&(l.font=`12px ${r}`,l.measureText("0").width,c=l.measureText("00 ").width,d=l.measureText(" ").width,g=l.measureText("00000000:").width+8,p=l.measureText("| 0000000000000000 | ").width,h=l.measureText(" ").width);const u=g+16*c+d+h+p,m=28*e.length+10;t.width=u*o,t.height=m*o,t.style.width=u+"px",t.style.height=m+"px";const f=t.getContext("2d");if(f){f.setTransform(1,0,0,1,0,0),f.clearRect(0,0,t.width,t.height),f.save(),f.scale(o,o),f.font=`12px ${r}`,f.textBaseline="middle",f.fillStyle="#f3f4f6",f.fillRect(0,0,u,m);for(let t=0;t<e.length;t++){const s=t+n,a=8+28*t+14;s%2==1&&(f.fillStyle="#f8fafc",f.fillRect(0,8+28*t,u,28)),f.font=`12px ${r}`,f.fillStyle="#9ca3af",f.textAlign="start";const i=s.toString(16).padStart(8,"0")+":";f.fillText(i,2,a);let o=g+2;f.textAlign="start";for(let n=0;n<e[t].length;n++)n>0&&n%8==0&&(o+=d),f.fillStyle="#23272e",f.fillText(e[t][n].padStart(2,"0"),o,a),o+=c;let l="";for(let n=0;n<e[t].length;n++){const s=parseInt(e[t][n],16);l+=s>=32&&s<=126?String.fromCharCode(s):"."}l=l.padEnd(16," "),f.fillStyle="#6b7280",f.textAlign="start";const p=g+16*c+d+h+2;f.fillText("| "+l+" | ",p,a)}f.restore(),t._hexLines=e,t._hexLineStart=n}}}class l extends HTMLElement{constructor(){super(),this.displayMode="parsed",this.cachedPcapData=null,this.stylesInjected=!1,this.fullscreen=!1,this.showFullscreenBtn=!1,this._resizeHandler=null,this.shadow=this.attachShadow({mode:"open"}),this.loadingElement=document.createElement("div"),this.contentElement=document.createElement("div"),this.errorElement=document.createElement("div"),this.toggleButton=document.createElement("button"),this.i18nManager=r.getInstance(),this.renderer=new i,this.setupStyles(),this.setupElements().catch(console.error)}static get observedAttributes(){return["src","lang","enableHexToggle","showfullscreenbtn","useCanvas"]}get useCanvas(){return"true"===this.getAttribute("useCanvas")}get showFullscreen(){return this.fullscreen}set showFullscreen(t){this.fullscreen=t,this.updateFullscreenState(),this.updateFullscreenButton()}get showFullscreenBtnProp(){return this.showFullscreenBtn}set showFullscreenBtnProp(t){this.showFullscreenBtn=t,this.updateFullscreenButton()}shouldShowToggleButton(){const t=this.hasAttribute("enableHexToggle")&&"false"!==this.getAttribute("enableHexToggle"),e=this.hasAttribute("show-hex")&&"false"!==this.getAttribute("show-hex");return t||e}setDisplayMode(t){this.displayMode=t}async renderCurrentMode(t){if("hex"===this.displayMode){const e=await this.renderer.renderPcapData(t,"hex",this.useCanvas);if(this.contentElement.innerHTML=`<div class="hex-content">${e}</div>`,this.useCanvas){this.contentElement.querySelectorAll("canvas").forEach(t=>{if(t.id&&window.__pcapHexCanvasData__&&window.__pcapHexCanvasData__[t.id]){const{lines:e,lineStart:n}=window.__pcapHexCanvasData__[t.id];o.draw(t,e,n||0),delete window.__pcapHexCanvasData__[t.id]}}),this._resizeHandler||(this._resizeHandler=()=>{this.contentElement.querySelectorAll("canvas").forEach(t=>{const e=t._hexLines,n=t._hexLineStart||0;e&&o.draw(t,e,n)})},window.addEventListener("resize",this._resizeHandler))}}else{const e=await this.renderer.renderPcapData(t,"parsed");this.contentElement.innerHTML=`<div class="parsed-content">${e}</div>`,this._resizeHandler&&(window.removeEventListener("resize",this._resizeHandler),this._resizeHandler=null)}this.updateContentDisplayMode()}async connectedCallback(){this.updateLanguage(),this.setDisplayMode("parsed"),await this.loadPcapData()}async attributeChangedCallback(t,e,n){"src"===t&&e!==n?await this.loadPcapData():"lang"===t&&e!==n?(this.updateLanguage(),await this.loadPcapData()):"enableHexToggle"===t&&e!==n?(this.setDisplayMode("parsed"),this.cachedPcapData&&await this.renderPcapData(this.cachedPcapData)):"showfullscreenbtn"===t&&e!==n?(this.showFullscreenBtn=null!==n&&"false"!==n,this.updateFullscreenButton()):"useCanvas"===t&&e!==n&&this.cachedPcapData&&await this.renderPcapData(this.cachedPcapData)}updateLanguage(){const t=this.getAttribute("lang")||"zh-cn";this.i18nManager.setLanguage(t)}updateDisplayMode(){const t=this.getAttribute("show-hex");this.displayMode="true"===t?"hex":"parsed"}async getText(t){return await this.i18nManager.getText(t)}setupStyles(){if(this.stylesInjected)return;this.stylesInjected=!0;const t=document.createElement("style");t.textContent="\n :host {\n --mono-font: 'JetBrains Mono', 'Consolas', 'Monaco', 'Courier New', monospace;\n --border-radius: 4px;\n --border-color: #e5e7eb;\n --bg-light: #fafafa;\n --bg-white: #fff;\n --bg-gray: #f3f4f6;\n --bg-gray-dark: #f8f9fa;\n --color-dark: #1f2937;\n --color-gray: #6b7280;\n --color-light: #374151;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-size-large: 15px;\n display: block;\n font-family: var(--mono-font);\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n padding: 12px;\n margin: 12px 0;\n background: var(--bg-light);\n font-size: var(--font-size-base);\n line-height: 1.4;\n position: relative;\n }\n .loading {\n text-align: center;\n padding: 16px;\n color: #666;\n font-size: var(--font-size-base);\n }\n .error {\n color: #d32f2f;\n background: #ffebee;\n padding: 8px 10px;\n border-radius: 3px;\n margin: 6px 0;\n font-size: 11px;\n border-left: 3px solid #d32f2f;\n }\n .format-toggle {\n position: absolute;\n top: 6px;\n right: 44px;\n background: #3b82f6;\n color: white;\n border: none;\n border-radius: var(--border-radius);\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.2s ease;\n z-index: 10;\n font-family: inherit;\n width: auto;\n height: 28px;\n vertical-align: middle;\n }\n .fullscreen-toggle {\n position: absolute;\n top: 6px;\n right: 8px;\n z-index: 20;\n background: #eee;\n border: none;\n border-radius: 4px;\n margin-left: 6px;\n cursor: pointer;\n vertical-align: middle;\n width: 28px;\n height: 28px;\n display: inline-block;\n }\n .format-toggle:hover { background: #2563eb; }\n .format-toggle:active { background: #1d4ed8; }\n .format-toggle.loading {\n opacity: 0.6;\n pointer-events: none;\n cursor: wait;\n }\n .summary {\n background: var(--bg-gray);\n padding: 10px;\n border-radius: var(--border-radius);\n margin-bottom: 12px;\n border: 1px solid var(--border-color);\n }\n .summary h3 {\n margin: 0 0 6px 0;\n color: var(--color-light);\n font-size: 13px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .packets h3 {\n margin: 6px 6px 6px 18px;\n color: var(--color-light);\n font-size: 13px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .summary-grid {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 8px;\n }\n .card, .summary-item, .detail-item, .packet-info .info-item {\n background: var(--bg-white);\n border-radius: var(--border-radius);\n border: 1px solid var(--border-color);\n padding: 6px 8px;\n text-align: center;\n min-width: 0;\n }\n .summary-item { flex: 1; }\n .label-small, .summary-item .label, .packet-info .info-label {\n font-size: var(--font-size-small);\n color: var(--color-gray);\n text-transform: uppercase;\n font-weight: 500;\n letter-spacing: 0.3px;\n }\n .value-strong, .summary-item .value, .packet-info .info-value, .detail-item strong {\n font-size: var(--font-size-large);\n font-weight: 600;\n color: var(--color-dark);\n margin-top: 2px;\n }\n .summary-details {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--border-color);\n font-size: 11px;\n line-height: 1.4;\n }\n .detail-item {\n color: var(--color-light);\n background: var(--bg-gray-dark);\n white-space: nowrap;\n }\n .packets {\n border: 1px solid var(--border-color);\n border-radius: var(--border-radius);\n background: var(--bg-white);\n }\n .packet.even { background: var(--bg-white); }\n .packet.odd { background: #f8fafc; }\n .packet {\n margin: 0;\n padding: 12px 16px;\n border: none;\n border-radius: 0;\n box-shadow: none;\n transition: background-color 0.2s ease;\n position: relative;\n }\n .packet:hover { background-color: var(--bg-gray) !important; }\n .packet-divider {\n height: 1px;\n background: var(--border-color);\n margin: 0;\n opacity: 0.6;\n }\n .packet-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--bg-gray);\n font-size: 11px;\n }\n .packet-title {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .packet-info {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px;\n margin-bottom: 6px;\n font-size: var(--font-size-small);\n }\n .packet-info .info-item {\n display: flex;\n justify-content: space-between;\n padding: 2px 4px;\n background: var(--bg-gray);\n border-radius: 2px;\n }\n .packet-data {\n background: var(--bg-gray-dark);\n padding: 8px;\n border-radius: 3px;\n font-family: var(--mono-font);\n font-size: var(--font-size-small);\n line-height: 1.3;\n max-height: 200px;\n overflow-y: auto;\n word-break: break-all;\n border: 1px solid #e9ecef;\n color: var(--color-light);\n }\n .hex-data {\n background: var(--bg-gray-dark);\n padding: 14px 12px 14px 18px;\n border-radius: var(--border-radius);\n font-family: var(--mono-font);\n font-size: var(--font-size-base);\n line-height: 1.6;\n font-weight: 500;\n word-break: break-all;\n border: 1px solid #e9ecef;\n color: #23272e;\n overflow-x: auto;\n }\n .hex-data pre {\n margin: 0;\n white-space: pre;\n word-break: break-all;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n font-weight: inherit;\n }\n .hex-data pre .hex-line {\n display: flex;\n align-items: center;\n padding: 0 2px;\n min-height: 1.6em;\n }\n .hex-data pre .hex-line:nth-child(even) {\n background: var(--bg-gray);\n }\n .hex-data pre .hex-offset {\n color: #9ca3af;\n min-width: 48px;\n display: inline-block;\n text-align: right;\n margin-right: 10px;\n font-weight: 400;\n }\n .hex-data pre .hex-bytes {\n color: #23272e;\n width: 330px;\n display: inline-block;\n font-variant-ligatures: none;\n }\n .hex-data pre .hex-bytes span {\n display: inline-block;\n min-width: 22px;\n text-align: center;\n }\n .hex-data pre .hex-ascii {\n color: var(--color-gray);\n margin: 6px 6px 6px 18px;\n font-weight: 400;\n display: inline-block;\n white-space: pre;\n }\n .hex-data pre .hex-sep {\n color: var(--border-color);\n }\n .protocol-badge {\n display: inline-block;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n text-transform: uppercase;\n color: white;\n background: var(--color-gray);\n }\n .protocol-badge.smtp { background: #059669; }\n .protocol-badge.tcp { background: #2563eb; }\n .protocol-badge.udp { background: #7c3aed; }\n .protocol-badge.icmp { background: #dc2626; }\n .protocol-badge.arp { background: #ea580c; }\n .protocol-badge.http { background: #059669; }\n .protocol-badge.https { background: #059669; }\n .content, .parsed-content, .hex-content {\n position: static;\n }\n .fullscreen {\n position: fixed !important;\n top: 0; left: 0; right: 6px; bottom: 0;\n width: auto !important;\n height: 100vh !important;\n z-index: 9999 !important;\n background: #fff !important;\n margin: 0 !important;\n border-radius: 0 !important;\n box-shadow: 0 0 0 9999px rgba(0,0,0,0.12);\n overflow: auto !important;\n overflow-y: auto !important;\n }\n :host(.fullscreen) {\n position: fixed !important;\n top: 0; left: 0; right: 6px; bottom: 0;\n width: auto !important;\n height: 100vh !important;\n z-index: 9999 !important;\n background: #fff !important;\n margin: 0 !important;\n border-radius: 0 !important;\n box-shadow: 0 0 0 9999px rgba(0,0,0,0.12);\n overflow: auto !important;\n overflow-y: auto !important;\n }\n @media (max-width: 768px) {\n :host {\n padding: 8px;\n font-size: 11px;\n }\n .summary-grid { flex-direction: column; gap: 6px; }\n .packet-info { grid-template-columns: 1fr; gap: 4px; }\n .packet-header { flex-direction: column; align-items: flex-start; gap: 4px; }\n }\n ",this.shadow.appendChild(t)}async setupElements(){this.setupLoadingElement(),this.setupErrorElement(),this.setupContentElement(),await this.setupToggleButton(),await this.setupFullscreenButton(),this.appendElementsToShadowRoot(),this.initContentDisplay()}setupLoadingElement(){this.loadingElement.className="loading"}setupErrorElement(){this.errorElement.className="error",this.errorElement.style.display="none"}setupContentElement(){this.contentElement.className="content"}async setupToggleButton(){this.toggleButton.className="format-toggle",this.toggleButton.style.display="none",this.toggleButton.addEventListener("click",()=>this.toggleDisplayMode()),this.toggleButton.textContent=await this.getText("loading")}async setupFullscreenButton(){this.fullscreenButton=document.createElement("button"),this.fullscreenButton.className="fullscreen-toggle";const t=await this.i18nManager.getAllTexts();this.fullscreenButton.title=t?this.fullscreen?t.exitFullscreen:t.fullscreen:"",this.updateFullscreenButtonIcon(),this.fullscreenButton.style.display=this.showFullscreenBtn?"inline-block":"none",this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen())}appendElementsToShadowRoot(){this.shadow.appendChild(this.loadingElement),this.shadow.appendChild(this.errorElement),this.shadow.appendChild(this.contentElement),this.shadow.appendChild(this.toggleButton),this.shadow.appendChild(this.fullscreenButton)}initContentDisplay(){this.updateContentDisplayMode(),this.updateFullscreenButton()}async toggleDisplayMode(){if(!this.shouldShowToggleButton())return;this.toggleButton.disabled=!0,this.toggleButton.classList.add("loading");let t="";try{t=await this.getText("buttonLoading")}catch{t=await this.getText("loading")}this.toggleButton.innerHTML='<span class="spinner" style="display:inline-block;vertical-align:middle;margin-right:6px;width:16px;height:16px;"><svg width="16" height="16" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="none" stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-dasharray="31.4 31.4" transform="rotate(-90 25 25)"><animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.8s" repeatCount="indefinite"/></circle></svg></span>'+t,this.toggleButton.style.background="#a5b4fc",this.toggleButton.style.opacity="0.7",this.setDisplayMode("parsed"===this.displayMode?"hex":"parsed"),await this.updateToggleButton(),this.cachedPcapData&&await this.renderCurrentMode(this.cachedPcapData),this.toggleButton.disabled=!1,this.toggleButton.classList.remove("loading"),this.toggleButton.style.background="",this.toggleButton.style.opacity=""}async updateToggleButton(){if(!this.shouldShowToggleButton())return;const t="parsed"===this.displayMode?await this.getText("showHex"):await this.getText("showParsed");this.toggleButton.textContent=t}updateContentDisplayMode(){const t=this.contentElement.querySelector(".parsed-content"),e=this.contentElement.querySelector(".hex-content");t&&(t.style.display="parsed"===this.displayMode?"block":"none"),e&&(e.style.display="hex"===this.displayMode?"block":"none")}async loadPcapData(){const t=this.getAttribute("src");if(!t||!t.trim()||/\{\{.*\}\}/.test(t)){const t=await this.getText("errorNoSrc");return void this.showError(t)}const e=new AbortController,{signal:n}=e;try{await this.showLoading();const a=setTimeout(()=>e.abort(),3e4),r=await fetch(t,{signal:n});if(clearTimeout(a),!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);const i=await r.arrayBuffer();if(0===i.byteLength)throw new Error("Empty file received");const o=await s.parsePcapFile(i);this.cachedPcapData=o,await this.renderPcapData(o)}catch(t){this.handleError(t)}finally{this.loadingElement.style.display="none"}}async handleError(t){let e=await this.getText("errorLoadFailed");console.error("加载PCAP失败:",t),this.showError(e||"加载PCAP文件失败")}async showLoading(){const{loadingElement:t,errorElement:e,contentElement:n,toggleButton:s}=this;if(t&&e&&n)try{const a=await this.getText("loading");t.style.display="block",t.textContent=a,e.style.display="none",n.style.display="none",s&&(s.style.display="none")}catch(t){console.error("加载 loading 文本时发生错误:",t)}else console.warn("缺少必要的 DOM 元素,无法正常显示加载状态")}showError(t){this.loadingElement&&this.errorElement&&this.contentElement?(this._setDisplayState(this.loadingElement,"none"),this._setDisplayState(this.errorElement,"block"),this._setDisplayState(this.contentElement,"none"),this.toggleButton&&(this.toggleButton.style.display="none"),this.errorElement.textContent=t||"发生错误,请重试"):console.warn("缺少必要的 DOM 元素,无法显示错误信息")}_setDisplayState(t,e){t.style.display=e}async renderPcapData(t){this.loadingElement.style.display="none",this.errorElement.style.display="none",this.contentElement.style.display="block",this.shouldShowToggleButton()?(this.toggleButton.style.display="block",await this.updateToggleButton()):(this.toggleButton.style.display="none",this.setDisplayMode("parsed"));try{await this.renderCurrentMode(t)}catch(t){console.error("渲染PCAP数据失败:",t),this.contentElement.style.display="none",this.errorElement.style.display="block",this.errorElement.textContent="加载数据时发生错误"}}toggleFullscreen(){this.fullscreen=!this.fullscreen,this.updateFullscreenState(),this.updateFullscreenButton(),document.body.style.overflow=this.fullscreen?"hidden":""}updateFullscreenState(){const t=this.shadow.host;this.fullscreen?(this.classList.add("fullscreen"),t&&t.classList.add("fullscreen"),document.body.style.overflow="hidden"):(this.classList.remove("fullscreen"),t&&t.classList.remove("fullscreen"),document.body.style.overflow="")}updateFullscreenButton(){this.fullscreenButton&&(this.fullscreenButton.style.display=this.showFullscreenBtn?"inline-block":"none",this.i18nManager.getAllTexts().then(t=>{this.fullscreenButton&&t&&(this.fullscreenButton.title=this.fullscreen?t.exitFullscreen:t.fullscreen)}),this.updateFullscreenButtonIcon())}updateFullscreenButtonIcon(){this.fullscreenButton&&(this.fullscreen?this.fullscreenButton.innerHTML='<svg width="16" height="16" viewBox="0 0 16 16"><polyline points="4,12 4,8 8,8" stroke="#888" stroke-width="2" fill="none"/><polyline points="12,4 12,8 8,8" stroke="#888" stroke-width="2" fill="none"/></svg>':this.fullscreenButton.innerHTML='<svg width="16" height="16" viewBox="0 0 16 16"><rect x="3" y="3" width="10" height="10" rx="2" fill="none" stroke="#888" stroke-width="2"/></svg>')}}customElements.define("pcap-element",l);export{l as PcapElement};