@w57124/exs-monitor-sdk 0.1.0
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/README.md +137 -0
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +125 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.global.js +27 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# FE Monitor SDK
|
|
2
|
+
|
|
3
|
+
前端监控 SDK:错误上报、性能指标、网络拦截。打包为 ESM/CJS/IIFE,支持浏览器直接注入与构建工具使用。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @your-scope/exs-monitor-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { MonitorClient } from '@your-scope/exs-monitor-sdk'
|
|
15
|
+
|
|
16
|
+
const client = new MonitorClient({
|
|
17
|
+
dsn: 'https://your-collector.example.com/ingest',
|
|
18
|
+
appVersion: '1.0.0',
|
|
19
|
+
enableErrors: true,
|
|
20
|
+
enablePerformance: true,
|
|
21
|
+
enableNetwork: true,
|
|
22
|
+
transport: { batch: true, batchSize: 20, flushIntervalMs: 5000 }
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
client.capture('custom', { action: 'page_view' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 白屏检测(可选)
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const client = initMonitor({
|
|
32
|
+
dsn: 'https://your-collector.example.com/ingest',
|
|
33
|
+
enableWhiteScreen: true,
|
|
34
|
+
whiteScreenOptions: {
|
|
35
|
+
delayMs: 3000, // 首次检测延迟
|
|
36
|
+
minVisibleCount: 2, // 视口内可见元素阈值(<= 判定白屏)
|
|
37
|
+
minArea: 2500, // 单元素最小面积阈值(px^2)
|
|
38
|
+
sampleTimes: 3, // 采样次数(连续采样)
|
|
39
|
+
sampleIntervalMs: 1000, // 采样间隔
|
|
40
|
+
includePerf: true // 是否附带性能快照(TTFB/DCL/Load/FCP/LCP)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
说明:将 `includePerf` 设为 `false` 可关闭性能快照以节省带宽。
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 本地存储模式与事件查看器
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { initMonitor } from '@your-scope/exs-monitor-sdk'
|
|
51
|
+
|
|
52
|
+
const client = initMonitor({
|
|
53
|
+
dsn: '', // 本地模式可为空
|
|
54
|
+
transport: { mode: 'local', localKey: '__FEMONITOR_EVENTS__', localMaxItems: 500 },
|
|
55
|
+
enableErrors: true,
|
|
56
|
+
enablePerformance: true,
|
|
57
|
+
enableNetwork: true
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 打开事件列表 UI(覆盖层)
|
|
61
|
+
client.openEventList()
|
|
62
|
+
|
|
63
|
+
// 如需自定义渲染,也可:
|
|
64
|
+
// import { openEventViewer } from '@your-scope/exs-monitor-sdk'
|
|
65
|
+
// openEventViewer(client.getStoredEvents(), () => client.clearStoredEvents())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
快捷键(手动绑定/解绑):不再默认启用,请按需绑定。
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { bindViewerHotkeyDefault, unbindViewerHotkeyDefault } from '@your-scope/exs-monitor-sdk'
|
|
72
|
+
|
|
73
|
+
// 绑定快捷键(默认 'Ctrl+Shift+M',可传入 'Alt+K' 等自定义)
|
|
74
|
+
bindViewerHotkeyDefault('Alt+K')
|
|
75
|
+
|
|
76
|
+
// 解绑
|
|
77
|
+
unbindViewerHotkeyDefault()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 网络拦截域名与 URL 过滤
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
initMonitor({
|
|
84
|
+
enableNetwork: true,
|
|
85
|
+
networkHostsAllowlist: ['api.example.com', '*.internal.example.com'],
|
|
86
|
+
networkHostsBlocklist: ['blocked.example.com'],
|
|
87
|
+
networkUrlAllowPatterns: [/\/v1\/orders\//, '^https://api.example.com/v2/'],
|
|
88
|
+
networkUrlBlockPatterns: ['\\.png$', '\\.(css|js)$'],
|
|
89
|
+
networkMaxBodyLength: 1000 // 请求/响应 body 最大长度(默认 1000),超出截断并标记,设为 0 表示不上报 body
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
说明:
|
|
94
|
+
- 网络事件上报包含 `requestBody` 和 `responseBody`(仅在存在时)
|
|
95
|
+
- `networkMaxBodyLength` 控制最大字符数,超出部分会被截断并添加 `...[truncated]` 标记
|
|
96
|
+
- 设为 `0` 或负数可完全禁用 body 上报以节省带宽
|
|
97
|
+
|
|
98
|
+
浏览器直接使用(IIFE,全局变量 `FEMonitorSDK`)
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<script src="/dist/index.global.js"></script>
|
|
102
|
+
<script>
|
|
103
|
+
// 方式一:直接调用导出函数
|
|
104
|
+
const client = FEMonitorSDK.initMonitor({ dsn: 'https://...' })
|
|
105
|
+
client.capture('custom', { hello: 'world' })
|
|
106
|
+
|
|
107
|
+
// 方式二:在 script 之前放置全局配置,可自动初始化
|
|
108
|
+
// <script>window.__FEMONITOR__ = { dsn: 'https://...' }</script>
|
|
109
|
+
// <script src="/dist/index.global.js"></script>
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 发布
|
|
114
|
+
|
|
115
|
+
- 更新 `package.json` 的 `name`、`version`、`repository`
|
|
116
|
+
- `npm run build`
|
|
117
|
+
- `npm publish --access public`
|
|
118
|
+
|
|
119
|
+
## 许可
|
|
120
|
+
|
|
121
|
+
MIT
|
|
122
|
+
|
|
123
|
+
## 文档(TypeDoc)
|
|
124
|
+
|
|
125
|
+
生成 API 文档到 `docs/`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm run docs
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 附录:sendBeacon 行为与兼容性
|
|
132
|
+
|
|
133
|
+
- 行为:当 `transport.useBeacon` 开启且环境支持时,SDK 在正常上报和页面隐藏/卸载时优先调用 `navigator.sendBeacon`,失败则自动回退 `fetch`。
|
|
134
|
+
- Header 限制:`sendBeacon` 不能自定义请求头,SDK 会以 `Blob(application/json)` 发送;若服务端依赖认证头,请改为从 URL 查询参数或请求体字段携带令牌。
|
|
135
|
+
- 方法与时机:`sendBeacon` 仅支持 POST,设计为在页面卸载时仍能可靠发送;SDK 在 `visibilitychange(hidden)`、`pagehide`、`beforeunload` 时尝试最后一次 flush。
|
|
136
|
+
- 体积与序列化:请保持单次批量 payload 尽量小(建议 < 64KB),超大 payload 可能被浏览器丢弃;SDK 默认批量大小可通过 `transport.batchSize` 控制。
|
|
137
|
+
- 关闭方式:设置 `transport.useBeacon = false` 可禁用。
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';var P=Object.defineProperty;var q=(t,e)=>()=>(t&&(e=t(t=0)),e);var V=(t,e)=>{for(var n in e)P(t,n,{get:e[n],enumerable:true});};var I={};V(I,{openEventViewer:()=>w});function F(){let t=document.createElement("div");return t.id="__femonitor_viewer__",t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.right="0",t.style.bottom="0",t.style.background="rgba(0,0,0,0.5)",t.style.zIndex="99999",t.style.display="flex",t.style.alignItems="center",t.style.justifyContent="center",t}function A(t){let e=document.createElement("div");return e.style.width="80%",e.style.maxWidth="960px",e.style.maxHeight="80%",e.style.overflow="auto",e.style.background="#fff",e.style.borderRadius="8px",e.style.boxShadow="0 10px 30px rgba(0,0,0,0.2)",e.style.padding="16px",e.innerHTML=t,e}function w(t,e){if(typeof document>"u")return;let n=document.getElementById("__femonitor_viewer__");n&&n.remove();let o=F(),s=t.slice().reverse().map(l=>`<tr>
|
|
2
|
+
<td style="white-space:nowrap">${new Date(l.timestamp).toLocaleString()}</td>
|
|
3
|
+
<td>${l.type}</td>
|
|
4
|
+
<td><pre style="margin:0;white-space:pre-wrap">${K(JSON.stringify(l.context??{},null,2))}</pre></td>
|
|
5
|
+
</tr>`).join(""),a=`
|
|
6
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px">
|
|
7
|
+
<h3 style="margin:0">FE Monitor \u672C\u5730\u4E8B\u4EF6 (${t.length})</h3>
|
|
8
|
+
<div style="display:flex;gap:8px">
|
|
9
|
+
<button id="__fem_close__">\u5173\u95ED</button>
|
|
10
|
+
<button id="__fem_clear__">\u6E05\u7A7A</button>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<table style="width:100%;border-collapse:collapse">
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u65F6\u95F4</th>
|
|
17
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u7C7B\u578B</th>
|
|
18
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u5185\u5BB9</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
${s||'<tr><td colspan="3" style="padding:12px 4px;color:#666">\u6682\u65E0\u6570\u636E</td></tr>'}
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
`,i=A(a);o.appendChild(i),document.body.appendChild(o),o.addEventListener("click",l=>{l.target===o&&o.remove();});let r=i.querySelector("#__fem_close__");r&&(r.onclick=()=>o.remove());let c=i.querySelector("#__fem_clear__");c&&(c.onclick=()=>{e?.(),o.remove();});}function K(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}var g=q(()=>{});function M(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():"xxxxxxxxyxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return (t==="x"?e:e&3|8).toString(16)})}function O(){if(!(typeof window>"u"))return {width:window.innerWidth,height:window.innerHeight}}function T(){if(!(typeof location>"u"))return location.href}var _={batch:true,batchSize:20,flushIntervalMs:5e3},y=class{constructor(e){this.queue=[];this.endpoint=e.endpoint,this.headers=e.headers??{"Content-Type":"application/json"},this.batch=e.batch??_.batch,this.batchSize=e.batchSize??_.batchSize,this.flushIntervalMs=e.flushIntervalMs??_.flushIntervalMs,this.mode=e.mode??"http",this.localKey=e.localKey??"__FEMONITOR_EVENTS__",this.localMaxItems=e.localMaxItems,this.useBeacon=e.useBeacon??true,this.batch&&this.start(),this.bindPageLifecycle();}enqueue(e){if(!this.batch){this.send([e]);return}this.queue.push(e),this.queue.length>=this.batchSize&&this.flush();}async flush(){if(this.queue.length===0)return;let e=this.queue.splice(0,this.queue.length);await this.send(e);}start(){this.stop(),this.timer=setInterval(()=>{this.flush();},this.flushIntervalMs);}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);}async send(e){try{if(this.mode==="local"||!this.endpoint){this.appendToLocal(e);return}if(this.useBeacon&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"&&this.sendViaBeacon(e))return;await fetch(this.endpoint,{method:"POST",headers:this.headers,body:JSON.stringify({events:e})});}catch{}}sendViaBeacon(e){try{let n=JSON.stringify({events:e}),o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(this.endpoint,o)}catch{return false}}bindPageLifecycle(){if(typeof window>"u")return;let e=()=>{if(this.mode!=="http"||!this.useBeacon||typeof navigator>"u"||typeof navigator.sendBeacon!="function"||this.queue.length===0)return;let n=this.queue.splice(0,this.queue.length);this.sendViaBeacon(n);};window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();}),window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e);}appendToLocal(e){if(typeof localStorage>"u")return;let o=this.getLocal().concat(e);typeof this.localMaxItems=="number"&&this.localMaxItems>0&&o.length>this.localMaxItems&&(o=o.slice(-this.localMaxItems));try{localStorage.setItem(this.localKey,JSON.stringify(o));}catch{}}getLocal(){if(typeof localStorage>"u")return [];let e=localStorage.getItem(this.localKey);if(!e)return [];try{let n=JSON.parse(e);return Array.isArray(n)?n:[]}catch{return []}}listStored(){return this.getLocal()}clearStored(){if(!(typeof localStorage>"u"))try{localStorage.removeItem(this.localKey);}catch{}}};g();var h=class{constructor(e){this.options=e;let n={endpoint:e.dsn,...e.transport};this.transport=new y(n);}capture(e,n){let o={id:M(),type:e,timestamp:Date.now(),appVersion:this.options.appVersion,viewport:O(),page:T(),context:n??{}};this.transport.enqueue(o);}openEventList(){let e=this.transport.listStored();w(e,()=>this.transport.clearStored());}getStoredEvents(){return this.transport.listStored()}clearStoredEvents(){this.transport.clearStored();}};g();function U(t,e){let n=e.split("+").map(c=>c.trim().toLowerCase()),o=n.includes("ctrl")||n.includes("control"),s=n.includes("shift"),a=n.includes("alt"),i=n.find(c=>c!=="ctrl"&&c!=="control"&&c!=="shift"&&c!=="alt"),r=(t.key||"").toLowerCase();return !!o==!!t.ctrlKey&&!!s==!!t.shiftKey&&!!a==!!t.altKey&&(i?r===i:true)}function W(t){return t&&t.trim()?t:"Ctrl+Shift+M"}function B(t,e,n){if(typeof window>"u")return;E();let o=W(n||window.__FEMONITOR_HOTKEY__);window.__FEMONITOR_HOTKEY__=o;let s=a=>{if(U(a,o))try{t();}catch{e();}};window.addEventListener("keydown",s),window.__FEMONITOR_KB_LISTENER__=s;}function E(){if(typeof window>"u")return;let t=window.__FEMONITOR_KB_LISTENER__;t&&window.removeEventListener("keydown",t),window.__FEMONITOR_KB_LISTENER__=void 0;}function C(t,e){e.enableErrors&&(typeof window>"u"||(window.addEventListener("error",n=>{let o=n.error||n.message;t.capture("error",{error:o});},true),window.addEventListener("unhandledrejection",n=>{t.capture("unhandledrejection",{reason:n.reason});})));}function L(t,e){if(!e.enablePerformance||typeof window>"u"||!("performance"in window))return;let n=performance.getEntriesByType("navigation")[0];if(n&&t.capture("performance",{navigation:{domContentLoaded:n.domContentLoadedEventEnd-n.startTime,loadEvent:n.loadEventEnd-n.startTime,ttfb:n.responseStart-n.requestStart}}),"PerformanceObserver"in window)try{let o=new PerformanceObserver(s=>{for(let a of s.getEntries())if(a.entryType==="largest-contentful-paint"&&t.capture("performance",{lcp:a.startTime}),a.entryType==="layout-shift"){let i=a;i&&!i.hadRecentInput&&typeof i.value=="number"&&t.capture("performance",{cls:i.value});}});o.observe({type:"largest-contentful-paint",buffered:!0}),o.observe({type:"layout-shift",buffered:!0});}catch{}}function R(t,e){if(!e.enableNetwork||typeof window>"u")return;let n=e.networkMaxBodyLength??1e3,o=window.fetch;window.fetch=async(...r)=>{let c=performance.now(),l;try{if(r[1]?.body){let f=r[1].body;if(typeof f=="string")l=u(f,n);else if(f instanceof FormData||f instanceof URLSearchParams)try{l=u(String(f),n);}catch{}}else if(!r[1]&&typeof r[0]!="string"&&r[0].body)try{let v=await r[0].clone().text();l=u(v,n);}catch{}let d=await o(...r),m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;if(!b(p,e)||!x(p,e))return d;let S;try{let v=await d.clone().text();S=u(v,n);}catch{}return t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:d.status,duration:m-c,requestBody:l,responseBody:S}}),d}catch(d){let m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;throw !b(p,e)||!x(p,e)||t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:-1,duration:m-c,error:String(d),requestBody:l}}),d}};let s=window.XMLHttpRequest,a=s.prototype.open,i=s.prototype.send;s.prototype.open=function(r,c,...l){return this.__monitor__={method:r,url:c},a.apply(this,[r,c,...l])},s.prototype.send=function(r){let c=this.__monitor__||{},l;if(r)if(typeof r=="string")l=u(r,n);else if(r instanceof FormData||r instanceof URLSearchParams)try{l=u(String(r),n);}catch{}else r instanceof Blob?l=u(`[Blob:${r.type},${r.size}]`,n):r instanceof Document&&(l=u(r.documentElement.outerHTML||"",n));let d=performance.now();return this.addEventListener("loadend",()=>{let m=performance.now();if(!b(c.url,e)||!x(c.url,e))return;let p;try{this.responseType===""||this.responseType==="text"?p=u(this.responseText||"",n):this.response&&(p=u(String(this.response),n));}catch{}t.capture("network",{xhr:{url:c.url,method:c.method,status:this.status,duration:m-d,requestBody:l,responseBody:p}});}),i.call(this,r)};}function b(t,e){let n=e.networkHostsAllowlist,o=e.networkHostsBlocklist;if(!n||n.length===0)return true;try{let a=new URL(t,typeof location<"u"?location.href:"http://localhost").hostname;return o&&o.some(i=>k(a,i))?!1:n.some(i=>k(a,i))}catch{return false}}function k(t,e){if(!e)return false;if(e.startsWith("*.")){let n=e.slice(1);return t.endsWith(n)}return t===e}function x(t,e){let n=e.networkUrlBlockPatterns,o=e.networkUrlAllowPatterns;try{return n&&n.some(s=>N(t,s))?!1:o&&o.length>0?o.some(s=>N(t,s)):!0}catch{return false}}function N(t,e){if(typeof e=="string")try{return new RegExp(e).test(t)}catch{return false}return e.test(t)}function u(t,e){if(e<=0||!t)return;if(t.length<=e)return t;let n="...[truncated]";return t.slice(0,e-n.length)+n}function H(t,e){if(!e.enableWhiteScreen||typeof window>"u"||typeof document>"u")return;let n=e.whiteScreenOptions?.delayMs??3e3,o=e.whiteScreenOptions?.minVisibleCount??2,s=e.whiteScreenOptions?.minArea??2500,a=Math.max(1,e.whiteScreenOptions?.sampleTimes??1),i=e.whiteScreenOptions?.sampleIntervalMs??1e3,r=()=>{setTimeout(()=>{try{j(s,a,i).then(c=>{let l=Math.min(...c);if(l<=o){let d=e.whiteScreenOptions?.includePerf===!1?void 0:$();t.capture("whitescreen",{visibleCount:l,samples:c,readyState:document.readyState,url:location.href,perf:d});}}).catch(()=>{});}catch{}},n);};document.readyState==="complete"?r():window.addEventListener("load",r,{once:true});}function D(t){let e=window.innerWidth,n=window.innerHeight,o=0,s=document.body?Array.from(document.body.querySelectorAll("*")):[];for(let a of s){if(!z(a))continue;let i=a.getBoundingClientRect();if(i.width<=0||i.height<=0||i.bottom<0||i.right<0||i.top>n||i.left>e)continue;if(i.width*i.height>=t&&o++,o>10)break}return o}function z(t){let e=getComputedStyle(t);return !(e.display==="none"||e.visibility==="hidden"||parseFloat(e.opacity||"1")===0||t.tagName==="SCRIPT"||t.tagName==="STYLE"||t.tagName==="LINK"||t.tagName==="META")}function j(t,e,n){let o=[];return new Promise(s=>{let a=i=>{if(o.push(D(t)),i<=1)return s(o);setTimeout(()=>a(i-1),n);};a(e);})}function $(){if(typeof performance>"u")return;let t=performance.getEntriesByType("navigation")[0],n=performance.getEntriesByType("paint").find(a=>a.name==="first-contentful-paint")?.startTime,o=performance.getEntriesByType("largest-contentful-paint")||[],s=o.length?Math.max(...o.map(a=>a.startTime)):void 0;return {navigation:t?{ttfb:t.responseStart-t.requestStart,domContentLoaded:t.domContentLoadedEventEnd-t.startTime,loadEvent:t.loadEventEnd-t.startTime}:void 0,fcp:n,lcp:s}}function J(t){let e=new h(t);return C(e,t),L(e,t),R(e,t),H(e,t),e}if(typeof window<"u"&&window.__FEMONITOR__&&!window.__FEMONITOR_INSTANCE__)try{window.__FEMONITOR_INSTANCE__=J(window.__FEMONITOR__);}catch{}function he(t){typeof window>"u"||B(()=>{let e=window.__FEMONITOR_INSTANCE__;e&&typeof e.openEventList=="function"&&e.openEventList();},()=>{let e="__FEMONITOR_EVENTS__",n=typeof localStorage<"u"?localStorage.getItem(e):null,o=n?JSON.parse(n):[];Promise.resolve().then(()=>(g(),I)).then(s=>{s.openEventViewer(Array.isArray(o)?o:[],()=>{try{localStorage.removeItem(e);}catch{}});}).catch(()=>{});},t);}function ye(){E();}
|
|
26
|
+
exports.MonitorClient=h;exports.bindViewerHotkeyDefault=he;exports.initMonitor=J;exports.openEventViewer=w;exports.unbindViewerHotkeyDefault=ye;//# sourceMappingURL=index.cjs.map
|
|
27
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/viewer.ts","../src/utils/uid.ts","../src/utils/env.ts","../src/core/Transport.ts","../src/core/Client.ts","../src/index.ts","../src/utils/hotkey.ts","../src/integrations/errors.ts","../src/integrations/perf.ts","../src/integrations/network.ts","../src/integrations/whitescreen.ts"],"names":["viewer_exports","__export","openEventViewer","createContainer","el","createPanel","html","panel","events","onClear","existing","container","rows","e","escapeHtml","btnClose","btnClear","str","init_viewer","__esmMin","createUid","c","r","getViewport","getPageUrl","DEFAULTS","Transport","options","event","payload","blob","flushWithBeacon","merged","raw","arr","MonitorClient","transportOptions","type","data","matchesHotkey","hotkey","parts","s","needCtrl","needShift","needAlt","keyPart","p","pressedKey","normalizeHotkey","bindViewerHotkey","open","openFallback","unbindViewerHotkey","finalHotkey","listener","prev","setupErrorIntegration","client","error","setupPerformanceIntegration","nav","po","list","entry","ls","setupNetworkIntegration","maxBodyLength","originalFetch","args","start","requestBody","body","truncateBody","text","res","end","url","isAllowedByHost","isUrlAllowedByPattern","responseBody","XHR","send","method","rest","meta","inputUrl","allow","block","host","rule","matchHost","suffix","testPattern","pattern","maxLength","marker","setupWhiteScreenIntegration","delay","minVisibleCount","minArea","sampleTimes","sampleInterval","run","multiSample","samples","worst","perf","getPerfSnapshot","countVisibleInViewport","viewportWidth","viewportHeight","count","nodes","isElementActuallyVisible","rect","style","times","interval","results","resolve","tick","left","fcp","lcpEntries","lcp","initMonitor","bindViewerHotkeyDefault","inst","key","mod","unbindViewerHotkeyDefault"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BaseEvent } from '../core/types'\n\n/** 创建遮罩容器 */\nfunction createContainer(): HTMLElement {\n const el = document.createElement('div')\n el.id = '__femonitor_viewer__'\n el.style.position = 'fixed'\n el.style.top = '0'\n el.style.left = '0'\n el.style.right = '0'\n el.style.bottom = '0'\n el.style.background = 'rgba(0,0,0,0.5)'\n el.style.zIndex = '99999'\n el.style.display = 'flex'\n el.style.alignItems = 'center'\n el.style.justifyContent = 'center'\n return el\n}\n\n/** 创建面板节点并填充 HTML */\nfunction createPanel(html: string): HTMLElement {\n const panel = document.createElement('div')\n panel.style.width = '80%'\n panel.style.maxWidth = '960px'\n panel.style.maxHeight = '80%'\n panel.style.overflow = 'auto'\n panel.style.background = '#fff'\n panel.style.borderRadius = '8px'\n panel.style.boxShadow = '0 10px 30px rgba(0,0,0,0.2)'\n panel.style.padding = '16px'\n panel.innerHTML = html\n return panel\n}\n\n/**\n * 打开事件查看器覆盖层\n * @param events 要展示的事件数组\n * @param onClear 点击清空后的回调\n */\nexport function openEventViewer(events: BaseEvent[], onClear?: () => void): void {\n if (typeof document === 'undefined') return\n const existing = document.getElementById('__femonitor_viewer__')\n if (existing) existing.remove()\n\n const container = createContainer()\n const rows = events\n .slice()\n .reverse()\n .map(e => `<tr>\n <td style=\"white-space:nowrap\">${new Date(e.timestamp).toLocaleString()}</td>\n <td>${e.type}</td>\n <td><pre style=\"margin:0;white-space:pre-wrap\">${escapeHtml(JSON.stringify(e.context ?? {}, null, 2))}</pre></td>\n </tr>`)\n .join('')\n\n const html = `\n <div style=\"display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px\">\n <h3 style=\"margin:0\">FE Monitor 本地事件 (${events.length})</h3>\n <div style=\"display:flex;gap:8px\">\n <button id=\"__fem_close__\">关闭</button>\n <button id=\"__fem_clear__\">清空</button>\n </div>\n </div>\n <table style=\"width:100%;border-collapse:collapse\">\n <thead>\n <tr>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">时间</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">类型</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">内容</th>\n </tr>\n </thead>\n <tbody>\n ${rows || '<tr><td colspan=\"3\" style=\"padding:12px 4px;color:#666\">暂无数据</td></tr>'}\n </tbody>\n </table>\n `\n\n const panel = createPanel(html)\n container.appendChild(panel)\n document.body.appendChild(container)\n\n container.addEventListener('click', (e) => {\n if (e.target === container) container.remove()\n })\n const btnClose = panel.querySelector('#__fem_close__') as HTMLButtonElement | null\n if (btnClose) btnClose.onclick = () => container.remove()\n\n const btnClear = panel.querySelector('#__fem_clear__') as HTMLButtonElement | null\n if (btnClear) btnClear.onclick = () => {\n onClear?.()\n container.remove()\n }\n}\n\n/** 简单的 HTML 转义 */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n\n","/** 生成简单唯一 ID(优先使用 crypto.randomUUID) */\nexport function createUid(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID()\n }\n return 'xxxxxxxxyxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n\n","/** 获取窗口视口尺寸 */\nexport function getViewport(): { width: number; height: number } | undefined {\n if (typeof window === 'undefined') return undefined\n return { width: window.innerWidth, height: window.innerHeight }\n}\n\n/** 获取当前页面 URL */\nexport function getPageUrl(): string | undefined {\n if (typeof location === 'undefined') return undefined\n return location.href\n}\n\n\n","import type { BaseEvent, TransportOptions } from './types'\n\nconst DEFAULTS: Required<Pick<TransportOptions, 'batch' | 'batchSize' | 'flushIntervalMs'>> = {\n batch: true,\n batchSize: 20,\n flushIntervalMs: 5000\n}\n\n/**\n * 负责事件的缓存、批量与传输(HTTP/localStorage)\n */\nexport class Transport {\n private readonly endpoint: string\n private readonly headers: Record<string, string>\n private readonly batch: boolean\n private readonly batchSize: number\n private readonly flushIntervalMs: number\n private readonly mode: 'http' | 'local'\n private readonly localKey: string\n private readonly localMaxItems: number | undefined\n private readonly useBeacon: boolean\n private queue: BaseEvent[] = []\n private timer: number | undefined\n\n /**\n * @param options 传输层配置\n */\n constructor(options: TransportOptions) {\n this.endpoint = options.endpoint\n this.headers = options.headers ?? { 'Content-Type': 'application/json' }\n this.batch = options.batch ?? DEFAULTS.batch\n this.batchSize = options.batchSize ?? DEFAULTS.batchSize\n this.flushIntervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs\n this.mode = options.mode ?? 'http'\n this.localKey = options.localKey ?? '__FEMONITOR_EVENTS__'\n this.localMaxItems = options.localMaxItems\n this.useBeacon = options.useBeacon ?? true\n if (this.batch) this.start()\n this.bindPageLifecycle()\n }\n\n /**\n * 入队一个事件;当非批量模式下立即发送\n */\n enqueue(event: BaseEvent): void {\n if (!this.batch) {\n void this.send([event])\n return\n }\n this.queue.push(event)\n if (this.queue.length >= this.batchSize) {\n void this.flush()\n }\n }\n\n /**\n * 发送并清空当前队列\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n await this.send(payload)\n }\n\n private start(): void {\n this.stop()\n this.timer = (setInterval(() => {\n void this.flush()\n }, this.flushIntervalMs) as unknown) as number\n }\n\n private stop(): void {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n }\n\n /**\n * 实际发送逻辑:优先 local、其后 sendBeacon、最后 fetch\n */\n private async send(events: BaseEvent[]): Promise<void> {\n try {\n if (this.mode === 'local' || !this.endpoint) {\n this.appendToLocal(events)\n return\n }\n if (this.useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const ok = this.sendViaBeacon(events)\n if (ok) return\n // 如果 sendBeacon 返回 false,回退到 fetch\n }\n await fetch(this.endpoint, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ events })\n })\n } catch {\n // 忽略网络错误,避免影响业务\n }\n }\n\n /**\n * 使用 sendBeacon 发送;返回是否成功排队\n */\n private sendViaBeacon(events: BaseEvent[]): boolean {\n try {\n const payload = JSON.stringify({ events })\n const blob = new Blob([payload], { type: 'application/json' })\n // sendBeacon 不支持自定义 headers;若服务端依赖 headers,请在收端做兼容\n return navigator.sendBeacon(this.endpoint, blob)\n } catch {\n return false\n }\n }\n\n /**\n * 绑定页面生命周期,在隐藏/卸载时使用 sendBeacon flush\n */\n private bindPageLifecycle(): void {\n if (typeof window === 'undefined') return\n const flushWithBeacon = () => {\n if (this.mode !== 'http') return\n if (!this.useBeacon || typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') return\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n this.sendViaBeacon(payload)\n }\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushWithBeacon()\n })\n window.addEventListener('pagehide', flushWithBeacon)\n window.addEventListener('beforeunload', flushWithBeacon)\n }\n\n /**\n * 追加事件到 localStorage(受最大条数限制)\n */\n private appendToLocal(events: BaseEvent[]): void {\n if (typeof localStorage === 'undefined') return\n const current = this.getLocal() \n let merged = current.concat(events)\n if (typeof this.localMaxItems === 'number' && this.localMaxItems > 0 && merged.length > this.localMaxItems) {\n merged = merged.slice(-this.localMaxItems)\n }\n try {\n localStorage.setItem(this.localKey, JSON.stringify(merged))\n } catch {}\n }\n\n /** 获取本地缓存事件 */\n private getLocal(): BaseEvent[] {\n if (typeof localStorage === 'undefined') return []\n const raw = localStorage.getItem(this.localKey)\n if (!raw) return []\n try {\n const arr = JSON.parse(raw)\n return Array.isArray(arr) ? arr : []\n } catch {\n return []\n }\n }\n\n /** 列出本地缓存事件(仅本地模式使用) */\n listStored(): BaseEvent[] {\n return this.getLocal()\n }\n\n /** 清空本地缓存事件 */\n clearStored(): void {\n if (typeof localStorage === 'undefined') return\n try { localStorage.removeItem(this.localKey) } catch {}\n }\n}\n\n\n","import { createUid } from '../utils/uid'\nimport { getPageUrl, getViewport } from '../utils/env'\nimport type { BaseEvent, MonitorClientOptions, TransportOptions } from './types'\nimport { Transport } from './Transport'\nimport { openEventViewer } from '../ui/viewer'\n\n/**\n * 监控客户端:负责构建事件与调用传输层上报\n */\nexport class MonitorClient {\n private readonly options: MonitorClientOptions\n private readonly transport: Transport\n\n /**\n * @param options SDK 初始化参数\n */\n constructor(options: MonitorClientOptions) {\n this.options = options\n const transportOptions: TransportOptions = {\n endpoint: options.dsn,\n ...options.transport\n } as TransportOptions\n this.transport = new Transport(transportOptions)\n }\n\n /**\n * 采集一个事件\n * @param type 事件类型\n * @param data 自定义上下文数据\n */\n capture(type: BaseEvent['type'], data?: Omit<BaseEvent, 'id' | 'type' | 'timestamp' | 'viewport' | 'page'>['context']): void {\n const event: BaseEvent = {\n id: createUid(),\n type,\n timestamp: Date.now(),\n appVersion: this.options.appVersion,\n viewport: getViewport(),\n page: getPageUrl(),\n context: data ?? {}\n }\n this.transport.enqueue(event)\n }\n\n /** 打开本地事件列表查看器 */\n openEventList(): void {\n const events = this.transport.listStored()\n openEventViewer(events, () => this.transport.clearStored())\n }\n\n /** 获取本地缓存事件(仅本地模式有效) */\n getStoredEvents(): BaseEvent[] {\n return this.transport.listStored()\n }\n\n /** 清空本地缓存事件(仅本地模式有效) */\n clearStoredEvents(): void {\n this.transport.clearStored()\n }\n}\n\n\n","export { MonitorClient } from './core/Client'\nexport type { MonitorClientOptions, BaseEvent, TransportOptions } from './core/types'\nexport { openEventViewer } from './ui/viewer'\nimport type { MonitorClientOptions as Options } from './core/types'\nimport { bindViewerHotkey, unbindViewerHotkey } from './utils/hotkey'\nimport { setupErrorIntegration } from './integrations/errors'\nimport { setupPerformanceIntegration } from './integrations/perf'\nimport { setupNetworkIntegration } from './integrations/network'\nimport { setupWhiteScreenIntegration } from './integrations/whitescreen'\nimport { MonitorClient } from './core/Client'\n\n/**\n * 初始化 SDK 并按配置启用集成\n */\nexport function initMonitor(options: Options): MonitorClient {\n const client = new MonitorClient(options)\n setupErrorIntegration(client, options)\n setupPerformanceIntegration(client, options)\n setupNetworkIntegration(client, options)\n setupWhiteScreenIntegration(client, options)\n return client\n}\n\n// 可选的全局自动初始化:当页面上提前定义 window.__FEMONITOR__ 时生效\ndeclare global {\n interface Window {\n __FEMONITOR__?: Options\n __FEMONITOR_INSTANCE__?: MonitorClient\n }\n}\n\nif (typeof window !== 'undefined' && window.__FEMONITOR__ && !window.__FEMONITOR_INSTANCE__) {\n try {\n window.__FEMONITOR_INSTANCE__ = initMonitor(window.__FEMONITOR__)\n } catch {}\n}\n\n\n// 手动绑定/解绑快捷键 API\n/**\n * 绑定默认的查看器快捷键处理(对外导出)\n * @param hotkey 自定义快捷键(如 'Alt+K'),不传则为默认\n */\nexport function bindViewerHotkeyDefault(hotkey?: string): void {\n if (typeof window === 'undefined') return\n bindViewerHotkey(\n () => {\n const inst = window.__FEMONITOR_INSTANCE__ as any\n if (inst && typeof inst.openEventList === 'function') inst.openEventList()\n },\n () => {\n const key = '__FEMONITOR_EVENTS__'\n const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null\n const events = raw ? JSON.parse(raw) : []\n import('./ui/viewer').then(mod => {\n mod.openEventViewer(Array.isArray(events) ? events : [], () => {\n try { localStorage.removeItem(key) } catch {}\n })\n }).catch(() => {})\n },\n hotkey\n )\n}\n\n/** 解绑默认的查看器快捷键处理 */\nexport function unbindViewerHotkeyDefault(): void {\n unbindViewerHotkey()\n}\n\n","/**\n * 判断键盘事件是否匹配快捷键,如 'Ctrl+Shift+M'\n */\nexport function matchesHotkey(event: KeyboardEvent, hotkey: string): boolean {\n // 支持形如 'Ctrl+Shift+M'、'Alt+K'(大小写不敏感)\n const parts = hotkey.split('+').map(s => s.trim().toLowerCase())\n const needCtrl = parts.includes('ctrl') || parts.includes('control')\n const needShift = parts.includes('shift')\n const needAlt = parts.includes('alt')\n const keyPart = parts.find(p => p !== 'ctrl' && p !== 'control' && p !== 'shift' && p !== 'alt')\n const pressedKey = (event.key || '').toLowerCase()\n return (\n (!!needCtrl === !!event.ctrlKey) &&\n (!!needShift === !!event.shiftKey) &&\n (!!needAlt === !!event.altKey) &&\n (!!keyPart ? pressedKey === keyPart : true)\n )\n}\n\n/** 归一化快捷键(默认 'Ctrl+Shift+M') */\nexport function normalizeHotkey(hotkey?: string): string {\n return hotkey && hotkey.trim() ? hotkey : 'Ctrl+Shift+M'\n}\n\nexport type HotkeyListener = (e: KeyboardEvent) => void\n\ndeclare global {\n interface Window {\n __FEMONITOR_KB_LISTENER__?: HotkeyListener\n __FEMONITOR_HOTKEY__?: string\n }\n}\n\n/**\n * 绑定查看器快捷键\n * @param hotkey 自定义快捷键,不传则沿用上一次或默认\n * @param open 优先使用已有实例打开\n * @param openFallback 无实例时从本地读取并打开\n */\nexport function bindViewerHotkey(open: () => void, openFallback: () => void, hotkey?: string): void {\n if (typeof window === 'undefined') return\n unbindViewerHotkey()\n const finalHotkey = normalizeHotkey(hotkey || window.__FEMONITOR_HOTKEY__)\n window.__FEMONITOR_HOTKEY__ = finalHotkey\n const listener: HotkeyListener = (e) => {\n if (matchesHotkey(e, finalHotkey)) {\n try { open() } catch { openFallback() }\n }\n }\n window.addEventListener('keydown', listener)\n window.__FEMONITOR_KB_LISTENER__ = listener\n}\n\n/** 解绑查看器快捷键 */\nexport function unbindViewerHotkey(): void {\n if (typeof window === 'undefined') return\n const prev = window.__FEMONITOR_KB_LISTENER__\n if (prev) window.removeEventListener('keydown', prev)\n window.__FEMONITOR_KB_LISTENER__ = undefined\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 错误与未捕获 Promise 监听\n */\nexport function setupErrorIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableErrors) return\n if (typeof window === 'undefined') return\n\n window.addEventListener('error', (event) => {\n const error = event.error || event.message\n client.capture('error', { error })\n }, true)\n\n window.addEventListener('unhandledrejection', (event) => {\n client.capture('unhandledrejection', { reason: (event as PromiseRejectionEvent).reason })\n })\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 性能监控采集:navigation timing、LCP、CLS\n */\nexport function setupPerformanceIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enablePerformance) return\n if (typeof window === 'undefined' || !('performance' in window)) return\n\n // 基础导航时间\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n if (nav) {\n client.capture('performance', {\n navigation: {\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime,\n ttfb: nav.responseStart - nav.requestStart\n }\n })\n }\n\n // Web Vitals: LCP/CLS(尽量使用 PerformanceObserver)\n if ('PerformanceObserver' in window) {\n try {\n const po = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'largest-contentful-paint') {\n client.capture('performance', { lcp: entry.startTime })\n }\n if (entry.entryType === 'layout-shift') {\n const ls = entry as unknown as { value?: number; hadRecentInput?: boolean }\n if (ls && !ls.hadRecentInput && typeof ls.value === 'number') {\n client.capture('performance', { cls: ls.value })\n }\n }\n }\n })\n po.observe({ type: 'largest-contentful-paint', buffered: true as unknown as boolean })\n po.observe({ type: 'layout-shift', buffered: true as unknown as boolean })\n } catch {\n // 低版本浏览器忽略\n }\n }\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 网络请求拦截:fetch 与 XHR,采集耗时与状态码\n */\nexport function setupNetworkIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableNetwork) return\n if (typeof window === 'undefined') return\n\n const maxBodyLength = options.networkMaxBodyLength ?? 1000\n\n // fetch 拦截\n const originalFetch = window.fetch\n window.fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const start = performance.now()\n let requestBody: string | undefined\n try {\n // 尝试读取请求 body(不影响原始请求)\n if (args[1]?.body) {\n const body = args[1].body\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n }\n } else if (!args[1] && typeof args[0] !== 'string' && (args[0] as Request).body) {\n try {\n const cloned = (args[0] as Request).clone()\n const text = await cloned.text()\n requestBody = truncateBody(text, maxBodyLength)\n } catch {}\n }\n const res = await originalFetch(...args)\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options)) return res\n if (!isUrlAllowedByPattern(url, options)) return res\n let responseBody: string | undefined\n try {\n const cloned = res.clone()\n const text = await cloned.text()\n responseBody = truncateBody(text, maxBodyLength)\n } catch {}\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: res.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n return res\n } catch (error) {\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options) || !isUrlAllowedByPattern(url, options)) throw error\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: -1,\n duration: end - start,\n error: String(error),\n requestBody\n }\n })\n throw error\n }\n }\n\n // XHR 拦截\n const XHR = window.XMLHttpRequest\n const open = XHR.prototype.open\n const send = XHR.prototype.send\n XHR.prototype.open = function(method: string, url: string, ...rest: any[]) {\n ;(this as any).__monitor__ = { method, url }\n return open.apply(this, [method, url, ...rest] as any)\n }\n XHR.prototype.send = function(body?: Document | XMLHttpRequestBodyInit | null) {\n const meta = (this as any).__monitor__ || {}\n let requestBody: string | undefined\n if (body) {\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n } else if (body instanceof Blob) {\n // Blob 太大,通常只记录类型\n requestBody = truncateBody(`[Blob:${body.type},${body.size}]`, maxBodyLength)\n } else if (body instanceof Document) {\n requestBody = truncateBody(body.documentElement.outerHTML || '', maxBodyLength)\n }\n }\n const start = performance.now()\n this.addEventListener('loadend', () => {\n const end = performance.now()\n if (!isAllowedByHost(meta.url, options) || !isUrlAllowedByPattern(meta.url, options)) return\n let responseBody: string | undefined\n try {\n if (this.responseType === '' || this.responseType === 'text') {\n responseBody = truncateBody(this.responseText || '', maxBodyLength)\n } else if (this.response) {\n // 其他类型(json/blob等)转换为字符串\n responseBody = truncateBody(String(this.response), maxBodyLength)\n }\n } catch {}\n client.capture('network', {\n xhr: {\n url: meta.url,\n method: meta.method,\n status: this.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n })\n return send.call(this, body as any)\n }\n}\n\nfunction isAllowedByHost(inputUrl: string, options: MonitorClientOptions): boolean {\n const allow = options.networkHostsAllowlist\n const block = options.networkHostsBlocklist\n if (!allow || allow.length === 0) return true\n try {\n const u = new URL(inputUrl, typeof location !== 'undefined' ? location.href : 'http://localhost')\n const host = u.hostname\n if (block && block.some(rule => matchHost(host, rule))) return false\n return allow.some(rule => matchHost(host, rule))\n } catch {\n return false\n }\n}\n\nfunction matchHost(host: string, rule: string): boolean {\n if (!rule) return false\n if (rule.startsWith('*.')) {\n const suffix = rule.slice(1) // '.example.com'\n return host.endsWith(suffix)\n }\n return host === rule\n}\n\nfunction isUrlAllowedByPattern(inputUrl: string, options: MonitorClientOptions): boolean {\n const block = options.networkUrlBlockPatterns\n const allow = options.networkUrlAllowPatterns\n try {\n if (block && block.some(p => testPattern(inputUrl, p))) return false\n if (allow && allow.length > 0) {\n return allow.some(p => testPattern(inputUrl, p))\n }\n return true\n } catch {\n return false\n }\n}\n\nfunction testPattern(url: string, pattern: string | RegExp): boolean {\n if (typeof pattern === 'string') {\n // 将字符串作为正则源;默认不加锚,用户可自行提供 ^/$\n try { return new RegExp(pattern).test(url) } catch { return false }\n }\n return pattern.test(url)\n}\n\n/**\n * 截断 body 字符串,超出长度则截断并添加标记\n * @param body 原始 body 字符串\n * @param maxLength 最大长度,<=0 表示不上报\n * @returns 截断后的字符串,或 undefined(不上报)\n */\nfunction truncateBody(body: string, maxLength: number): string | undefined {\n if (maxLength <= 0 || !body) return undefined\n if (body.length <= maxLength) return body\n const marker = '...[truncated]'\n const truncated = body.slice(0, maxLength - marker.length)\n return truncated + marker\n}\n\n\n","import { MonitorClient } from '../core/Client'\nimport type { MonitorClientOptions } from '../core/types'\n\n/**\n * 白屏检测:在页面就绪后延迟一定时间,统计视口内可见元素数量\n * 若可见元素过少(阈值内),则认为存在白屏并上报。\n */\nexport function setupWhiteScreenIntegration(\n client: MonitorClient,\n options: MonitorClientOptions\n): void {\n if (!options.enableWhiteScreen) return\n if (typeof window === 'undefined' || typeof document === 'undefined') return\n\n const delay = options.whiteScreenOptions?.delayMs ?? 3000\n const minVisibleCount = options.whiteScreenOptions?.minVisibleCount ?? 2\n const minArea = options.whiteScreenOptions?.minArea ?? 50 * 50\n const sampleTimes = Math.max(1, options.whiteScreenOptions?.sampleTimes ?? 1)\n const sampleInterval = options.whiteScreenOptions?.sampleIntervalMs ?? 1000\n\n const run = () => {\n setTimeout(() => {\n try {\n multiSample(minArea, sampleTimes, sampleInterval).then(samples => {\n const worst = Math.min(...samples)\n if (worst <= minVisibleCount) {\n const perf = options.whiteScreenOptions?.includePerf === false ? undefined : getPerfSnapshot()\n client.capture('whitescreen', {\n visibleCount: worst,\n samples,\n readyState: document.readyState,\n url: location.href,\n perf\n })\n }\n }).catch(() => {})\n } catch {\n // ignore\n }\n }, delay)\n }\n\n if (document.readyState === 'complete') {\n run()\n } else {\n window.addEventListener('load', run, { once: true })\n }\n}\n\nfunction countVisibleInViewport(minArea: number): number {\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n let count = 0\n const nodes = document.body ? Array.from(document.body.querySelectorAll('*')) as HTMLElement[] : []\n for (const el of nodes) {\n if (!isElementActuallyVisible(el)) continue\n const rect = el.getBoundingClientRect()\n if (rect.width <= 0 || rect.height <= 0) continue\n if (rect.bottom < 0 || rect.right < 0 || rect.top > viewportHeight || rect.left > viewportWidth) continue\n const area = rect.width * rect.height\n if (area >= minArea) count++\n if (count > 10) break // 足够认为非白屏\n }\n return count\n}\n\nfunction isElementActuallyVisible(el: HTMLElement): boolean {\n const style = getComputedStyle(el)\n if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || '1') === 0) return false\n if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'LINK' || el.tagName === 'META') return false\n return true\n}\n\nfunction multiSample(minArea: number, times: number, interval: number): Promise<number[]> {\n const results: number[] = []\n return new Promise(resolve => {\n const tick = (left: number) => {\n results.push(countVisibleInViewport(minArea))\n if (left <= 1) return resolve(results)\n setTimeout(() => tick(left - 1), interval)\n }\n tick(times)\n })\n}\n\nfunction getPerfSnapshot(): Record<string, unknown> | undefined {\n if (typeof performance === 'undefined') return undefined\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n const paints = performance.getEntriesByType('paint') as PerformanceEntry[]\n const fcp = paints.find(p => p.name === 'first-contentful-paint')?.startTime\n const lcpEntries = (performance.getEntriesByType('largest-contentful-paint') as PerformanceEntry[]) || []\n const lcp = lcpEntries.length ? Math.max(...lcpEntries.map(e => e.startTime)) : undefined\n return {\n navigation: nav ? {\n ttfb: nav.responseStart - nav.requestStart,\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime\n } : undefined,\n fcp,\n lcp\n }\n}\n\n\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 事件类型枚举
|
|
3
|
+
*/
|
|
4
|
+
type EventType = 'error' | 'unhandledrejection' | 'performance' | 'network' | 'custom' | 'whitescreen';
|
|
5
|
+
/**
|
|
6
|
+
* SDK 上报的基础事件结构
|
|
7
|
+
*/
|
|
8
|
+
interface BaseEvent {
|
|
9
|
+
id: string;
|
|
10
|
+
type: EventType;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
appVersion?: string;
|
|
13
|
+
viewport?: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
page?: string;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 传输层(上报)配置
|
|
22
|
+
*/
|
|
23
|
+
interface TransportOptions {
|
|
24
|
+
endpoint: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
batch?: boolean;
|
|
27
|
+
batchSize?: number;
|
|
28
|
+
flushIntervalMs?: number;
|
|
29
|
+
mode?: 'http' | 'local';
|
|
30
|
+
localKey?: string;
|
|
31
|
+
localMaxItems?: number;
|
|
32
|
+
useBeacon?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* MonitorClient 初始化配置
|
|
36
|
+
*/
|
|
37
|
+
interface MonitorClientOptions {
|
|
38
|
+
dsn: string;
|
|
39
|
+
appVersion?: string;
|
|
40
|
+
release?: string;
|
|
41
|
+
env?: string;
|
|
42
|
+
transport?: Partial<TransportOptions>;
|
|
43
|
+
enableErrors?: boolean;
|
|
44
|
+
enablePerformance?: boolean;
|
|
45
|
+
enableNetwork?: boolean;
|
|
46
|
+
viewerHotkey?: string;
|
|
47
|
+
enableWhiteScreen?: boolean;
|
|
48
|
+
whiteScreenOptions?: {
|
|
49
|
+
delayMs?: number;
|
|
50
|
+
minVisibleCount?: number;
|
|
51
|
+
minArea?: number;
|
|
52
|
+
sampleTimes?: number;
|
|
53
|
+
sampleIntervalMs?: number;
|
|
54
|
+
includePerf?: boolean;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 网络拦截白名单:仅当匹配这些域名时才上报网络事件。
|
|
58
|
+
* 支持:
|
|
59
|
+
* - 精确域名:例如 'api.example.com'
|
|
60
|
+
* - 通配后缀:例如 '*.example.com'(匹配任意子域)
|
|
61
|
+
*/
|
|
62
|
+
networkHostsAllowlist?: string[];
|
|
63
|
+
/** 网络拦截黑名单(域名),优先级高于白名单 */
|
|
64
|
+
networkHostsBlocklist?: string[];
|
|
65
|
+
/** URL 允许上报的正则(字符串或 RegExp),存在则需匹配其一 */
|
|
66
|
+
networkUrlAllowPatterns?: (string | RegExp)[];
|
|
67
|
+
/** URL 禁止上报的正则(字符串或 RegExp),命中则直接跳过 */
|
|
68
|
+
networkUrlBlockPatterns?: (string | RegExp)[];
|
|
69
|
+
/**
|
|
70
|
+
* 网络请求/响应 body 最大长度(字符数),超出将截断并标记
|
|
71
|
+
* 默认 1000,设为 0 或负数表示不上报 body
|
|
72
|
+
*/
|
|
73
|
+
networkMaxBodyLength?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 监控客户端:负责构建事件与调用传输层上报
|
|
78
|
+
*/
|
|
79
|
+
declare class MonitorClient {
|
|
80
|
+
private readonly options;
|
|
81
|
+
private readonly transport;
|
|
82
|
+
/**
|
|
83
|
+
* @param options SDK 初始化参数
|
|
84
|
+
*/
|
|
85
|
+
constructor(options: MonitorClientOptions);
|
|
86
|
+
/**
|
|
87
|
+
* 采集一个事件
|
|
88
|
+
* @param type 事件类型
|
|
89
|
+
* @param data 自定义上下文数据
|
|
90
|
+
*/
|
|
91
|
+
capture(type: BaseEvent['type'], data?: Omit<BaseEvent, 'id' | 'type' | 'timestamp' | 'viewport' | 'page'>['context']): void;
|
|
92
|
+
/** 打开本地事件列表查看器 */
|
|
93
|
+
openEventList(): void;
|
|
94
|
+
/** 获取本地缓存事件(仅本地模式有效) */
|
|
95
|
+
getStoredEvents(): BaseEvent[];
|
|
96
|
+
/** 清空本地缓存事件(仅本地模式有效) */
|
|
97
|
+
clearStoredEvents(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 打开事件查看器覆盖层
|
|
102
|
+
* @param events 要展示的事件数组
|
|
103
|
+
* @param onClear 点击清空后的回调
|
|
104
|
+
*/
|
|
105
|
+
declare function openEventViewer(events: BaseEvent[], onClear?: () => void): void;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 初始化 SDK 并按配置启用集成
|
|
109
|
+
*/
|
|
110
|
+
declare function initMonitor(options: MonitorClientOptions): MonitorClient;
|
|
111
|
+
declare global {
|
|
112
|
+
interface Window {
|
|
113
|
+
__FEMONITOR__?: MonitorClientOptions;
|
|
114
|
+
__FEMONITOR_INSTANCE__?: MonitorClient;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 绑定默认的查看器快捷键处理(对外导出)
|
|
119
|
+
* @param hotkey 自定义快捷键(如 'Alt+K'),不传则为默认
|
|
120
|
+
*/
|
|
121
|
+
declare function bindViewerHotkeyDefault(hotkey?: string): void;
|
|
122
|
+
/** 解绑默认的查看器快捷键处理 */
|
|
123
|
+
declare function unbindViewerHotkeyDefault(): void;
|
|
124
|
+
|
|
125
|
+
export { type BaseEvent, MonitorClient, type MonitorClientOptions, type TransportOptions, bindViewerHotkeyDefault, initMonitor, openEventViewer, unbindViewerHotkeyDefault };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 事件类型枚举
|
|
3
|
+
*/
|
|
4
|
+
type EventType = 'error' | 'unhandledrejection' | 'performance' | 'network' | 'custom' | 'whitescreen';
|
|
5
|
+
/**
|
|
6
|
+
* SDK 上报的基础事件结构
|
|
7
|
+
*/
|
|
8
|
+
interface BaseEvent {
|
|
9
|
+
id: string;
|
|
10
|
+
type: EventType;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
appVersion?: string;
|
|
13
|
+
viewport?: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
page?: string;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 传输层(上报)配置
|
|
22
|
+
*/
|
|
23
|
+
interface TransportOptions {
|
|
24
|
+
endpoint: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
batch?: boolean;
|
|
27
|
+
batchSize?: number;
|
|
28
|
+
flushIntervalMs?: number;
|
|
29
|
+
mode?: 'http' | 'local';
|
|
30
|
+
localKey?: string;
|
|
31
|
+
localMaxItems?: number;
|
|
32
|
+
useBeacon?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* MonitorClient 初始化配置
|
|
36
|
+
*/
|
|
37
|
+
interface MonitorClientOptions {
|
|
38
|
+
dsn: string;
|
|
39
|
+
appVersion?: string;
|
|
40
|
+
release?: string;
|
|
41
|
+
env?: string;
|
|
42
|
+
transport?: Partial<TransportOptions>;
|
|
43
|
+
enableErrors?: boolean;
|
|
44
|
+
enablePerformance?: boolean;
|
|
45
|
+
enableNetwork?: boolean;
|
|
46
|
+
viewerHotkey?: string;
|
|
47
|
+
enableWhiteScreen?: boolean;
|
|
48
|
+
whiteScreenOptions?: {
|
|
49
|
+
delayMs?: number;
|
|
50
|
+
minVisibleCount?: number;
|
|
51
|
+
minArea?: number;
|
|
52
|
+
sampleTimes?: number;
|
|
53
|
+
sampleIntervalMs?: number;
|
|
54
|
+
includePerf?: boolean;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 网络拦截白名单:仅当匹配这些域名时才上报网络事件。
|
|
58
|
+
* 支持:
|
|
59
|
+
* - 精确域名:例如 'api.example.com'
|
|
60
|
+
* - 通配后缀:例如 '*.example.com'(匹配任意子域)
|
|
61
|
+
*/
|
|
62
|
+
networkHostsAllowlist?: string[];
|
|
63
|
+
/** 网络拦截黑名单(域名),优先级高于白名单 */
|
|
64
|
+
networkHostsBlocklist?: string[];
|
|
65
|
+
/** URL 允许上报的正则(字符串或 RegExp),存在则需匹配其一 */
|
|
66
|
+
networkUrlAllowPatterns?: (string | RegExp)[];
|
|
67
|
+
/** URL 禁止上报的正则(字符串或 RegExp),命中则直接跳过 */
|
|
68
|
+
networkUrlBlockPatterns?: (string | RegExp)[];
|
|
69
|
+
/**
|
|
70
|
+
* 网络请求/响应 body 最大长度(字符数),超出将截断并标记
|
|
71
|
+
* 默认 1000,设为 0 或负数表示不上报 body
|
|
72
|
+
*/
|
|
73
|
+
networkMaxBodyLength?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 监控客户端:负责构建事件与调用传输层上报
|
|
78
|
+
*/
|
|
79
|
+
declare class MonitorClient {
|
|
80
|
+
private readonly options;
|
|
81
|
+
private readonly transport;
|
|
82
|
+
/**
|
|
83
|
+
* @param options SDK 初始化参数
|
|
84
|
+
*/
|
|
85
|
+
constructor(options: MonitorClientOptions);
|
|
86
|
+
/**
|
|
87
|
+
* 采集一个事件
|
|
88
|
+
* @param type 事件类型
|
|
89
|
+
* @param data 自定义上下文数据
|
|
90
|
+
*/
|
|
91
|
+
capture(type: BaseEvent['type'], data?: Omit<BaseEvent, 'id' | 'type' | 'timestamp' | 'viewport' | 'page'>['context']): void;
|
|
92
|
+
/** 打开本地事件列表查看器 */
|
|
93
|
+
openEventList(): void;
|
|
94
|
+
/** 获取本地缓存事件(仅本地模式有效) */
|
|
95
|
+
getStoredEvents(): BaseEvent[];
|
|
96
|
+
/** 清空本地缓存事件(仅本地模式有效) */
|
|
97
|
+
clearStoredEvents(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 打开事件查看器覆盖层
|
|
102
|
+
* @param events 要展示的事件数组
|
|
103
|
+
* @param onClear 点击清空后的回调
|
|
104
|
+
*/
|
|
105
|
+
declare function openEventViewer(events: BaseEvent[], onClear?: () => void): void;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 初始化 SDK 并按配置启用集成
|
|
109
|
+
*/
|
|
110
|
+
declare function initMonitor(options: MonitorClientOptions): MonitorClient;
|
|
111
|
+
declare global {
|
|
112
|
+
interface Window {
|
|
113
|
+
__FEMONITOR__?: MonitorClientOptions;
|
|
114
|
+
__FEMONITOR_INSTANCE__?: MonitorClient;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 绑定默认的查看器快捷键处理(对外导出)
|
|
119
|
+
* @param hotkey 自定义快捷键(如 'Alt+K'),不传则为默认
|
|
120
|
+
*/
|
|
121
|
+
declare function bindViewerHotkeyDefault(hotkey?: string): void;
|
|
122
|
+
/** 解绑默认的查看器快捷键处理 */
|
|
123
|
+
declare function unbindViewerHotkeyDefault(): void;
|
|
124
|
+
|
|
125
|
+
export { type BaseEvent, MonitorClient, type MonitorClientOptions, type TransportOptions, bindViewerHotkeyDefault, initMonitor, openEventViewer, unbindViewerHotkeyDefault };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var FEMonitorSDK=(function(exports){'use strict';var P=Object.defineProperty;var q=(t,e)=>()=>(t&&(e=t(t=0)),e);var V=(t,e)=>{for(var n in e)P(t,n,{get:e[n],enumerable:true});};var I={};V(I,{openEventViewer:()=>w});function F(){let t=document.createElement("div");return t.id="__femonitor_viewer__",t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.right="0",t.style.bottom="0",t.style.background="rgba(0,0,0,0.5)",t.style.zIndex="99999",t.style.display="flex",t.style.alignItems="center",t.style.justifyContent="center",t}function A(t){let e=document.createElement("div");return e.style.width="80%",e.style.maxWidth="960px",e.style.maxHeight="80%",e.style.overflow="auto",e.style.background="#fff",e.style.borderRadius="8px",e.style.boxShadow="0 10px 30px rgba(0,0,0,0.2)",e.style.padding="16px",e.innerHTML=t,e}function w(t,e){if(typeof document>"u")return;let n=document.getElementById("__femonitor_viewer__");n&&n.remove();let o=F(),s=t.slice().reverse().map(l=>`<tr>
|
|
2
|
+
<td style="white-space:nowrap">${new Date(l.timestamp).toLocaleString()}</td>
|
|
3
|
+
<td>${l.type}</td>
|
|
4
|
+
<td><pre style="margin:0;white-space:pre-wrap">${K(JSON.stringify(l.context??{},null,2))}</pre></td>
|
|
5
|
+
</tr>`).join(""),a=`
|
|
6
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px">
|
|
7
|
+
<h3 style="margin:0">FE Monitor \u672C\u5730\u4E8B\u4EF6 (${t.length})</h3>
|
|
8
|
+
<div style="display:flex;gap:8px">
|
|
9
|
+
<button id="__fem_close__">\u5173\u95ED</button>
|
|
10
|
+
<button id="__fem_clear__">\u6E05\u7A7A</button>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<table style="width:100%;border-collapse:collapse">
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u65F6\u95F4</th>
|
|
17
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u7C7B\u578B</th>
|
|
18
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u5185\u5BB9</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
${s||'<tr><td colspan="3" style="padding:12px 4px;color:#666">\u6682\u65E0\u6570\u636E</td></tr>'}
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
`,i=A(a);o.appendChild(i),document.body.appendChild(o),o.addEventListener("click",l=>{l.target===o&&o.remove();});let r=i.querySelector("#__fem_close__");r&&(r.onclick=()=>o.remove());let c=i.querySelector("#__fem_clear__");c&&(c.onclick=()=>{e?.(),o.remove();});}function K(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}var g=q(()=>{});function M(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():"xxxxxxxxyxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return (t==="x"?e:e&3|8).toString(16)})}function O(){if(!(typeof window>"u"))return {width:window.innerWidth,height:window.innerHeight}}function T(){if(!(typeof location>"u"))return location.href}var _={batch:true,batchSize:20,flushIntervalMs:5e3},y=class{constructor(e){this.queue=[];this.endpoint=e.endpoint,this.headers=e.headers??{"Content-Type":"application/json"},this.batch=e.batch??_.batch,this.batchSize=e.batchSize??_.batchSize,this.flushIntervalMs=e.flushIntervalMs??_.flushIntervalMs,this.mode=e.mode??"http",this.localKey=e.localKey??"__FEMONITOR_EVENTS__",this.localMaxItems=e.localMaxItems,this.useBeacon=e.useBeacon??true,this.batch&&this.start(),this.bindPageLifecycle();}enqueue(e){if(!this.batch){this.send([e]);return}this.queue.push(e),this.queue.length>=this.batchSize&&this.flush();}async flush(){if(this.queue.length===0)return;let e=this.queue.splice(0,this.queue.length);await this.send(e);}start(){this.stop(),this.timer=setInterval(()=>{this.flush();},this.flushIntervalMs);}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);}async send(e){try{if(this.mode==="local"||!this.endpoint){this.appendToLocal(e);return}if(this.useBeacon&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"&&this.sendViaBeacon(e))return;await fetch(this.endpoint,{method:"POST",headers:this.headers,body:JSON.stringify({events:e})});}catch{}}sendViaBeacon(e){try{let n=JSON.stringify({events:e}),o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(this.endpoint,o)}catch{return false}}bindPageLifecycle(){if(typeof window>"u")return;let e=()=>{if(this.mode!=="http"||!this.useBeacon||typeof navigator>"u"||typeof navigator.sendBeacon!="function"||this.queue.length===0)return;let n=this.queue.splice(0,this.queue.length);this.sendViaBeacon(n);};window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();}),window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e);}appendToLocal(e){if(typeof localStorage>"u")return;let o=this.getLocal().concat(e);typeof this.localMaxItems=="number"&&this.localMaxItems>0&&o.length>this.localMaxItems&&(o=o.slice(-this.localMaxItems));try{localStorage.setItem(this.localKey,JSON.stringify(o));}catch{}}getLocal(){if(typeof localStorage>"u")return [];let e=localStorage.getItem(this.localKey);if(!e)return [];try{let n=JSON.parse(e);return Array.isArray(n)?n:[]}catch{return []}}listStored(){return this.getLocal()}clearStored(){if(!(typeof localStorage>"u"))try{localStorage.removeItem(this.localKey);}catch{}}};g();var h=class{constructor(e){this.options=e;let n={endpoint:e.dsn,...e.transport};this.transport=new y(n);}capture(e,n){let o={id:M(),type:e,timestamp:Date.now(),appVersion:this.options.appVersion,viewport:O(),page:T(),context:n??{}};this.transport.enqueue(o);}openEventList(){let e=this.transport.listStored();w(e,()=>this.transport.clearStored());}getStoredEvents(){return this.transport.listStored()}clearStoredEvents(){this.transport.clearStored();}};g();function U(t,e){let n=e.split("+").map(c=>c.trim().toLowerCase()),o=n.includes("ctrl")||n.includes("control"),s=n.includes("shift"),a=n.includes("alt"),i=n.find(c=>c!=="ctrl"&&c!=="control"&&c!=="shift"&&c!=="alt"),r=(t.key||"").toLowerCase();return !!o==!!t.ctrlKey&&!!s==!!t.shiftKey&&!!a==!!t.altKey&&(i?r===i:true)}function W(t){return t&&t.trim()?t:"Ctrl+Shift+M"}function B(t,e,n){if(typeof window>"u")return;E();let o=W(n||window.__FEMONITOR_HOTKEY__);window.__FEMONITOR_HOTKEY__=o;let s=a=>{if(U(a,o))try{t();}catch{e();}};window.addEventListener("keydown",s),window.__FEMONITOR_KB_LISTENER__=s;}function E(){if(typeof window>"u")return;let t=window.__FEMONITOR_KB_LISTENER__;t&&window.removeEventListener("keydown",t),window.__FEMONITOR_KB_LISTENER__=void 0;}function C(t,e){e.enableErrors&&(typeof window>"u"||(window.addEventListener("error",n=>{let o=n.error||n.message;t.capture("error",{error:o});},true),window.addEventListener("unhandledrejection",n=>{t.capture("unhandledrejection",{reason:n.reason});})));}function L(t,e){if(!e.enablePerformance||typeof window>"u"||!("performance"in window))return;let n=performance.getEntriesByType("navigation")[0];if(n&&t.capture("performance",{navigation:{domContentLoaded:n.domContentLoadedEventEnd-n.startTime,loadEvent:n.loadEventEnd-n.startTime,ttfb:n.responseStart-n.requestStart}}),"PerformanceObserver"in window)try{let o=new PerformanceObserver(s=>{for(let a of s.getEntries())if(a.entryType==="largest-contentful-paint"&&t.capture("performance",{lcp:a.startTime}),a.entryType==="layout-shift"){let i=a;i&&!i.hadRecentInput&&typeof i.value=="number"&&t.capture("performance",{cls:i.value});}});o.observe({type:"largest-contentful-paint",buffered:!0}),o.observe({type:"layout-shift",buffered:!0});}catch{}}function R(t,e){if(!e.enableNetwork||typeof window>"u")return;let n=e.networkMaxBodyLength??1e3,o=window.fetch;window.fetch=async(...r)=>{let c=performance.now(),l;try{if(r[1]?.body){let f=r[1].body;if(typeof f=="string")l=u(f,n);else if(f instanceof FormData||f instanceof URLSearchParams)try{l=u(String(f),n);}catch{}}else if(!r[1]&&typeof r[0]!="string"&&r[0].body)try{let v=await r[0].clone().text();l=u(v,n);}catch{}let d=await o(...r),m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;if(!b(p,e)||!x(p,e))return d;let S;try{let v=await d.clone().text();S=u(v,n);}catch{}return t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:d.status,duration:m-c,requestBody:l,responseBody:S}}),d}catch(d){let m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;throw !b(p,e)||!x(p,e)||t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:-1,duration:m-c,error:String(d),requestBody:l}}),d}};let s=window.XMLHttpRequest,a=s.prototype.open,i=s.prototype.send;s.prototype.open=function(r,c,...l){return this.__monitor__={method:r,url:c},a.apply(this,[r,c,...l])},s.prototype.send=function(r){let c=this.__monitor__||{},l;if(r)if(typeof r=="string")l=u(r,n);else if(r instanceof FormData||r instanceof URLSearchParams)try{l=u(String(r),n);}catch{}else r instanceof Blob?l=u(`[Blob:${r.type},${r.size}]`,n):r instanceof Document&&(l=u(r.documentElement.outerHTML||"",n));let d=performance.now();return this.addEventListener("loadend",()=>{let m=performance.now();if(!b(c.url,e)||!x(c.url,e))return;let p;try{this.responseType===""||this.responseType==="text"?p=u(this.responseText||"",n):this.response&&(p=u(String(this.response),n));}catch{}t.capture("network",{xhr:{url:c.url,method:c.method,status:this.status,duration:m-d,requestBody:l,responseBody:p}});}),i.call(this,r)};}function b(t,e){let n=e.networkHostsAllowlist,o=e.networkHostsBlocklist;if(!n||n.length===0)return true;try{let a=new URL(t,typeof location<"u"?location.href:"http://localhost").hostname;return o&&o.some(i=>k(a,i))?!1:n.some(i=>k(a,i))}catch{return false}}function k(t,e){if(!e)return false;if(e.startsWith("*.")){let n=e.slice(1);return t.endsWith(n)}return t===e}function x(t,e){let n=e.networkUrlBlockPatterns,o=e.networkUrlAllowPatterns;try{return n&&n.some(s=>N(t,s))?!1:o&&o.length>0?o.some(s=>N(t,s)):!0}catch{return false}}function N(t,e){if(typeof e=="string")try{return new RegExp(e).test(t)}catch{return false}return e.test(t)}function u(t,e){if(e<=0||!t)return;if(t.length<=e)return t;let n="...[truncated]";return t.slice(0,e-n.length)+n}function H(t,e){if(!e.enableWhiteScreen||typeof window>"u"||typeof document>"u")return;let n=e.whiteScreenOptions?.delayMs??3e3,o=e.whiteScreenOptions?.minVisibleCount??2,s=e.whiteScreenOptions?.minArea??2500,a=Math.max(1,e.whiteScreenOptions?.sampleTimes??1),i=e.whiteScreenOptions?.sampleIntervalMs??1e3,r=()=>{setTimeout(()=>{try{j(s,a,i).then(c=>{let l=Math.min(...c);if(l<=o){let d=e.whiteScreenOptions?.includePerf===!1?void 0:$();t.capture("whitescreen",{visibleCount:l,samples:c,readyState:document.readyState,url:location.href,perf:d});}}).catch(()=>{});}catch{}},n);};document.readyState==="complete"?r():window.addEventListener("load",r,{once:true});}function D(t){let e=window.innerWidth,n=window.innerHeight,o=0,s=document.body?Array.from(document.body.querySelectorAll("*")):[];for(let a of s){if(!z(a))continue;let i=a.getBoundingClientRect();if(i.width<=0||i.height<=0||i.bottom<0||i.right<0||i.top>n||i.left>e)continue;if(i.width*i.height>=t&&o++,o>10)break}return o}function z(t){let e=getComputedStyle(t);return !(e.display==="none"||e.visibility==="hidden"||parseFloat(e.opacity||"1")===0||t.tagName==="SCRIPT"||t.tagName==="STYLE"||t.tagName==="LINK"||t.tagName==="META")}function j(t,e,n){let o=[];return new Promise(s=>{let a=i=>{if(o.push(D(t)),i<=1)return s(o);setTimeout(()=>a(i-1),n);};a(e);})}function $(){if(typeof performance>"u")return;let t=performance.getEntriesByType("navigation")[0],n=performance.getEntriesByType("paint").find(a=>a.name==="first-contentful-paint")?.startTime,o=performance.getEntriesByType("largest-contentful-paint")||[],s=o.length?Math.max(...o.map(a=>a.startTime)):void 0;return {navigation:t?{ttfb:t.responseStart-t.requestStart,domContentLoaded:t.domContentLoadedEventEnd-t.startTime,loadEvent:t.loadEventEnd-t.startTime}:void 0,fcp:n,lcp:s}}function J(t){let e=new h(t);return C(e,t),L(e,t),R(e,t),H(e,t),e}if(typeof window<"u"&&window.__FEMONITOR__&&!window.__FEMONITOR_INSTANCE__)try{window.__FEMONITOR_INSTANCE__=J(window.__FEMONITOR__);}catch{}function he(t){typeof window>"u"||B(()=>{let e=window.__FEMONITOR_INSTANCE__;e&&typeof e.openEventList=="function"&&e.openEventList();},()=>{let e="__FEMONITOR_EVENTS__",n=typeof localStorage<"u"?localStorage.getItem(e):null,o=n?JSON.parse(n):[];Promise.resolve().then(()=>(g(),I)).then(s=>{s.openEventViewer(Array.isArray(o)?o:[],()=>{try{localStorage.removeItem(e);}catch{}});}).catch(()=>{});},t);}function ye(){E();}
|
|
26
|
+
exports.MonitorClient=h;exports.bindViewerHotkeyDefault=he;exports.initMonitor=J;exports.openEventViewer=w;exports.unbindViewerHotkeyDefault=ye;return exports;})({});//# sourceMappingURL=index.global.js.map
|
|
27
|
+
//# sourceMappingURL=index.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/viewer.ts","../src/utils/uid.ts","../src/utils/env.ts","../src/core/Transport.ts","../src/core/Client.ts","../src/index.ts","../src/utils/hotkey.ts","../src/integrations/errors.ts","../src/integrations/perf.ts","../src/integrations/network.ts","../src/integrations/whitescreen.ts"],"names":["viewer_exports","__export","openEventViewer","createContainer","el","createPanel","html","panel","events","onClear","existing","container","rows","e","escapeHtml","btnClose","btnClear","str","init_viewer","__esmMin","createUid","c","r","getViewport","getPageUrl","DEFAULTS","Transport","options","event","payload","blob","flushWithBeacon","merged","raw","arr","MonitorClient","transportOptions","type","data","matchesHotkey","hotkey","parts","s","needCtrl","needShift","needAlt","keyPart","p","pressedKey","normalizeHotkey","bindViewerHotkey","open","openFallback","unbindViewerHotkey","finalHotkey","listener","prev","setupErrorIntegration","client","error","setupPerformanceIntegration","nav","po","list","entry","ls","setupNetworkIntegration","maxBodyLength","originalFetch","args","start","requestBody","body","truncateBody","text","res","end","url","isAllowedByHost","isUrlAllowedByPattern","responseBody","XHR","send","method","rest","meta","inputUrl","allow","block","host","rule","matchHost","suffix","testPattern","pattern","maxLength","marker","setupWhiteScreenIntegration","delay","minVisibleCount","minArea","sampleTimes","sampleInterval","run","multiSample","samples","worst","perf","getPerfSnapshot","countVisibleInViewport","viewportWidth","viewportHeight","count","nodes","isElementActuallyVisible","rect","style","times","interval","results","resolve","tick","left","fcp","lcpEntries","lcp","initMonitor","bindViewerHotkeyDefault","inst","key","mod","unbindViewerHotkeyDefault"],"mappings":"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","file":"index.global.js","sourcesContent":["import type { BaseEvent } from '../core/types'\n\n/** 创建遮罩容器 */\nfunction createContainer(): HTMLElement {\n const el = document.createElement('div')\n el.id = '__femonitor_viewer__'\n el.style.position = 'fixed'\n el.style.top = '0'\n el.style.left = '0'\n el.style.right = '0'\n el.style.bottom = '0'\n el.style.background = 'rgba(0,0,0,0.5)'\n el.style.zIndex = '99999'\n el.style.display = 'flex'\n el.style.alignItems = 'center'\n el.style.justifyContent = 'center'\n return el\n}\n\n/** 创建面板节点并填充 HTML */\nfunction createPanel(html: string): HTMLElement {\n const panel = document.createElement('div')\n panel.style.width = '80%'\n panel.style.maxWidth = '960px'\n panel.style.maxHeight = '80%'\n panel.style.overflow = 'auto'\n panel.style.background = '#fff'\n panel.style.borderRadius = '8px'\n panel.style.boxShadow = '0 10px 30px rgba(0,0,0,0.2)'\n panel.style.padding = '16px'\n panel.innerHTML = html\n return panel\n}\n\n/**\n * 打开事件查看器覆盖层\n * @param events 要展示的事件数组\n * @param onClear 点击清空后的回调\n */\nexport function openEventViewer(events: BaseEvent[], onClear?: () => void): void {\n if (typeof document === 'undefined') return\n const existing = document.getElementById('__femonitor_viewer__')\n if (existing) existing.remove()\n\n const container = createContainer()\n const rows = events\n .slice()\n .reverse()\n .map(e => `<tr>\n <td style=\"white-space:nowrap\">${new Date(e.timestamp).toLocaleString()}</td>\n <td>${e.type}</td>\n <td><pre style=\"margin:0;white-space:pre-wrap\">${escapeHtml(JSON.stringify(e.context ?? {}, null, 2))}</pre></td>\n </tr>`)\n .join('')\n\n const html = `\n <div style=\"display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px\">\n <h3 style=\"margin:0\">FE Monitor 本地事件 (${events.length})</h3>\n <div style=\"display:flex;gap:8px\">\n <button id=\"__fem_close__\">关闭</button>\n <button id=\"__fem_clear__\">清空</button>\n </div>\n </div>\n <table style=\"width:100%;border-collapse:collapse\">\n <thead>\n <tr>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">时间</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">类型</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">内容</th>\n </tr>\n </thead>\n <tbody>\n ${rows || '<tr><td colspan=\"3\" style=\"padding:12px 4px;color:#666\">暂无数据</td></tr>'}\n </tbody>\n </table>\n `\n\n const panel = createPanel(html)\n container.appendChild(panel)\n document.body.appendChild(container)\n\n container.addEventListener('click', (e) => {\n if (e.target === container) container.remove()\n })\n const btnClose = panel.querySelector('#__fem_close__') as HTMLButtonElement | null\n if (btnClose) btnClose.onclick = () => container.remove()\n\n const btnClear = panel.querySelector('#__fem_clear__') as HTMLButtonElement | null\n if (btnClear) btnClear.onclick = () => {\n onClear?.()\n container.remove()\n }\n}\n\n/** 简单的 HTML 转义 */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n\n","/** 生成简单唯一 ID(优先使用 crypto.randomUUID) */\nexport function createUid(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID()\n }\n return 'xxxxxxxxyxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n\n","/** 获取窗口视口尺寸 */\nexport function getViewport(): { width: number; height: number } | undefined {\n if (typeof window === 'undefined') return undefined\n return { width: window.innerWidth, height: window.innerHeight }\n}\n\n/** 获取当前页面 URL */\nexport function getPageUrl(): string | undefined {\n if (typeof location === 'undefined') return undefined\n return location.href\n}\n\n\n","import type { BaseEvent, TransportOptions } from './types'\n\nconst DEFAULTS: Required<Pick<TransportOptions, 'batch' | 'batchSize' | 'flushIntervalMs'>> = {\n batch: true,\n batchSize: 20,\n flushIntervalMs: 5000\n}\n\n/**\n * 负责事件的缓存、批量与传输(HTTP/localStorage)\n */\nexport class Transport {\n private readonly endpoint: string\n private readonly headers: Record<string, string>\n private readonly batch: boolean\n private readonly batchSize: number\n private readonly flushIntervalMs: number\n private readonly mode: 'http' | 'local'\n private readonly localKey: string\n private readonly localMaxItems: number | undefined\n private readonly useBeacon: boolean\n private queue: BaseEvent[] = []\n private timer: number | undefined\n\n /**\n * @param options 传输层配置\n */\n constructor(options: TransportOptions) {\n this.endpoint = options.endpoint\n this.headers = options.headers ?? { 'Content-Type': 'application/json' }\n this.batch = options.batch ?? DEFAULTS.batch\n this.batchSize = options.batchSize ?? DEFAULTS.batchSize\n this.flushIntervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs\n this.mode = options.mode ?? 'http'\n this.localKey = options.localKey ?? '__FEMONITOR_EVENTS__'\n this.localMaxItems = options.localMaxItems\n this.useBeacon = options.useBeacon ?? true\n if (this.batch) this.start()\n this.bindPageLifecycle()\n }\n\n /**\n * 入队一个事件;当非批量模式下立即发送\n */\n enqueue(event: BaseEvent): void {\n if (!this.batch) {\n void this.send([event])\n return\n }\n this.queue.push(event)\n if (this.queue.length >= this.batchSize) {\n void this.flush()\n }\n }\n\n /**\n * 发送并清空当前队列\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n await this.send(payload)\n }\n\n private start(): void {\n this.stop()\n this.timer = (setInterval(() => {\n void this.flush()\n }, this.flushIntervalMs) as unknown) as number\n }\n\n private stop(): void {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n }\n\n /**\n * 实际发送逻辑:优先 local、其后 sendBeacon、最后 fetch\n */\n private async send(events: BaseEvent[]): Promise<void> {\n try {\n if (this.mode === 'local' || !this.endpoint) {\n this.appendToLocal(events)\n return\n }\n if (this.useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const ok = this.sendViaBeacon(events)\n if (ok) return\n // 如果 sendBeacon 返回 false,回退到 fetch\n }\n await fetch(this.endpoint, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ events })\n })\n } catch {\n // 忽略网络错误,避免影响业务\n }\n }\n\n /**\n * 使用 sendBeacon 发送;返回是否成功排队\n */\n private sendViaBeacon(events: BaseEvent[]): boolean {\n try {\n const payload = JSON.stringify({ events })\n const blob = new Blob([payload], { type: 'application/json' })\n // sendBeacon 不支持自定义 headers;若服务端依赖 headers,请在收端做兼容\n return navigator.sendBeacon(this.endpoint, blob)\n } catch {\n return false\n }\n }\n\n /**\n * 绑定页面生命周期,在隐藏/卸载时使用 sendBeacon flush\n */\n private bindPageLifecycle(): void {\n if (typeof window === 'undefined') return\n const flushWithBeacon = () => {\n if (this.mode !== 'http') return\n if (!this.useBeacon || typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') return\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n this.sendViaBeacon(payload)\n }\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushWithBeacon()\n })\n window.addEventListener('pagehide', flushWithBeacon)\n window.addEventListener('beforeunload', flushWithBeacon)\n }\n\n /**\n * 追加事件到 localStorage(受最大条数限制)\n */\n private appendToLocal(events: BaseEvent[]): void {\n if (typeof localStorage === 'undefined') return\n const current = this.getLocal() \n let merged = current.concat(events)\n if (typeof this.localMaxItems === 'number' && this.localMaxItems > 0 && merged.length > this.localMaxItems) {\n merged = merged.slice(-this.localMaxItems)\n }\n try {\n localStorage.setItem(this.localKey, JSON.stringify(merged))\n } catch {}\n }\n\n /** 获取本地缓存事件 */\n private getLocal(): BaseEvent[] {\n if (typeof localStorage === 'undefined') return []\n const raw = localStorage.getItem(this.localKey)\n if (!raw) return []\n try {\n const arr = JSON.parse(raw)\n return Array.isArray(arr) ? arr : []\n } catch {\n return []\n }\n }\n\n /** 列出本地缓存事件(仅本地模式使用) */\n listStored(): BaseEvent[] {\n return this.getLocal()\n }\n\n /** 清空本地缓存事件 */\n clearStored(): void {\n if (typeof localStorage === 'undefined') return\n try { localStorage.removeItem(this.localKey) } catch {}\n }\n}\n\n\n","import { createUid } from '../utils/uid'\nimport { getPageUrl, getViewport } from '../utils/env'\nimport type { BaseEvent, MonitorClientOptions, TransportOptions } from './types'\nimport { Transport } from './Transport'\nimport { openEventViewer } from '../ui/viewer'\n\n/**\n * 监控客户端:负责构建事件与调用传输层上报\n */\nexport class MonitorClient {\n private readonly options: MonitorClientOptions\n private readonly transport: Transport\n\n /**\n * @param options SDK 初始化参数\n */\n constructor(options: MonitorClientOptions) {\n this.options = options\n const transportOptions: TransportOptions = {\n endpoint: options.dsn,\n ...options.transport\n } as TransportOptions\n this.transport = new Transport(transportOptions)\n }\n\n /**\n * 采集一个事件\n * @param type 事件类型\n * @param data 自定义上下文数据\n */\n capture(type: BaseEvent['type'], data?: Omit<BaseEvent, 'id' | 'type' | 'timestamp' | 'viewport' | 'page'>['context']): void {\n const event: BaseEvent = {\n id: createUid(),\n type,\n timestamp: Date.now(),\n appVersion: this.options.appVersion,\n viewport: getViewport(),\n page: getPageUrl(),\n context: data ?? {}\n }\n this.transport.enqueue(event)\n }\n\n /** 打开本地事件列表查看器 */\n openEventList(): void {\n const events = this.transport.listStored()\n openEventViewer(events, () => this.transport.clearStored())\n }\n\n /** 获取本地缓存事件(仅本地模式有效) */\n getStoredEvents(): BaseEvent[] {\n return this.transport.listStored()\n }\n\n /** 清空本地缓存事件(仅本地模式有效) */\n clearStoredEvents(): void {\n this.transport.clearStored()\n }\n}\n\n\n","export { MonitorClient } from './core/Client'\nexport type { MonitorClientOptions, BaseEvent, TransportOptions } from './core/types'\nexport { openEventViewer } from './ui/viewer'\nimport type { MonitorClientOptions as Options } from './core/types'\nimport { bindViewerHotkey, unbindViewerHotkey } from './utils/hotkey'\nimport { setupErrorIntegration } from './integrations/errors'\nimport { setupPerformanceIntegration } from './integrations/perf'\nimport { setupNetworkIntegration } from './integrations/network'\nimport { setupWhiteScreenIntegration } from './integrations/whitescreen'\nimport { MonitorClient } from './core/Client'\n\n/**\n * 初始化 SDK 并按配置启用集成\n */\nexport function initMonitor(options: Options): MonitorClient {\n const client = new MonitorClient(options)\n setupErrorIntegration(client, options)\n setupPerformanceIntegration(client, options)\n setupNetworkIntegration(client, options)\n setupWhiteScreenIntegration(client, options)\n return client\n}\n\n// 可选的全局自动初始化:当页面上提前定义 window.__FEMONITOR__ 时生效\ndeclare global {\n interface Window {\n __FEMONITOR__?: Options\n __FEMONITOR_INSTANCE__?: MonitorClient\n }\n}\n\nif (typeof window !== 'undefined' && window.__FEMONITOR__ && !window.__FEMONITOR_INSTANCE__) {\n try {\n window.__FEMONITOR_INSTANCE__ = initMonitor(window.__FEMONITOR__)\n } catch {}\n}\n\n\n// 手动绑定/解绑快捷键 API\n/**\n * 绑定默认的查看器快捷键处理(对外导出)\n * @param hotkey 自定义快捷键(如 'Alt+K'),不传则为默认\n */\nexport function bindViewerHotkeyDefault(hotkey?: string): void {\n if (typeof window === 'undefined') return\n bindViewerHotkey(\n () => {\n const inst = window.__FEMONITOR_INSTANCE__ as any\n if (inst && typeof inst.openEventList === 'function') inst.openEventList()\n },\n () => {\n const key = '__FEMONITOR_EVENTS__'\n const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null\n const events = raw ? JSON.parse(raw) : []\n import('./ui/viewer').then(mod => {\n mod.openEventViewer(Array.isArray(events) ? events : [], () => {\n try { localStorage.removeItem(key) } catch {}\n })\n }).catch(() => {})\n },\n hotkey\n )\n}\n\n/** 解绑默认的查看器快捷键处理 */\nexport function unbindViewerHotkeyDefault(): void {\n unbindViewerHotkey()\n}\n\n","/**\n * 判断键盘事件是否匹配快捷键,如 'Ctrl+Shift+M'\n */\nexport function matchesHotkey(event: KeyboardEvent, hotkey: string): boolean {\n // 支持形如 'Ctrl+Shift+M'、'Alt+K'(大小写不敏感)\n const parts = hotkey.split('+').map(s => s.trim().toLowerCase())\n const needCtrl = parts.includes('ctrl') || parts.includes('control')\n const needShift = parts.includes('shift')\n const needAlt = parts.includes('alt')\n const keyPart = parts.find(p => p !== 'ctrl' && p !== 'control' && p !== 'shift' && p !== 'alt')\n const pressedKey = (event.key || '').toLowerCase()\n return (\n (!!needCtrl === !!event.ctrlKey) &&\n (!!needShift === !!event.shiftKey) &&\n (!!needAlt === !!event.altKey) &&\n (!!keyPart ? pressedKey === keyPart : true)\n )\n}\n\n/** 归一化快捷键(默认 'Ctrl+Shift+M') */\nexport function normalizeHotkey(hotkey?: string): string {\n return hotkey && hotkey.trim() ? hotkey : 'Ctrl+Shift+M'\n}\n\nexport type HotkeyListener = (e: KeyboardEvent) => void\n\ndeclare global {\n interface Window {\n __FEMONITOR_KB_LISTENER__?: HotkeyListener\n __FEMONITOR_HOTKEY__?: string\n }\n}\n\n/**\n * 绑定查看器快捷键\n * @param hotkey 自定义快捷键,不传则沿用上一次或默认\n * @param open 优先使用已有实例打开\n * @param openFallback 无实例时从本地读取并打开\n */\nexport function bindViewerHotkey(open: () => void, openFallback: () => void, hotkey?: string): void {\n if (typeof window === 'undefined') return\n unbindViewerHotkey()\n const finalHotkey = normalizeHotkey(hotkey || window.__FEMONITOR_HOTKEY__)\n window.__FEMONITOR_HOTKEY__ = finalHotkey\n const listener: HotkeyListener = (e) => {\n if (matchesHotkey(e, finalHotkey)) {\n try { open() } catch { openFallback() }\n }\n }\n window.addEventListener('keydown', listener)\n window.__FEMONITOR_KB_LISTENER__ = listener\n}\n\n/** 解绑查看器快捷键 */\nexport function unbindViewerHotkey(): void {\n if (typeof window === 'undefined') return\n const prev = window.__FEMONITOR_KB_LISTENER__\n if (prev) window.removeEventListener('keydown', prev)\n window.__FEMONITOR_KB_LISTENER__ = undefined\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 错误与未捕获 Promise 监听\n */\nexport function setupErrorIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableErrors) return\n if (typeof window === 'undefined') return\n\n window.addEventListener('error', (event) => {\n const error = event.error || event.message\n client.capture('error', { error })\n }, true)\n\n window.addEventListener('unhandledrejection', (event) => {\n client.capture('unhandledrejection', { reason: (event as PromiseRejectionEvent).reason })\n })\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 性能监控采集:navigation timing、LCP、CLS\n */\nexport function setupPerformanceIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enablePerformance) return\n if (typeof window === 'undefined' || !('performance' in window)) return\n\n // 基础导航时间\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n if (nav) {\n client.capture('performance', {\n navigation: {\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime,\n ttfb: nav.responseStart - nav.requestStart\n }\n })\n }\n\n // Web Vitals: LCP/CLS(尽量使用 PerformanceObserver)\n if ('PerformanceObserver' in window) {\n try {\n const po = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'largest-contentful-paint') {\n client.capture('performance', { lcp: entry.startTime })\n }\n if (entry.entryType === 'layout-shift') {\n const ls = entry as unknown as { value?: number; hadRecentInput?: boolean }\n if (ls && !ls.hadRecentInput && typeof ls.value === 'number') {\n client.capture('performance', { cls: ls.value })\n }\n }\n }\n })\n po.observe({ type: 'largest-contentful-paint', buffered: true as unknown as boolean })\n po.observe({ type: 'layout-shift', buffered: true as unknown as boolean })\n } catch {\n // 低版本浏览器忽略\n }\n }\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 网络请求拦截:fetch 与 XHR,采集耗时与状态码\n */\nexport function setupNetworkIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableNetwork) return\n if (typeof window === 'undefined') return\n\n const maxBodyLength = options.networkMaxBodyLength ?? 1000\n\n // fetch 拦截\n const originalFetch = window.fetch\n window.fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const start = performance.now()\n let requestBody: string | undefined\n try {\n // 尝试读取请求 body(不影响原始请求)\n if (args[1]?.body) {\n const body = args[1].body\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n }\n } else if (!args[1] && typeof args[0] !== 'string' && (args[0] as Request).body) {\n try {\n const cloned = (args[0] as Request).clone()\n const text = await cloned.text()\n requestBody = truncateBody(text, maxBodyLength)\n } catch {}\n }\n const res = await originalFetch(...args)\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options)) return res\n if (!isUrlAllowedByPattern(url, options)) return res\n let responseBody: string | undefined\n try {\n const cloned = res.clone()\n const text = await cloned.text()\n responseBody = truncateBody(text, maxBodyLength)\n } catch {}\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: res.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n return res\n } catch (error) {\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options) || !isUrlAllowedByPattern(url, options)) throw error\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: -1,\n duration: end - start,\n error: String(error),\n requestBody\n }\n })\n throw error\n }\n }\n\n // XHR 拦截\n const XHR = window.XMLHttpRequest\n const open = XHR.prototype.open\n const send = XHR.prototype.send\n XHR.prototype.open = function(method: string, url: string, ...rest: any[]) {\n ;(this as any).__monitor__ = { method, url }\n return open.apply(this, [method, url, ...rest] as any)\n }\n XHR.prototype.send = function(body?: Document | XMLHttpRequestBodyInit | null) {\n const meta = (this as any).__monitor__ || {}\n let requestBody: string | undefined\n if (body) {\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n } else if (body instanceof Blob) {\n // Blob 太大,通常只记录类型\n requestBody = truncateBody(`[Blob:${body.type},${body.size}]`, maxBodyLength)\n } else if (body instanceof Document) {\n requestBody = truncateBody(body.documentElement.outerHTML || '', maxBodyLength)\n }\n }\n const start = performance.now()\n this.addEventListener('loadend', () => {\n const end = performance.now()\n if (!isAllowedByHost(meta.url, options) || !isUrlAllowedByPattern(meta.url, options)) return\n let responseBody: string | undefined\n try {\n if (this.responseType === '' || this.responseType === 'text') {\n responseBody = truncateBody(this.responseText || '', maxBodyLength)\n } else if (this.response) {\n // 其他类型(json/blob等)转换为字符串\n responseBody = truncateBody(String(this.response), maxBodyLength)\n }\n } catch {}\n client.capture('network', {\n xhr: {\n url: meta.url,\n method: meta.method,\n status: this.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n })\n return send.call(this, body as any)\n }\n}\n\nfunction isAllowedByHost(inputUrl: string, options: MonitorClientOptions): boolean {\n const allow = options.networkHostsAllowlist\n const block = options.networkHostsBlocklist\n if (!allow || allow.length === 0) return true\n try {\n const u = new URL(inputUrl, typeof location !== 'undefined' ? location.href : 'http://localhost')\n const host = u.hostname\n if (block && block.some(rule => matchHost(host, rule))) return false\n return allow.some(rule => matchHost(host, rule))\n } catch {\n return false\n }\n}\n\nfunction matchHost(host: string, rule: string): boolean {\n if (!rule) return false\n if (rule.startsWith('*.')) {\n const suffix = rule.slice(1) // '.example.com'\n return host.endsWith(suffix)\n }\n return host === rule\n}\n\nfunction isUrlAllowedByPattern(inputUrl: string, options: MonitorClientOptions): boolean {\n const block = options.networkUrlBlockPatterns\n const allow = options.networkUrlAllowPatterns\n try {\n if (block && block.some(p => testPattern(inputUrl, p))) return false\n if (allow && allow.length > 0) {\n return allow.some(p => testPattern(inputUrl, p))\n }\n return true\n } catch {\n return false\n }\n}\n\nfunction testPattern(url: string, pattern: string | RegExp): boolean {\n if (typeof pattern === 'string') {\n // 将字符串作为正则源;默认不加锚,用户可自行提供 ^/$\n try { return new RegExp(pattern).test(url) } catch { return false }\n }\n return pattern.test(url)\n}\n\n/**\n * 截断 body 字符串,超出长度则截断并添加标记\n * @param body 原始 body 字符串\n * @param maxLength 最大长度,<=0 表示不上报\n * @returns 截断后的字符串,或 undefined(不上报)\n */\nfunction truncateBody(body: string, maxLength: number): string | undefined {\n if (maxLength <= 0 || !body) return undefined\n if (body.length <= maxLength) return body\n const marker = '...[truncated]'\n const truncated = body.slice(0, maxLength - marker.length)\n return truncated + marker\n}\n\n\n","import { MonitorClient } from '../core/Client'\nimport type { MonitorClientOptions } from '../core/types'\n\n/**\n * 白屏检测:在页面就绪后延迟一定时间,统计视口内可见元素数量\n * 若可见元素过少(阈值内),则认为存在白屏并上报。\n */\nexport function setupWhiteScreenIntegration(\n client: MonitorClient,\n options: MonitorClientOptions\n): void {\n if (!options.enableWhiteScreen) return\n if (typeof window === 'undefined' || typeof document === 'undefined') return\n\n const delay = options.whiteScreenOptions?.delayMs ?? 3000\n const minVisibleCount = options.whiteScreenOptions?.minVisibleCount ?? 2\n const minArea = options.whiteScreenOptions?.minArea ?? 50 * 50\n const sampleTimes = Math.max(1, options.whiteScreenOptions?.sampleTimes ?? 1)\n const sampleInterval = options.whiteScreenOptions?.sampleIntervalMs ?? 1000\n\n const run = () => {\n setTimeout(() => {\n try {\n multiSample(minArea, sampleTimes, sampleInterval).then(samples => {\n const worst = Math.min(...samples)\n if (worst <= minVisibleCount) {\n const perf = options.whiteScreenOptions?.includePerf === false ? undefined : getPerfSnapshot()\n client.capture('whitescreen', {\n visibleCount: worst,\n samples,\n readyState: document.readyState,\n url: location.href,\n perf\n })\n }\n }).catch(() => {})\n } catch {\n // ignore\n }\n }, delay)\n }\n\n if (document.readyState === 'complete') {\n run()\n } else {\n window.addEventListener('load', run, { once: true })\n }\n}\n\nfunction countVisibleInViewport(minArea: number): number {\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n let count = 0\n const nodes = document.body ? Array.from(document.body.querySelectorAll('*')) as HTMLElement[] : []\n for (const el of nodes) {\n if (!isElementActuallyVisible(el)) continue\n const rect = el.getBoundingClientRect()\n if (rect.width <= 0 || rect.height <= 0) continue\n if (rect.bottom < 0 || rect.right < 0 || rect.top > viewportHeight || rect.left > viewportWidth) continue\n const area = rect.width * rect.height\n if (area >= minArea) count++\n if (count > 10) break // 足够认为非白屏\n }\n return count\n}\n\nfunction isElementActuallyVisible(el: HTMLElement): boolean {\n const style = getComputedStyle(el)\n if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || '1') === 0) return false\n if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'LINK' || el.tagName === 'META') return false\n return true\n}\n\nfunction multiSample(minArea: number, times: number, interval: number): Promise<number[]> {\n const results: number[] = []\n return new Promise(resolve => {\n const tick = (left: number) => {\n results.push(countVisibleInViewport(minArea))\n if (left <= 1) return resolve(results)\n setTimeout(() => tick(left - 1), interval)\n }\n tick(times)\n })\n}\n\nfunction getPerfSnapshot(): Record<string, unknown> | undefined {\n if (typeof performance === 'undefined') return undefined\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n const paints = performance.getEntriesByType('paint') as PerformanceEntry[]\n const fcp = paints.find(p => p.name === 'first-contentful-paint')?.startTime\n const lcpEntries = (performance.getEntriesByType('largest-contentful-paint') as PerformanceEntry[]) || []\n const lcp = lcpEntries.length ? Math.max(...lcpEntries.map(e => e.startTime)) : undefined\n return {\n navigation: nav ? {\n ttfb: nav.responseStart - nav.requestStart,\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime\n } : undefined,\n fcp,\n lcp\n }\n}\n\n\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var P=Object.defineProperty;var q=(t,e)=>()=>(t&&(e=t(t=0)),e);var V=(t,e)=>{for(var n in e)P(t,n,{get:e[n],enumerable:true});};var I={};V(I,{openEventViewer:()=>w});function F(){let t=document.createElement("div");return t.id="__femonitor_viewer__",t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.right="0",t.style.bottom="0",t.style.background="rgba(0,0,0,0.5)",t.style.zIndex="99999",t.style.display="flex",t.style.alignItems="center",t.style.justifyContent="center",t}function A(t){let e=document.createElement("div");return e.style.width="80%",e.style.maxWidth="960px",e.style.maxHeight="80%",e.style.overflow="auto",e.style.background="#fff",e.style.borderRadius="8px",e.style.boxShadow="0 10px 30px rgba(0,0,0,0.2)",e.style.padding="16px",e.innerHTML=t,e}function w(t,e){if(typeof document>"u")return;let n=document.getElementById("__femonitor_viewer__");n&&n.remove();let o=F(),s=t.slice().reverse().map(l=>`<tr>
|
|
2
|
+
<td style="white-space:nowrap">${new Date(l.timestamp).toLocaleString()}</td>
|
|
3
|
+
<td>${l.type}</td>
|
|
4
|
+
<td><pre style="margin:0;white-space:pre-wrap">${K(JSON.stringify(l.context??{},null,2))}</pre></td>
|
|
5
|
+
</tr>`).join(""),a=`
|
|
6
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px">
|
|
7
|
+
<h3 style="margin:0">FE Monitor \u672C\u5730\u4E8B\u4EF6 (${t.length})</h3>
|
|
8
|
+
<div style="display:flex;gap:8px">
|
|
9
|
+
<button id="__fem_close__">\u5173\u95ED</button>
|
|
10
|
+
<button id="__fem_clear__">\u6E05\u7A7A</button>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<table style="width:100%;border-collapse:collapse">
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u65F6\u95F4</th>
|
|
17
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u7C7B\u578B</th>
|
|
18
|
+
<th style="text-align:left;border-bottom:1px solid #eee;padding:8px 4px">\u5185\u5BB9</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
${s||'<tr><td colspan="3" style="padding:12px 4px;color:#666">\u6682\u65E0\u6570\u636E</td></tr>'}
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
`,i=A(a);o.appendChild(i),document.body.appendChild(o),o.addEventListener("click",l=>{l.target===o&&o.remove();});let r=i.querySelector("#__fem_close__");r&&(r.onclick=()=>o.remove());let c=i.querySelector("#__fem_clear__");c&&(c.onclick=()=>{e?.(),o.remove();});}function K(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}var g=q(()=>{});function M(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():"xxxxxxxxyxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return (t==="x"?e:e&3|8).toString(16)})}function O(){if(!(typeof window>"u"))return {width:window.innerWidth,height:window.innerHeight}}function T(){if(!(typeof location>"u"))return location.href}var _={batch:true,batchSize:20,flushIntervalMs:5e3},y=class{constructor(e){this.queue=[];this.endpoint=e.endpoint,this.headers=e.headers??{"Content-Type":"application/json"},this.batch=e.batch??_.batch,this.batchSize=e.batchSize??_.batchSize,this.flushIntervalMs=e.flushIntervalMs??_.flushIntervalMs,this.mode=e.mode??"http",this.localKey=e.localKey??"__FEMONITOR_EVENTS__",this.localMaxItems=e.localMaxItems,this.useBeacon=e.useBeacon??true,this.batch&&this.start(),this.bindPageLifecycle();}enqueue(e){if(!this.batch){this.send([e]);return}this.queue.push(e),this.queue.length>=this.batchSize&&this.flush();}async flush(){if(this.queue.length===0)return;let e=this.queue.splice(0,this.queue.length);await this.send(e);}start(){this.stop(),this.timer=setInterval(()=>{this.flush();},this.flushIntervalMs);}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);}async send(e){try{if(this.mode==="local"||!this.endpoint){this.appendToLocal(e);return}if(this.useBeacon&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"&&this.sendViaBeacon(e))return;await fetch(this.endpoint,{method:"POST",headers:this.headers,body:JSON.stringify({events:e})});}catch{}}sendViaBeacon(e){try{let n=JSON.stringify({events:e}),o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(this.endpoint,o)}catch{return false}}bindPageLifecycle(){if(typeof window>"u")return;let e=()=>{if(this.mode!=="http"||!this.useBeacon||typeof navigator>"u"||typeof navigator.sendBeacon!="function"||this.queue.length===0)return;let n=this.queue.splice(0,this.queue.length);this.sendViaBeacon(n);};window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();}),window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e);}appendToLocal(e){if(typeof localStorage>"u")return;let o=this.getLocal().concat(e);typeof this.localMaxItems=="number"&&this.localMaxItems>0&&o.length>this.localMaxItems&&(o=o.slice(-this.localMaxItems));try{localStorage.setItem(this.localKey,JSON.stringify(o));}catch{}}getLocal(){if(typeof localStorage>"u")return [];let e=localStorage.getItem(this.localKey);if(!e)return [];try{let n=JSON.parse(e);return Array.isArray(n)?n:[]}catch{return []}}listStored(){return this.getLocal()}clearStored(){if(!(typeof localStorage>"u"))try{localStorage.removeItem(this.localKey);}catch{}}};g();var h=class{constructor(e){this.options=e;let n={endpoint:e.dsn,...e.transport};this.transport=new y(n);}capture(e,n){let o={id:M(),type:e,timestamp:Date.now(),appVersion:this.options.appVersion,viewport:O(),page:T(),context:n??{}};this.transport.enqueue(o);}openEventList(){let e=this.transport.listStored();w(e,()=>this.transport.clearStored());}getStoredEvents(){return this.transport.listStored()}clearStoredEvents(){this.transport.clearStored();}};g();function U(t,e){let n=e.split("+").map(c=>c.trim().toLowerCase()),o=n.includes("ctrl")||n.includes("control"),s=n.includes("shift"),a=n.includes("alt"),i=n.find(c=>c!=="ctrl"&&c!=="control"&&c!=="shift"&&c!=="alt"),r=(t.key||"").toLowerCase();return !!o==!!t.ctrlKey&&!!s==!!t.shiftKey&&!!a==!!t.altKey&&(i?r===i:true)}function W(t){return t&&t.trim()?t:"Ctrl+Shift+M"}function B(t,e,n){if(typeof window>"u")return;E();let o=W(n||window.__FEMONITOR_HOTKEY__);window.__FEMONITOR_HOTKEY__=o;let s=a=>{if(U(a,o))try{t();}catch{e();}};window.addEventListener("keydown",s),window.__FEMONITOR_KB_LISTENER__=s;}function E(){if(typeof window>"u")return;let t=window.__FEMONITOR_KB_LISTENER__;t&&window.removeEventListener("keydown",t),window.__FEMONITOR_KB_LISTENER__=void 0;}function C(t,e){e.enableErrors&&(typeof window>"u"||(window.addEventListener("error",n=>{let o=n.error||n.message;t.capture("error",{error:o});},true),window.addEventListener("unhandledrejection",n=>{t.capture("unhandledrejection",{reason:n.reason});})));}function L(t,e){if(!e.enablePerformance||typeof window>"u"||!("performance"in window))return;let n=performance.getEntriesByType("navigation")[0];if(n&&t.capture("performance",{navigation:{domContentLoaded:n.domContentLoadedEventEnd-n.startTime,loadEvent:n.loadEventEnd-n.startTime,ttfb:n.responseStart-n.requestStart}}),"PerformanceObserver"in window)try{let o=new PerformanceObserver(s=>{for(let a of s.getEntries())if(a.entryType==="largest-contentful-paint"&&t.capture("performance",{lcp:a.startTime}),a.entryType==="layout-shift"){let i=a;i&&!i.hadRecentInput&&typeof i.value=="number"&&t.capture("performance",{cls:i.value});}});o.observe({type:"largest-contentful-paint",buffered:!0}),o.observe({type:"layout-shift",buffered:!0});}catch{}}function R(t,e){if(!e.enableNetwork||typeof window>"u")return;let n=e.networkMaxBodyLength??1e3,o=window.fetch;window.fetch=async(...r)=>{let c=performance.now(),l;try{if(r[1]?.body){let f=r[1].body;if(typeof f=="string")l=u(f,n);else if(f instanceof FormData||f instanceof URLSearchParams)try{l=u(String(f),n);}catch{}}else if(!r[1]&&typeof r[0]!="string"&&r[0].body)try{let v=await r[0].clone().text();l=u(v,n);}catch{}let d=await o(...r),m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;if(!b(p,e)||!x(p,e))return d;let S;try{let v=await d.clone().text();S=u(v,n);}catch{}return t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:d.status,duration:m-c,requestBody:l,responseBody:S}}),d}catch(d){let m=performance.now(),p=typeof r[0]=="string"?r[0]:r[0].url;throw !b(p,e)||!x(p,e)||t.capture("network",{fetch:{url:p,method:r[1]?.method??(typeof r[0]!="string"?r[0].method:"GET"),status:-1,duration:m-c,error:String(d),requestBody:l}}),d}};let s=window.XMLHttpRequest,a=s.prototype.open,i=s.prototype.send;s.prototype.open=function(r,c,...l){return this.__monitor__={method:r,url:c},a.apply(this,[r,c,...l])},s.prototype.send=function(r){let c=this.__monitor__||{},l;if(r)if(typeof r=="string")l=u(r,n);else if(r instanceof FormData||r instanceof URLSearchParams)try{l=u(String(r),n);}catch{}else r instanceof Blob?l=u(`[Blob:${r.type},${r.size}]`,n):r instanceof Document&&(l=u(r.documentElement.outerHTML||"",n));let d=performance.now();return this.addEventListener("loadend",()=>{let m=performance.now();if(!b(c.url,e)||!x(c.url,e))return;let p;try{this.responseType===""||this.responseType==="text"?p=u(this.responseText||"",n):this.response&&(p=u(String(this.response),n));}catch{}t.capture("network",{xhr:{url:c.url,method:c.method,status:this.status,duration:m-d,requestBody:l,responseBody:p}});}),i.call(this,r)};}function b(t,e){let n=e.networkHostsAllowlist,o=e.networkHostsBlocklist;if(!n||n.length===0)return true;try{let a=new URL(t,typeof location<"u"?location.href:"http://localhost").hostname;return o&&o.some(i=>k(a,i))?!1:n.some(i=>k(a,i))}catch{return false}}function k(t,e){if(!e)return false;if(e.startsWith("*.")){let n=e.slice(1);return t.endsWith(n)}return t===e}function x(t,e){let n=e.networkUrlBlockPatterns,o=e.networkUrlAllowPatterns;try{return n&&n.some(s=>N(t,s))?!1:o&&o.length>0?o.some(s=>N(t,s)):!0}catch{return false}}function N(t,e){if(typeof e=="string")try{return new RegExp(e).test(t)}catch{return false}return e.test(t)}function u(t,e){if(e<=0||!t)return;if(t.length<=e)return t;let n="...[truncated]";return t.slice(0,e-n.length)+n}function H(t,e){if(!e.enableWhiteScreen||typeof window>"u"||typeof document>"u")return;let n=e.whiteScreenOptions?.delayMs??3e3,o=e.whiteScreenOptions?.minVisibleCount??2,s=e.whiteScreenOptions?.minArea??2500,a=Math.max(1,e.whiteScreenOptions?.sampleTimes??1),i=e.whiteScreenOptions?.sampleIntervalMs??1e3,r=()=>{setTimeout(()=>{try{j(s,a,i).then(c=>{let l=Math.min(...c);if(l<=o){let d=e.whiteScreenOptions?.includePerf===!1?void 0:$();t.capture("whitescreen",{visibleCount:l,samples:c,readyState:document.readyState,url:location.href,perf:d});}}).catch(()=>{});}catch{}},n);};document.readyState==="complete"?r():window.addEventListener("load",r,{once:true});}function D(t){let e=window.innerWidth,n=window.innerHeight,o=0,s=document.body?Array.from(document.body.querySelectorAll("*")):[];for(let a of s){if(!z(a))continue;let i=a.getBoundingClientRect();if(i.width<=0||i.height<=0||i.bottom<0||i.right<0||i.top>n||i.left>e)continue;if(i.width*i.height>=t&&o++,o>10)break}return o}function z(t){let e=getComputedStyle(t);return !(e.display==="none"||e.visibility==="hidden"||parseFloat(e.opacity||"1")===0||t.tagName==="SCRIPT"||t.tagName==="STYLE"||t.tagName==="LINK"||t.tagName==="META")}function j(t,e,n){let o=[];return new Promise(s=>{let a=i=>{if(o.push(D(t)),i<=1)return s(o);setTimeout(()=>a(i-1),n);};a(e);})}function $(){if(typeof performance>"u")return;let t=performance.getEntriesByType("navigation")[0],n=performance.getEntriesByType("paint").find(a=>a.name==="first-contentful-paint")?.startTime,o=performance.getEntriesByType("largest-contentful-paint")||[],s=o.length?Math.max(...o.map(a=>a.startTime)):void 0;return {navigation:t?{ttfb:t.responseStart-t.requestStart,domContentLoaded:t.domContentLoadedEventEnd-t.startTime,loadEvent:t.loadEventEnd-t.startTime}:void 0,fcp:n,lcp:s}}function J(t){let e=new h(t);return C(e,t),L(e,t),R(e,t),H(e,t),e}if(typeof window<"u"&&window.__FEMONITOR__&&!window.__FEMONITOR_INSTANCE__)try{window.__FEMONITOR_INSTANCE__=J(window.__FEMONITOR__);}catch{}function he(t){typeof window>"u"||B(()=>{let e=window.__FEMONITOR_INSTANCE__;e&&typeof e.openEventList=="function"&&e.openEventList();},()=>{let e="__FEMONITOR_EVENTS__",n=typeof localStorage<"u"?localStorage.getItem(e):null,o=n?JSON.parse(n):[];Promise.resolve().then(()=>(g(),I)).then(s=>{s.openEventViewer(Array.isArray(o)?o:[],()=>{try{localStorage.removeItem(e);}catch{}});}).catch(()=>{});},t);}function ye(){E();}
|
|
26
|
+
export{h as MonitorClient,he as bindViewerHotkeyDefault,J as initMonitor,w as openEventViewer,ye as unbindViewerHotkeyDefault};//# sourceMappingURL=index.js.map
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/viewer.ts","../src/utils/uid.ts","../src/utils/env.ts","../src/core/Transport.ts","../src/core/Client.ts","../src/index.ts","../src/utils/hotkey.ts","../src/integrations/errors.ts","../src/integrations/perf.ts","../src/integrations/network.ts","../src/integrations/whitescreen.ts"],"names":["viewer_exports","__export","openEventViewer","createContainer","el","createPanel","html","panel","events","onClear","existing","container","rows","e","escapeHtml","btnClose","btnClear","str","init_viewer","__esmMin","createUid","c","r","getViewport","getPageUrl","DEFAULTS","Transport","options","event","payload","blob","flushWithBeacon","merged","raw","arr","MonitorClient","transportOptions","type","data","matchesHotkey","hotkey","parts","s","needCtrl","needShift","needAlt","keyPart","p","pressedKey","normalizeHotkey","bindViewerHotkey","open","openFallback","unbindViewerHotkey","finalHotkey","listener","prev","setupErrorIntegration","client","error","setupPerformanceIntegration","nav","po","list","entry","ls","setupNetworkIntegration","maxBodyLength","originalFetch","args","start","requestBody","body","truncateBody","text","res","end","url","isAllowedByHost","isUrlAllowedByPattern","responseBody","XHR","send","method","rest","meta","inputUrl","allow","block","host","rule","matchHost","suffix","testPattern","pattern","maxLength","marker","setupWhiteScreenIntegration","delay","minVisibleCount","minArea","sampleTimes","sampleInterval","run","multiSample","samples","worst","perf","getPerfSnapshot","countVisibleInViewport","viewportWidth","viewportHeight","count","nodes","isElementActuallyVisible","rect","style","times","interval","results","resolve","tick","left","fcp","lcpEntries","lcp","initMonitor","bindViewerHotkeyDefault","inst","key","mod","unbindViewerHotkeyDefault"],"mappings":"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","file":"index.js","sourcesContent":["import type { BaseEvent } from '../core/types'\n\n/** 创建遮罩容器 */\nfunction createContainer(): HTMLElement {\n const el = document.createElement('div')\n el.id = '__femonitor_viewer__'\n el.style.position = 'fixed'\n el.style.top = '0'\n el.style.left = '0'\n el.style.right = '0'\n el.style.bottom = '0'\n el.style.background = 'rgba(0,0,0,0.5)'\n el.style.zIndex = '99999'\n el.style.display = 'flex'\n el.style.alignItems = 'center'\n el.style.justifyContent = 'center'\n return el\n}\n\n/** 创建面板节点并填充 HTML */\nfunction createPanel(html: string): HTMLElement {\n const panel = document.createElement('div')\n panel.style.width = '80%'\n panel.style.maxWidth = '960px'\n panel.style.maxHeight = '80%'\n panel.style.overflow = 'auto'\n panel.style.background = '#fff'\n panel.style.borderRadius = '8px'\n panel.style.boxShadow = '0 10px 30px rgba(0,0,0,0.2)'\n panel.style.padding = '16px'\n panel.innerHTML = html\n return panel\n}\n\n/**\n * 打开事件查看器覆盖层\n * @param events 要展示的事件数组\n * @param onClear 点击清空后的回调\n */\nexport function openEventViewer(events: BaseEvent[], onClear?: () => void): void {\n if (typeof document === 'undefined') return\n const existing = document.getElementById('__femonitor_viewer__')\n if (existing) existing.remove()\n\n const container = createContainer()\n const rows = events\n .slice()\n .reverse()\n .map(e => `<tr>\n <td style=\"white-space:nowrap\">${new Date(e.timestamp).toLocaleString()}</td>\n <td>${e.type}</td>\n <td><pre style=\"margin:0;white-space:pre-wrap\">${escapeHtml(JSON.stringify(e.context ?? {}, null, 2))}</pre></td>\n </tr>`)\n .join('')\n\n const html = `\n <div style=\"display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px\">\n <h3 style=\"margin:0\">FE Monitor 本地事件 (${events.length})</h3>\n <div style=\"display:flex;gap:8px\">\n <button id=\"__fem_close__\">关闭</button>\n <button id=\"__fem_clear__\">清空</button>\n </div>\n </div>\n <table style=\"width:100%;border-collapse:collapse\">\n <thead>\n <tr>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">时间</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">类型</th>\n <th style=\"text-align:left;border-bottom:1px solid #eee;padding:8px 4px\">内容</th>\n </tr>\n </thead>\n <tbody>\n ${rows || '<tr><td colspan=\"3\" style=\"padding:12px 4px;color:#666\">暂无数据</td></tr>'}\n </tbody>\n </table>\n `\n\n const panel = createPanel(html)\n container.appendChild(panel)\n document.body.appendChild(container)\n\n container.addEventListener('click', (e) => {\n if (e.target === container) container.remove()\n })\n const btnClose = panel.querySelector('#__fem_close__') as HTMLButtonElement | null\n if (btnClose) btnClose.onclick = () => container.remove()\n\n const btnClear = panel.querySelector('#__fem_clear__') as HTMLButtonElement | null\n if (btnClear) btnClear.onclick = () => {\n onClear?.()\n container.remove()\n }\n}\n\n/** 简单的 HTML 转义 */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n\n","/** 生成简单唯一 ID(优先使用 crypto.randomUUID) */\nexport function createUid(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID()\n }\n return 'xxxxxxxxyxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n\n","/** 获取窗口视口尺寸 */\nexport function getViewport(): { width: number; height: number } | undefined {\n if (typeof window === 'undefined') return undefined\n return { width: window.innerWidth, height: window.innerHeight }\n}\n\n/** 获取当前页面 URL */\nexport function getPageUrl(): string | undefined {\n if (typeof location === 'undefined') return undefined\n return location.href\n}\n\n\n","import type { BaseEvent, TransportOptions } from './types'\n\nconst DEFAULTS: Required<Pick<TransportOptions, 'batch' | 'batchSize' | 'flushIntervalMs'>> = {\n batch: true,\n batchSize: 20,\n flushIntervalMs: 5000\n}\n\n/**\n * 负责事件的缓存、批量与传输(HTTP/localStorage)\n */\nexport class Transport {\n private readonly endpoint: string\n private readonly headers: Record<string, string>\n private readonly batch: boolean\n private readonly batchSize: number\n private readonly flushIntervalMs: number\n private readonly mode: 'http' | 'local'\n private readonly localKey: string\n private readonly localMaxItems: number | undefined\n private readonly useBeacon: boolean\n private queue: BaseEvent[] = []\n private timer: number | undefined\n\n /**\n * @param options 传输层配置\n */\n constructor(options: TransportOptions) {\n this.endpoint = options.endpoint\n this.headers = options.headers ?? { 'Content-Type': 'application/json' }\n this.batch = options.batch ?? DEFAULTS.batch\n this.batchSize = options.batchSize ?? DEFAULTS.batchSize\n this.flushIntervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs\n this.mode = options.mode ?? 'http'\n this.localKey = options.localKey ?? '__FEMONITOR_EVENTS__'\n this.localMaxItems = options.localMaxItems\n this.useBeacon = options.useBeacon ?? true\n if (this.batch) this.start()\n this.bindPageLifecycle()\n }\n\n /**\n * 入队一个事件;当非批量模式下立即发送\n */\n enqueue(event: BaseEvent): void {\n if (!this.batch) {\n void this.send([event])\n return\n }\n this.queue.push(event)\n if (this.queue.length >= this.batchSize) {\n void this.flush()\n }\n }\n\n /**\n * 发送并清空当前队列\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n await this.send(payload)\n }\n\n private start(): void {\n this.stop()\n this.timer = (setInterval(() => {\n void this.flush()\n }, this.flushIntervalMs) as unknown) as number\n }\n\n private stop(): void {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n }\n\n /**\n * 实际发送逻辑:优先 local、其后 sendBeacon、最后 fetch\n */\n private async send(events: BaseEvent[]): Promise<void> {\n try {\n if (this.mode === 'local' || !this.endpoint) {\n this.appendToLocal(events)\n return\n }\n if (this.useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const ok = this.sendViaBeacon(events)\n if (ok) return\n // 如果 sendBeacon 返回 false,回退到 fetch\n }\n await fetch(this.endpoint, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({ events })\n })\n } catch {\n // 忽略网络错误,避免影响业务\n }\n }\n\n /**\n * 使用 sendBeacon 发送;返回是否成功排队\n */\n private sendViaBeacon(events: BaseEvent[]): boolean {\n try {\n const payload = JSON.stringify({ events })\n const blob = new Blob([payload], { type: 'application/json' })\n // sendBeacon 不支持自定义 headers;若服务端依赖 headers,请在收端做兼容\n return navigator.sendBeacon(this.endpoint, blob)\n } catch {\n return false\n }\n }\n\n /**\n * 绑定页面生命周期,在隐藏/卸载时使用 sendBeacon flush\n */\n private bindPageLifecycle(): void {\n if (typeof window === 'undefined') return\n const flushWithBeacon = () => {\n if (this.mode !== 'http') return\n if (!this.useBeacon || typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') return\n if (this.queue.length === 0) return\n const payload = this.queue.splice(0, this.queue.length)\n this.sendViaBeacon(payload)\n }\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushWithBeacon()\n })\n window.addEventListener('pagehide', flushWithBeacon)\n window.addEventListener('beforeunload', flushWithBeacon)\n }\n\n /**\n * 追加事件到 localStorage(受最大条数限制)\n */\n private appendToLocal(events: BaseEvent[]): void {\n if (typeof localStorage === 'undefined') return\n const current = this.getLocal() \n let merged = current.concat(events)\n if (typeof this.localMaxItems === 'number' && this.localMaxItems > 0 && merged.length > this.localMaxItems) {\n merged = merged.slice(-this.localMaxItems)\n }\n try {\n localStorage.setItem(this.localKey, JSON.stringify(merged))\n } catch {}\n }\n\n /** 获取本地缓存事件 */\n private getLocal(): BaseEvent[] {\n if (typeof localStorage === 'undefined') return []\n const raw = localStorage.getItem(this.localKey)\n if (!raw) return []\n try {\n const arr = JSON.parse(raw)\n return Array.isArray(arr) ? arr : []\n } catch {\n return []\n }\n }\n\n /** 列出本地缓存事件(仅本地模式使用) */\n listStored(): BaseEvent[] {\n return this.getLocal()\n }\n\n /** 清空本地缓存事件 */\n clearStored(): void {\n if (typeof localStorage === 'undefined') return\n try { localStorage.removeItem(this.localKey) } catch {}\n }\n}\n\n\n","import { createUid } from '../utils/uid'\nimport { getPageUrl, getViewport } from '../utils/env'\nimport type { BaseEvent, MonitorClientOptions, TransportOptions } from './types'\nimport { Transport } from './Transport'\nimport { openEventViewer } from '../ui/viewer'\n\n/**\n * 监控客户端:负责构建事件与调用传输层上报\n */\nexport class MonitorClient {\n private readonly options: MonitorClientOptions\n private readonly transport: Transport\n\n /**\n * @param options SDK 初始化参数\n */\n constructor(options: MonitorClientOptions) {\n this.options = options\n const transportOptions: TransportOptions = {\n endpoint: options.dsn,\n ...options.transport\n } as TransportOptions\n this.transport = new Transport(transportOptions)\n }\n\n /**\n * 采集一个事件\n * @param type 事件类型\n * @param data 自定义上下文数据\n */\n capture(type: BaseEvent['type'], data?: Omit<BaseEvent, 'id' | 'type' | 'timestamp' | 'viewport' | 'page'>['context']): void {\n const event: BaseEvent = {\n id: createUid(),\n type,\n timestamp: Date.now(),\n appVersion: this.options.appVersion,\n viewport: getViewport(),\n page: getPageUrl(),\n context: data ?? {}\n }\n this.transport.enqueue(event)\n }\n\n /** 打开本地事件列表查看器 */\n openEventList(): void {\n const events = this.transport.listStored()\n openEventViewer(events, () => this.transport.clearStored())\n }\n\n /** 获取本地缓存事件(仅本地模式有效) */\n getStoredEvents(): BaseEvent[] {\n return this.transport.listStored()\n }\n\n /** 清空本地缓存事件(仅本地模式有效) */\n clearStoredEvents(): void {\n this.transport.clearStored()\n }\n}\n\n\n","export { MonitorClient } from './core/Client'\nexport type { MonitorClientOptions, BaseEvent, TransportOptions } from './core/types'\nexport { openEventViewer } from './ui/viewer'\nimport type { MonitorClientOptions as Options } from './core/types'\nimport { bindViewerHotkey, unbindViewerHotkey } from './utils/hotkey'\nimport { setupErrorIntegration } from './integrations/errors'\nimport { setupPerformanceIntegration } from './integrations/perf'\nimport { setupNetworkIntegration } from './integrations/network'\nimport { setupWhiteScreenIntegration } from './integrations/whitescreen'\nimport { MonitorClient } from './core/Client'\n\n/**\n * 初始化 SDK 并按配置启用集成\n */\nexport function initMonitor(options: Options): MonitorClient {\n const client = new MonitorClient(options)\n setupErrorIntegration(client, options)\n setupPerformanceIntegration(client, options)\n setupNetworkIntegration(client, options)\n setupWhiteScreenIntegration(client, options)\n return client\n}\n\n// 可选的全局自动初始化:当页面上提前定义 window.__FEMONITOR__ 时生效\ndeclare global {\n interface Window {\n __FEMONITOR__?: Options\n __FEMONITOR_INSTANCE__?: MonitorClient\n }\n}\n\nif (typeof window !== 'undefined' && window.__FEMONITOR__ && !window.__FEMONITOR_INSTANCE__) {\n try {\n window.__FEMONITOR_INSTANCE__ = initMonitor(window.__FEMONITOR__)\n } catch {}\n}\n\n\n// 手动绑定/解绑快捷键 API\n/**\n * 绑定默认的查看器快捷键处理(对外导出)\n * @param hotkey 自定义快捷键(如 'Alt+K'),不传则为默认\n */\nexport function bindViewerHotkeyDefault(hotkey?: string): void {\n if (typeof window === 'undefined') return\n bindViewerHotkey(\n () => {\n const inst = window.__FEMONITOR_INSTANCE__ as any\n if (inst && typeof inst.openEventList === 'function') inst.openEventList()\n },\n () => {\n const key = '__FEMONITOR_EVENTS__'\n const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null\n const events = raw ? JSON.parse(raw) : []\n import('./ui/viewer').then(mod => {\n mod.openEventViewer(Array.isArray(events) ? events : [], () => {\n try { localStorage.removeItem(key) } catch {}\n })\n }).catch(() => {})\n },\n hotkey\n )\n}\n\n/** 解绑默认的查看器快捷键处理 */\nexport function unbindViewerHotkeyDefault(): void {\n unbindViewerHotkey()\n}\n\n","/**\n * 判断键盘事件是否匹配快捷键,如 'Ctrl+Shift+M'\n */\nexport function matchesHotkey(event: KeyboardEvent, hotkey: string): boolean {\n // 支持形如 'Ctrl+Shift+M'、'Alt+K'(大小写不敏感)\n const parts = hotkey.split('+').map(s => s.trim().toLowerCase())\n const needCtrl = parts.includes('ctrl') || parts.includes('control')\n const needShift = parts.includes('shift')\n const needAlt = parts.includes('alt')\n const keyPart = parts.find(p => p !== 'ctrl' && p !== 'control' && p !== 'shift' && p !== 'alt')\n const pressedKey = (event.key || '').toLowerCase()\n return (\n (!!needCtrl === !!event.ctrlKey) &&\n (!!needShift === !!event.shiftKey) &&\n (!!needAlt === !!event.altKey) &&\n (!!keyPart ? pressedKey === keyPart : true)\n )\n}\n\n/** 归一化快捷键(默认 'Ctrl+Shift+M') */\nexport function normalizeHotkey(hotkey?: string): string {\n return hotkey && hotkey.trim() ? hotkey : 'Ctrl+Shift+M'\n}\n\nexport type HotkeyListener = (e: KeyboardEvent) => void\n\ndeclare global {\n interface Window {\n __FEMONITOR_KB_LISTENER__?: HotkeyListener\n __FEMONITOR_HOTKEY__?: string\n }\n}\n\n/**\n * 绑定查看器快捷键\n * @param hotkey 自定义快捷键,不传则沿用上一次或默认\n * @param open 优先使用已有实例打开\n * @param openFallback 无实例时从本地读取并打开\n */\nexport function bindViewerHotkey(open: () => void, openFallback: () => void, hotkey?: string): void {\n if (typeof window === 'undefined') return\n unbindViewerHotkey()\n const finalHotkey = normalizeHotkey(hotkey || window.__FEMONITOR_HOTKEY__)\n window.__FEMONITOR_HOTKEY__ = finalHotkey\n const listener: HotkeyListener = (e) => {\n if (matchesHotkey(e, finalHotkey)) {\n try { open() } catch { openFallback() }\n }\n }\n window.addEventListener('keydown', listener)\n window.__FEMONITOR_KB_LISTENER__ = listener\n}\n\n/** 解绑查看器快捷键 */\nexport function unbindViewerHotkey(): void {\n if (typeof window === 'undefined') return\n const prev = window.__FEMONITOR_KB_LISTENER__\n if (prev) window.removeEventListener('keydown', prev)\n window.__FEMONITOR_KB_LISTENER__ = undefined\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 错误与未捕获 Promise 监听\n */\nexport function setupErrorIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableErrors) return\n if (typeof window === 'undefined') return\n\n window.addEventListener('error', (event) => {\n const error = event.error || event.message\n client.capture('error', { error })\n }, true)\n\n window.addEventListener('unhandledrejection', (event) => {\n client.capture('unhandledrejection', { reason: (event as PromiseRejectionEvent).reason })\n })\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 性能监控采集:navigation timing、LCP、CLS\n */\nexport function setupPerformanceIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enablePerformance) return\n if (typeof window === 'undefined' || !('performance' in window)) return\n\n // 基础导航时间\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n if (nav) {\n client.capture('performance', {\n navigation: {\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime,\n ttfb: nav.responseStart - nav.requestStart\n }\n })\n }\n\n // Web Vitals: LCP/CLS(尽量使用 PerformanceObserver)\n if ('PerformanceObserver' in window) {\n try {\n const po = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'largest-contentful-paint') {\n client.capture('performance', { lcp: entry.startTime })\n }\n if (entry.entryType === 'layout-shift') {\n const ls = entry as unknown as { value?: number; hadRecentInput?: boolean }\n if (ls && !ls.hadRecentInput && typeof ls.value === 'number') {\n client.capture('performance', { cls: ls.value })\n }\n }\n }\n })\n po.observe({ type: 'largest-contentful-paint', buffered: true as unknown as boolean })\n po.observe({ type: 'layout-shift', buffered: true as unknown as boolean })\n } catch {\n // 低版本浏览器忽略\n }\n }\n}\n\n\n","import type { MonitorClientOptions } from '../core/types'\nimport { MonitorClient } from '../core/Client'\n\n/**\n * 网络请求拦截:fetch 与 XHR,采集耗时与状态码\n */\nexport function setupNetworkIntegration(client: MonitorClient, options: MonitorClientOptions): void {\n if (!options.enableNetwork) return\n if (typeof window === 'undefined') return\n\n const maxBodyLength = options.networkMaxBodyLength ?? 1000\n\n // fetch 拦截\n const originalFetch = window.fetch\n window.fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const start = performance.now()\n let requestBody: string | undefined\n try {\n // 尝试读取请求 body(不影响原始请求)\n if (args[1]?.body) {\n const body = args[1].body\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n }\n } else if (!args[1] && typeof args[0] !== 'string' && (args[0] as Request).body) {\n try {\n const cloned = (args[0] as Request).clone()\n const text = await cloned.text()\n requestBody = truncateBody(text, maxBodyLength)\n } catch {}\n }\n const res = await originalFetch(...args)\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options)) return res\n if (!isUrlAllowedByPattern(url, options)) return res\n let responseBody: string | undefined\n try {\n const cloned = res.clone()\n const text = await cloned.text()\n responseBody = truncateBody(text, maxBodyLength)\n } catch {}\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: res.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n return res\n } catch (error) {\n const end = performance.now()\n const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url\n if (!isAllowedByHost(url, options) || !isUrlAllowedByPattern(url, options)) throw error\n client.capture('network', {\n fetch: {\n url,\n method: (args[1]?.method ?? (typeof args[0] !== 'string' ? (args[0] as Request).method : 'GET')),\n status: -1,\n duration: end - start,\n error: String(error),\n requestBody\n }\n })\n throw error\n }\n }\n\n // XHR 拦截\n const XHR = window.XMLHttpRequest\n const open = XHR.prototype.open\n const send = XHR.prototype.send\n XHR.prototype.open = function(method: string, url: string, ...rest: any[]) {\n ;(this as any).__monitor__ = { method, url }\n return open.apply(this, [method, url, ...rest] as any)\n }\n XHR.prototype.send = function(body?: Document | XMLHttpRequestBodyInit | null) {\n const meta = (this as any).__monitor__ || {}\n let requestBody: string | undefined\n if (body) {\n if (typeof body === 'string') {\n requestBody = truncateBody(body, maxBodyLength)\n } else if (body instanceof FormData || body instanceof URLSearchParams) {\n try {\n requestBody = truncateBody(String(body), maxBodyLength)\n } catch {}\n } else if (body instanceof Blob) {\n // Blob 太大,通常只记录类型\n requestBody = truncateBody(`[Blob:${body.type},${body.size}]`, maxBodyLength)\n } else if (body instanceof Document) {\n requestBody = truncateBody(body.documentElement.outerHTML || '', maxBodyLength)\n }\n }\n const start = performance.now()\n this.addEventListener('loadend', () => {\n const end = performance.now()\n if (!isAllowedByHost(meta.url, options) || !isUrlAllowedByPattern(meta.url, options)) return\n let responseBody: string | undefined\n try {\n if (this.responseType === '' || this.responseType === 'text') {\n responseBody = truncateBody(this.responseText || '', maxBodyLength)\n } else if (this.response) {\n // 其他类型(json/blob等)转换为字符串\n responseBody = truncateBody(String(this.response), maxBodyLength)\n }\n } catch {}\n client.capture('network', {\n xhr: {\n url: meta.url,\n method: meta.method,\n status: this.status,\n duration: end - start,\n requestBody,\n responseBody\n }\n })\n })\n return send.call(this, body as any)\n }\n}\n\nfunction isAllowedByHost(inputUrl: string, options: MonitorClientOptions): boolean {\n const allow = options.networkHostsAllowlist\n const block = options.networkHostsBlocklist\n if (!allow || allow.length === 0) return true\n try {\n const u = new URL(inputUrl, typeof location !== 'undefined' ? location.href : 'http://localhost')\n const host = u.hostname\n if (block && block.some(rule => matchHost(host, rule))) return false\n return allow.some(rule => matchHost(host, rule))\n } catch {\n return false\n }\n}\n\nfunction matchHost(host: string, rule: string): boolean {\n if (!rule) return false\n if (rule.startsWith('*.')) {\n const suffix = rule.slice(1) // '.example.com'\n return host.endsWith(suffix)\n }\n return host === rule\n}\n\nfunction isUrlAllowedByPattern(inputUrl: string, options: MonitorClientOptions): boolean {\n const block = options.networkUrlBlockPatterns\n const allow = options.networkUrlAllowPatterns\n try {\n if (block && block.some(p => testPattern(inputUrl, p))) return false\n if (allow && allow.length > 0) {\n return allow.some(p => testPattern(inputUrl, p))\n }\n return true\n } catch {\n return false\n }\n}\n\nfunction testPattern(url: string, pattern: string | RegExp): boolean {\n if (typeof pattern === 'string') {\n // 将字符串作为正则源;默认不加锚,用户可自行提供 ^/$\n try { return new RegExp(pattern).test(url) } catch { return false }\n }\n return pattern.test(url)\n}\n\n/**\n * 截断 body 字符串,超出长度则截断并添加标记\n * @param body 原始 body 字符串\n * @param maxLength 最大长度,<=0 表示不上报\n * @returns 截断后的字符串,或 undefined(不上报)\n */\nfunction truncateBody(body: string, maxLength: number): string | undefined {\n if (maxLength <= 0 || !body) return undefined\n if (body.length <= maxLength) return body\n const marker = '...[truncated]'\n const truncated = body.slice(0, maxLength - marker.length)\n return truncated + marker\n}\n\n\n","import { MonitorClient } from '../core/Client'\nimport type { MonitorClientOptions } from '../core/types'\n\n/**\n * 白屏检测:在页面就绪后延迟一定时间,统计视口内可见元素数量\n * 若可见元素过少(阈值内),则认为存在白屏并上报。\n */\nexport function setupWhiteScreenIntegration(\n client: MonitorClient,\n options: MonitorClientOptions\n): void {\n if (!options.enableWhiteScreen) return\n if (typeof window === 'undefined' || typeof document === 'undefined') return\n\n const delay = options.whiteScreenOptions?.delayMs ?? 3000\n const minVisibleCount = options.whiteScreenOptions?.minVisibleCount ?? 2\n const minArea = options.whiteScreenOptions?.minArea ?? 50 * 50\n const sampleTimes = Math.max(1, options.whiteScreenOptions?.sampleTimes ?? 1)\n const sampleInterval = options.whiteScreenOptions?.sampleIntervalMs ?? 1000\n\n const run = () => {\n setTimeout(() => {\n try {\n multiSample(minArea, sampleTimes, sampleInterval).then(samples => {\n const worst = Math.min(...samples)\n if (worst <= minVisibleCount) {\n const perf = options.whiteScreenOptions?.includePerf === false ? undefined : getPerfSnapshot()\n client.capture('whitescreen', {\n visibleCount: worst,\n samples,\n readyState: document.readyState,\n url: location.href,\n perf\n })\n }\n }).catch(() => {})\n } catch {\n // ignore\n }\n }, delay)\n }\n\n if (document.readyState === 'complete') {\n run()\n } else {\n window.addEventListener('load', run, { once: true })\n }\n}\n\nfunction countVisibleInViewport(minArea: number): number {\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n let count = 0\n const nodes = document.body ? Array.from(document.body.querySelectorAll('*')) as HTMLElement[] : []\n for (const el of nodes) {\n if (!isElementActuallyVisible(el)) continue\n const rect = el.getBoundingClientRect()\n if (rect.width <= 0 || rect.height <= 0) continue\n if (rect.bottom < 0 || rect.right < 0 || rect.top > viewportHeight || rect.left > viewportWidth) continue\n const area = rect.width * rect.height\n if (area >= minArea) count++\n if (count > 10) break // 足够认为非白屏\n }\n return count\n}\n\nfunction isElementActuallyVisible(el: HTMLElement): boolean {\n const style = getComputedStyle(el)\n if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || '1') === 0) return false\n if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'LINK' || el.tagName === 'META') return false\n return true\n}\n\nfunction multiSample(minArea: number, times: number, interval: number): Promise<number[]> {\n const results: number[] = []\n return new Promise(resolve => {\n const tick = (left: number) => {\n results.push(countVisibleInViewport(minArea))\n if (left <= 1) return resolve(results)\n setTimeout(() => tick(left - 1), interval)\n }\n tick(times)\n })\n}\n\nfunction getPerfSnapshot(): Record<string, unknown> | undefined {\n if (typeof performance === 'undefined') return undefined\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined\n const paints = performance.getEntriesByType('paint') as PerformanceEntry[]\n const fcp = paints.find(p => p.name === 'first-contentful-paint')?.startTime\n const lcpEntries = (performance.getEntriesByType('largest-contentful-paint') as PerformanceEntry[]) || []\n const lcp = lcpEntries.length ? Math.max(...lcpEntries.map(e => e.startTime)) : undefined\n return {\n navigation: nav ? {\n ttfb: nav.responseStart - nav.requestStart,\n domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,\n loadEvent: nav.loadEventEnd - nav.startTime\n } : undefined,\n fcp,\n lcp\n }\n}\n\n\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@w57124/exs-monitor-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "前端监控 SDK:错误上报、性能指标、网络拦截",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"monitoring",
|
|
7
|
+
"frontend",
|
|
8
|
+
"sdk",
|
|
9
|
+
"performance",
|
|
10
|
+
"error-reporting"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": ""
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/index.cjs",
|
|
20
|
+
"module": "dist/index.mjs",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.mjs",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"unpkg": "dist/index.global.js",
|
|
30
|
+
"jsdelivr": "dist/index.global.js",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.1.0",
|
|
40
|
+
"typescript": "^5.4.0",
|
|
41
|
+
"rimraf": "^5.0.5",
|
|
42
|
+
"typedoc": "^0.25.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"clean": "rimraf dist",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"docs": "typedoc",
|
|
53
|
+
"docs:clean": "rimraf docs"
|
|
54
|
+
}
|
|
55
|
+
}
|