feat: 新增trace功能
This commit is contained in:
parent
c7e8e24332
commit
8c7e5947cd
|
|
@ -0,0 +1,224 @@
|
||||||
|
body, html {
|
||||||
|
margin:0; padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Helvetica Neue, Helvetica, Arial;
|
||||||
|
font-size: 14px;
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
.small { font-size: 12px; }
|
||||||
|
*, *:after, *:before {
|
||||||
|
-webkit-box-sizing:border-box;
|
||||||
|
-moz-box-sizing:border-box;
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
h1 { font-size: 20px; margin: 0;}
|
||||||
|
h2 { font-size: 14px; }
|
||||||
|
pre {
|
||||||
|
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-moz-tab-size: 2;
|
||||||
|
-o-tab-size: 2;
|
||||||
|
tab-size: 2;
|
||||||
|
}
|
||||||
|
a { color:#0074D9; text-decoration:none; }
|
||||||
|
a:hover { text-decoration:underline; }
|
||||||
|
.strong { font-weight: bold; }
|
||||||
|
.space-top1 { padding: 10px 0 0 0; }
|
||||||
|
.pad2y { padding: 20px 0; }
|
||||||
|
.pad1y { padding: 10px 0; }
|
||||||
|
.pad2x { padding: 0 20px; }
|
||||||
|
.pad2 { padding: 20px; }
|
||||||
|
.pad1 { padding: 10px; }
|
||||||
|
.space-left2 { padding-left:55px; }
|
||||||
|
.space-right2 { padding-right:20px; }
|
||||||
|
.center { text-align:center; }
|
||||||
|
.clearfix { display:block; }
|
||||||
|
.clearfix:after {
|
||||||
|
content:'';
|
||||||
|
display:block;
|
||||||
|
height:0;
|
||||||
|
clear:both;
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
.fl { float: left; }
|
||||||
|
@media only screen and (max-width:640px) {
|
||||||
|
.col3 { width:100%; max-width:100%; }
|
||||||
|
.hide-mobile { display:none!important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiet {
|
||||||
|
color: #7f7f7f;
|
||||||
|
color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.quiet a { opacity: 0.7; }
|
||||||
|
|
||||||
|
.fraction {
|
||||||
|
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
background: #E8E8E8;
|
||||||
|
padding: 4px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.path a:link, div.path a:visited { color: #333; }
|
||||||
|
table.coverage {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.coverage td {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
table.coverage td.line-count {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 5px 0 20px;
|
||||||
|
}
|
||||||
|
table.coverage td.line-coverage {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
min-width:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.coverage td span.cline-any {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.missing-if-branch {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 4px;
|
||||||
|
background: #333;
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-if-branch {
|
||||||
|
display: none;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 4px;
|
||||||
|
background: #ccc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.missing-if-branch .typ, .skip-if-branch .typ {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
.coverage-summary {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
||||||
|
.keyline-all { border: 1px solid #ddd; }
|
||||||
|
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
||||||
|
.coverage-summary tbody { border: 1px solid #bbb; }
|
||||||
|
.coverage-summary td { border-right: 1px solid #bbb; }
|
||||||
|
.coverage-summary td:last-child { border-right: none; }
|
||||||
|
.coverage-summary th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.coverage-summary th.file { border-right: none !important; }
|
||||||
|
.coverage-summary th.pct { }
|
||||||
|
.coverage-summary th.pic,
|
||||||
|
.coverage-summary th.abs,
|
||||||
|
.coverage-summary td.pct,
|
||||||
|
.coverage-summary td.abs { text-align: right; }
|
||||||
|
.coverage-summary td.file { white-space: nowrap; }
|
||||||
|
.coverage-summary td.pic { min-width: 120px !important; }
|
||||||
|
.coverage-summary tfoot td { }
|
||||||
|
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
height: 10px;
|
||||||
|
width: 7px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
||||||
|
}
|
||||||
|
.coverage-summary .sorted .sorter {
|
||||||
|
background-position: 0 -20px;
|
||||||
|
}
|
||||||
|
.coverage-summary .sorted-desc .sorter {
|
||||||
|
background-position: 0 -10px;
|
||||||
|
}
|
||||||
|
.status-line { height: 10px; }
|
||||||
|
/* yellow */
|
||||||
|
.cbranch-no { background: yellow !important; color: #111; }
|
||||||
|
/* dark red */
|
||||||
|
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
||||||
|
.low .chart { border:1px solid #C21F39 }
|
||||||
|
.highlighted,
|
||||||
|
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
||||||
|
background: #C21F39 !important;
|
||||||
|
}
|
||||||
|
/* medium red */
|
||||||
|
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
||||||
|
/* light red */
|
||||||
|
.low, .cline-no { background:#FCE1E5 }
|
||||||
|
/* light green */
|
||||||
|
.high, .cline-yes { background:rgb(230,245,208) }
|
||||||
|
/* medium green */
|
||||||
|
.cstat-yes { background:rgb(161,215,106) }
|
||||||
|
/* dark green */
|
||||||
|
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
||||||
|
.high .chart { border:1px solid rgb(77,146,33) }
|
||||||
|
/* dark yellow (gold) */
|
||||||
|
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
||||||
|
.medium .chart { border:1px solid #f9cd0b; }
|
||||||
|
/* light yellow */
|
||||||
|
.medium { background: #fff4c2; }
|
||||||
|
|
||||||
|
.cstat-skip { background: #ddd; color: #111; }
|
||||||
|
.fstat-skip { background: #ddd; color: #111 !important; }
|
||||||
|
.cbranch-skip { background: #ddd !important; color: #111; }
|
||||||
|
|
||||||
|
span.cline-neutral { background: #eaeaea; }
|
||||||
|
|
||||||
|
.coverage-summary td.empty {
|
||||||
|
opacity: .5;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-fill, .cover-empty {
|
||||||
|
display:inline-block;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
.chart {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.cover-empty {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.cover-full {
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
pre.prettyprint {
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
.com { color: #999 !important; }
|
||||||
|
.ignore-none { color: #999; font-weight: normal; }
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 auto -48px;
|
||||||
|
}
|
||||||
|
.footer, .push {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
var jumpToCode = (function init() {
|
||||||
|
// Classes of code we would like to highlight in the file view
|
||||||
|
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
||||||
|
|
||||||
|
// Elements to highlight in the file listing view
|
||||||
|
var fileListingElements = ['td.pct.low'];
|
||||||
|
|
||||||
|
// We don't want to select elements that are direct descendants of another match
|
||||||
|
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
||||||
|
|
||||||
|
// Selector that finds elements on the page to which we can jump
|
||||||
|
var selector =
|
||||||
|
fileListingElements.join(', ') +
|
||||||
|
', ' +
|
||||||
|
notSelector +
|
||||||
|
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
||||||
|
|
||||||
|
// The NodeList of matching elements
|
||||||
|
var missingCoverageElements = document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
var currentIndex;
|
||||||
|
|
||||||
|
function toggleClass(index) {
|
||||||
|
missingCoverageElements
|
||||||
|
.item(currentIndex)
|
||||||
|
.classList.remove('highlighted');
|
||||||
|
missingCoverageElements.item(index).classList.add('highlighted');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCurrent(index) {
|
||||||
|
toggleClass(index);
|
||||||
|
currentIndex = index;
|
||||||
|
missingCoverageElements.item(index).scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'center'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevious() {
|
||||||
|
var nextIndex = 0;
|
||||||
|
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
||||||
|
nextIndex = missingCoverageElements.length - 1;
|
||||||
|
} else if (missingCoverageElements.length > 1) {
|
||||||
|
nextIndex = currentIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent(nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNext() {
|
||||||
|
var nextIndex = 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof currentIndex === 'number' &&
|
||||||
|
currentIndex < missingCoverageElements.length - 1
|
||||||
|
) {
|
||||||
|
nextIndex = currentIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent(nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function jump(event) {
|
||||||
|
if (
|
||||||
|
document.getElementById('fileSearch') === document.activeElement &&
|
||||||
|
document.activeElement != null
|
||||||
|
) {
|
||||||
|
// if we're currently focused on the search input, we don't want to navigate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.which) {
|
||||||
|
case 78: // n
|
||||||
|
case 74: // j
|
||||||
|
goToNext();
|
||||||
|
break;
|
||||||
|
case 66: // b
|
||||||
|
case 75: // k
|
||||||
|
case 80: // p
|
||||||
|
goToPrevious();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
window.addEventListener('keydown', jumpToCode);
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
|
|
@ -0,0 +1,161 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for All files</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="prettify.css" />
|
||||||
|
<link rel="stylesheet" href="base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1>All files</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">49.43% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>2635/5330</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">87.24% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>513/588</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">41.97% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>123/293</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">49.43% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>2635/5330</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<div class="pad1">
|
||||||
|
<table class="coverage-summary">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||||
|
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||||
|
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||||
|
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||||
|
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||||
|
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||||
|
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td class="file medium" data-value="src"><a href="src/index.html">src</a></td>
|
||||||
|
<td data-value="50.39" class="pic medium">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 50%"></div><div class="cover-empty" style="width: 50%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="50.39" class="pct medium">50.39%</td>
|
||||||
|
<td data-value="252" class="abs medium">127/252</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="21" class="abs low">0/21</td>
|
||||||
|
<td data-value="50.39" class="pct medium">50.39%</td>
|
||||||
|
<td data-value="252" class="abs medium">127/252</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file medium" data-value="src/core"><a href="src/core/index.html">src/core</a></td>
|
||||||
|
<td data-value="60.64" class="pic medium">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 60%"></div><div class="cover-empty" style="width: 40%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="60.64" class="pct medium">60.64%</td>
|
||||||
|
<td data-value="1606" class="abs medium">974/1606</td>
|
||||||
|
<td data-value="85.78" class="pct high">85.78%</td>
|
||||||
|
<td data-value="197" class="abs high">169/197</td>
|
||||||
|
<td data-value="52" class="pct medium">52%</td>
|
||||||
|
<td data-value="100" class="abs medium">52/100</td>
|
||||||
|
<td data-value="60.64" class="pct medium">60.64%</td>
|
||||||
|
<td data-value="1606" class="abs medium">974/1606</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="src/plugins"><a href="src/plugins/index.html">src/plugins</a></td>
|
||||||
|
<td data-value="18.25" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 18%"></div><div class="cover-empty" style="width: 82%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="18.25" class="pct low">18.25%</td>
|
||||||
|
<td data-value="2334" class="abs low">426/2334</td>
|
||||||
|
<td data-value="89.28" class="pct high">89.28%</td>
|
||||||
|
<td data-value="28" class="abs high">25/28</td>
|
||||||
|
<td data-value="7.47" class="pct low">7.47%</td>
|
||||||
|
<td data-value="107" class="abs low">8/107</td>
|
||||||
|
<td data-value="18.25" class="pct low">18.25%</td>
|
||||||
|
<td data-value="2334" class="abs low">426/2334</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="src/utils"><a href="src/utils/index.html">src/utils</a></td>
|
||||||
|
<td data-value="97.36" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="97.36" class="pct high">97.36%</td>
|
||||||
|
<td data-value="1138" class="abs high">1108/1138</td>
|
||||||
|
<td data-value="87.87" class="pct high">87.87%</td>
|
||||||
|
<td data-value="363" class="abs high">319/363</td>
|
||||||
|
<td data-value="96.92" class="pct high">96.92%</td>
|
||||||
|
<td data-value="65" class="abs high">63/65</td>
|
||||||
|
<td data-value="97.36" class="pct high">97.36%</td>
|
||||||
|
<td data-value="1138" class="abs high">1108/1138</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="sorter.js"></script>
|
||||||
|
<script src="block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
|
|
@ -0,0 +1,210 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
var addSorting = (function() {
|
||||||
|
'use strict';
|
||||||
|
var cols,
|
||||||
|
currentSort = {
|
||||||
|
index: 0,
|
||||||
|
desc: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns the summary table element
|
||||||
|
function getTable() {
|
||||||
|
return document.querySelector('.coverage-summary');
|
||||||
|
}
|
||||||
|
// returns the thead element of the summary table
|
||||||
|
function getTableHeader() {
|
||||||
|
return getTable().querySelector('thead tr');
|
||||||
|
}
|
||||||
|
// returns the tbody element of the summary table
|
||||||
|
function getTableBody() {
|
||||||
|
return getTable().querySelector('tbody');
|
||||||
|
}
|
||||||
|
// returns the th element for nth column
|
||||||
|
function getNthColumn(n) {
|
||||||
|
return getTableHeader().querySelectorAll('th')[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFilterInput() {
|
||||||
|
const searchValue = document.getElementById('fileSearch').value;
|
||||||
|
const rows = document.getElementsByTagName('tbody')[0].children;
|
||||||
|
|
||||||
|
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
||||||
|
// it will be treated as a plain text search
|
||||||
|
let searchRegex;
|
||||||
|
try {
|
||||||
|
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
||||||
|
} catch (error) {
|
||||||
|
searchRegex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
let isMatch = false;
|
||||||
|
|
||||||
|
if (searchRegex) {
|
||||||
|
// If a valid regex was created, use it for matching
|
||||||
|
isMatch = searchRegex.test(row.textContent);
|
||||||
|
} else {
|
||||||
|
// Otherwise, fall back to the original plain text search
|
||||||
|
isMatch = row.textContent
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchValue.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = isMatch ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads the search box
|
||||||
|
function addSearchBox() {
|
||||||
|
var template = document.getElementById('filterTemplate');
|
||||||
|
var templateClone = template.content.cloneNode(true);
|
||||||
|
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
||||||
|
template.parentElement.appendChild(templateClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads all columns
|
||||||
|
function loadColumns() {
|
||||||
|
var colNodes = getTableHeader().querySelectorAll('th'),
|
||||||
|
colNode,
|
||||||
|
cols = [],
|
||||||
|
col,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < colNodes.length; i += 1) {
|
||||||
|
colNode = colNodes[i];
|
||||||
|
col = {
|
||||||
|
key: colNode.getAttribute('data-col'),
|
||||||
|
sortable: !colNode.getAttribute('data-nosort'),
|
||||||
|
type: colNode.getAttribute('data-type') || 'string'
|
||||||
|
};
|
||||||
|
cols.push(col);
|
||||||
|
if (col.sortable) {
|
||||||
|
col.defaultDescSort = col.type === 'number';
|
||||||
|
colNode.innerHTML =
|
||||||
|
colNode.innerHTML + '<span class="sorter"></span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
// attaches a data attribute to every tr element with an object
|
||||||
|
// of data values keyed by column name
|
||||||
|
function loadRowData(tableRow) {
|
||||||
|
var tableCols = tableRow.querySelectorAll('td'),
|
||||||
|
colNode,
|
||||||
|
col,
|
||||||
|
data = {},
|
||||||
|
i,
|
||||||
|
val;
|
||||||
|
for (i = 0; i < tableCols.length; i += 1) {
|
||||||
|
colNode = tableCols[i];
|
||||||
|
col = cols[i];
|
||||||
|
val = colNode.getAttribute('data-value');
|
||||||
|
if (col.type === 'number') {
|
||||||
|
val = Number(val);
|
||||||
|
}
|
||||||
|
data[col.key] = val;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// loads all row data
|
||||||
|
function loadData() {
|
||||||
|
var rows = getTableBody().querySelectorAll('tr'),
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i += 1) {
|
||||||
|
rows[i].data = loadRowData(rows[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sorts the table using the data for the ith column
|
||||||
|
function sortByIndex(index, desc) {
|
||||||
|
var key = cols[index].key,
|
||||||
|
sorter = function(a, b) {
|
||||||
|
a = a.data[key];
|
||||||
|
b = b.data[key];
|
||||||
|
return a < b ? -1 : a > b ? 1 : 0;
|
||||||
|
},
|
||||||
|
finalSorter = sorter,
|
||||||
|
tableBody = document.querySelector('.coverage-summary tbody'),
|
||||||
|
rowNodes = tableBody.querySelectorAll('tr'),
|
||||||
|
rows = [],
|
||||||
|
i;
|
||||||
|
|
||||||
|
if (desc) {
|
||||||
|
finalSorter = function(a, b) {
|
||||||
|
return -1 * sorter(a, b);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < rowNodes.length; i += 1) {
|
||||||
|
rows.push(rowNodes[i]);
|
||||||
|
tableBody.removeChild(rowNodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.sort(finalSorter);
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i += 1) {
|
||||||
|
tableBody.appendChild(rows[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// removes sort indicators for current column being sorted
|
||||||
|
function removeSortIndicators() {
|
||||||
|
var col = getNthColumn(currentSort.index),
|
||||||
|
cls = col.className;
|
||||||
|
|
||||||
|
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
||||||
|
col.className = cls;
|
||||||
|
}
|
||||||
|
// adds sort indicators for current column being sorted
|
||||||
|
function addSortIndicators() {
|
||||||
|
getNthColumn(currentSort.index).className += currentSort.desc
|
||||||
|
? ' sorted-desc'
|
||||||
|
: ' sorted';
|
||||||
|
}
|
||||||
|
// adds event listeners for all sorter widgets
|
||||||
|
function enableUI() {
|
||||||
|
var i,
|
||||||
|
el,
|
||||||
|
ithSorter = function ithSorter(i) {
|
||||||
|
var col = cols[i];
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
var desc = col.defaultDescSort;
|
||||||
|
|
||||||
|
if (currentSort.index === i) {
|
||||||
|
desc = !currentSort.desc;
|
||||||
|
}
|
||||||
|
sortByIndex(i, desc);
|
||||||
|
removeSortIndicators();
|
||||||
|
currentSort.index = i;
|
||||||
|
currentSort.desc = desc;
|
||||||
|
addSortIndicators();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
for (i = 0; i < cols.length; i += 1) {
|
||||||
|
if (cols[i].sortable) {
|
||||||
|
// add the click event handler on the th so users
|
||||||
|
// dont have to click on those tiny arrows
|
||||||
|
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
||||||
|
if (el.addEventListener) {
|
||||||
|
el.addEventListener('click', ithSorter(i));
|
||||||
|
} else {
|
||||||
|
el.attachEvent('onclick', ithSorter(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// adds sorting functionality to the UI
|
||||||
|
return function() {
|
||||||
|
if (!getTable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cols = loadColumns();
|
||||||
|
loadData();
|
||||||
|
addSearchBox();
|
||||||
|
addSortIndicators();
|
||||||
|
enableUI();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
window.addEventListener('load', addSorting);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,817 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/core/ConfigManager.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/core</a> ConfigManager.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">95.9% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>234/244</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">81.81% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>45/55</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">92.85% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>13/14</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">95.9% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>234/244</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a>
|
||||||
|
<a name='L209'></a><a href='#L209'>209</a>
|
||||||
|
<a name='L210'></a><a href='#L210'>210</a>
|
||||||
|
<a name='L211'></a><a href='#L211'>211</a>
|
||||||
|
<a name='L212'></a><a href='#L212'>212</a>
|
||||||
|
<a name='L213'></a><a href='#L213'>213</a>
|
||||||
|
<a name='L214'></a><a href='#L214'>214</a>
|
||||||
|
<a name='L215'></a><a href='#L215'>215</a>
|
||||||
|
<a name='L216'></a><a href='#L216'>216</a>
|
||||||
|
<a name='L217'></a><a href='#L217'>217</a>
|
||||||
|
<a name='L218'></a><a href='#L218'>218</a>
|
||||||
|
<a name='L219'></a><a href='#L219'>219</a>
|
||||||
|
<a name='L220'></a><a href='#L220'>220</a>
|
||||||
|
<a name='L221'></a><a href='#L221'>221</a>
|
||||||
|
<a name='L222'></a><a href='#L222'>222</a>
|
||||||
|
<a name='L223'></a><a href='#L223'>223</a>
|
||||||
|
<a name='L224'></a><a href='#L224'>224</a>
|
||||||
|
<a name='L225'></a><a href='#L225'>225</a>
|
||||||
|
<a name='L226'></a><a href='#L226'>226</a>
|
||||||
|
<a name='L227'></a><a href='#L227'>227</a>
|
||||||
|
<a name='L228'></a><a href='#L228'>228</a>
|
||||||
|
<a name='L229'></a><a href='#L229'>229</a>
|
||||||
|
<a name='L230'></a><a href='#L230'>230</a>
|
||||||
|
<a name='L231'></a><a href='#L231'>231</a>
|
||||||
|
<a name='L232'></a><a href='#L232'>232</a>
|
||||||
|
<a name='L233'></a><a href='#L233'>233</a>
|
||||||
|
<a name='L234'></a><a href='#L234'>234</a>
|
||||||
|
<a name='L235'></a><a href='#L235'>235</a>
|
||||||
|
<a name='L236'></a><a href='#L236'>236</a>
|
||||||
|
<a name='L237'></a><a href='#L237'>237</a>
|
||||||
|
<a name='L238'></a><a href='#L238'>238</a>
|
||||||
|
<a name='L239'></a><a href='#L239'>239</a>
|
||||||
|
<a name='L240'></a><a href='#L240'>240</a>
|
||||||
|
<a name='L241'></a><a href='#L241'>241</a>
|
||||||
|
<a name='L242'></a><a href='#L242'>242</a>
|
||||||
|
<a name='L243'></a><a href='#L243'>243</a>
|
||||||
|
<a name='L244'></a><a href='#L244'>244</a>
|
||||||
|
<a name='L245'></a><a href='#L245'>245</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">40x</span>
|
||||||
|
<span class="cline-any cline-yes">40x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightConfig, SentryEvent, UserInfo, ContextLevelMap, ErrorEvent, StackFrame, Breadcrumb, EventLevel } from '../types';
|
||||||
|
import { getContexts, getContextId, getPageUrl, getReferrer } from '../utils/env';
|
||||||
|
import { matchPatterns } from '../utils/helper';
|
||||||
|
import { getAnonymousId, isAnonymousTrackingEnabled } from '../utils/identity';
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: Partial<LightConfig> = {
|
||||||
|
enabled: true,
|
||||||
|
sampleRate: 1,
|
||||||
|
maxQueueSize: 100,
|
||||||
|
flushInterval: 5000,
|
||||||
|
maxRetries: 3,
|
||||||
|
retryDelay: 1000,
|
||||||
|
environment: 'production',
|
||||||
|
// 上下文分层策略:按事件级别决定堆栈深度和 breadcrumbs 数量
|
||||||
|
contextLevel: {
|
||||||
|
fatal: { maxStackFrames: 5, maxBreadcrumbs: 20 },
|
||||||
|
error: { maxStackFrames: 5, maxBreadcrumbs: 10 },
|
||||||
|
warning: { maxStackFrames: 3, maxBreadcrumbs: 5 },
|
||||||
|
info: { maxStackFrames: 0, maxBreadcrumbs: 0 },
|
||||||
|
debug: { maxStackFrames: 0, maxBreadcrumbs: 0 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ConfigManager {
|
||||||
|
private config: LightConfig;
|
||||||
|
private contexts: ReturnType<typeof getContexts> | null = null;
|
||||||
|
private contextId: string | null = null;
|
||||||
|
private contextReported: boolean = false;
|
||||||
|
|
||||||
|
constructor(config: LightConfig) {
|
||||||
|
this.config = { ...DEFAULT_CONFIG, ...config } as LightConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
markContextReported(): void {
|
||||||
|
this.contextReported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get<K extends keyof LightConfig>(key: K): LightConfig[K] {
|
||||||
|
return this.config[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(): LightConfig {
|
||||||
|
return { ...this.config };
|
||||||
|
}
|
||||||
|
|
||||||
|
set<K extends keyof LightConfig>(key: K, value: LightConfig[K]): void {
|
||||||
|
this.config[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser(user: UserInfo | null): void {
|
||||||
|
this.config.user = user || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTags(tags: Record<string, string>): void {
|
||||||
|
this.config.tags = { ...(this.config.tags || {}), ...tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
setTag(key: string, value: string): void {
|
||||||
|
if (!this.config.tags) {
|
||||||
|
this.config.tags = {};
|
||||||
|
}
|
||||||
|
(this.config.tags as Record<string, string>)[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExtra(key: string, value: unknown): void {
|
||||||
|
if (!this.config.extra) {
|
||||||
|
this.config.extra = {};
|
||||||
|
}
|
||||||
|
(this.config.extra as Record<string, unknown>)[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIgnoredError(message: string): boolean {
|
||||||
|
return matchPatterns(message, this.config.<span class="branch-0 cbranch-no" title="branch not covered" >ignoreErrors || [])</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldSample(): boolean {
|
||||||
|
const rate = this.config.<span class="branch-0 cbranch-no" title="branch not covered" >sampleRate ?? 1;</span>
|
||||||
|
if (rate >= 1) return true;
|
||||||
|
if (rate <= 0) return <span class="branch-0 cbranch-no" title="branch not covered" >false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return Math.random() < rate;</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编码后的环境信息(延迟初始化,只在首次上报时计算)
|
||||||
|
* 注意:env.ts 的 getContexts 已经返回编码后的数据
|
||||||
|
*/
|
||||||
|
private <span class="fstat-no" title="function not covered" >getEncodedContext(): ReturnType<typeof getContexts> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.contexts) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.contexts = getContexts();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return this.contexts;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将配置应用到事件
|
||||||
|
* @param event 事件对象
|
||||||
|
* @param includeFullContext 是否包含完整环境信息(批次内只有首个事件需要)
|
||||||
|
*/
|
||||||
|
applyToEvent(event: SentryEvent, includeFullContext: boolean = true): SentryEvent {
|
||||||
|
// 使用 Record 来避免联合类型的问题
|
||||||
|
const enriched: Record<string, unknown> = { ...event };
|
||||||
|
|
||||||
|
// 处理 tags
|
||||||
|
if (event.tags) {
|
||||||
|
enriched.tags = { ...event.tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 request
|
||||||
|
if (event.request) {
|
||||||
|
enriched.request = { ...event.request };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 exception(仅 error 事件有)
|
||||||
|
if ('exception' in event && event.exception) {
|
||||||
|
const exc = event.exception;
|
||||||
|
enriched.exception = {
|
||||||
|
...exc,
|
||||||
|
stacktrace: exc.stacktrace ? {
|
||||||
|
...exc.stacktrace,
|
||||||
|
frames: exc.stacktrace.frames ? exc.stacktrace.frames.map((f: StackFrame) => ({ ...f })<span class="branch-0 cbranch-no" title="branch not covered" >) : undefined,</span>
|
||||||
|
<span class="branch-0 cbranch-no" title="branch not covered" > } : undefined,</span>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 breadcrumbs
|
||||||
|
if (event.breadcrumbs) {
|
||||||
|
enriched.breadcrumbs = event.breadcrumbs.map((b: Breadcrumb) => ({ ...b }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 user
|
||||||
|
if (event.user) {
|
||||||
|
enriched.user = { ...event.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 contexts
|
||||||
|
if (event.contexts) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > enriched.contexts = { ...event.contexts };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
if (this.config.release) {
|
||||||
|
enriched.release = this.config.release;
|
||||||
|
}
|
||||||
|
if (this.config.environment) {
|
||||||
|
enriched.environment = this.config.environment;
|
||||||
|
}
|
||||||
|
enriched.platform = 'javascript';
|
||||||
|
if (this.config.user) {
|
||||||
|
enriched.user = this.config.user;
|
||||||
|
}
|
||||||
|
if (this.config.tags) {
|
||||||
|
enriched.tags = { ...this.config.tags, ...(event.tags as Record<string, string> || {}) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enriched.request) {
|
||||||
|
enriched.request = {
|
||||||
|
url: getPageUrl(),
|
||||||
|
referrer: getReferrer(),
|
||||||
|
};
|
||||||
|
} else if (!(enriched.request as Record<string, unknown>).url) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > (enriched.request as Record<string, unknown>).url = getPageUrl();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
if (!this.contextId) {
|
||||||
|
this.contextId = getContextId();
|
||||||
|
}
|
||||||
|
enriched.context_id = this.contextId;
|
||||||
|
|
||||||
|
// 处理匿名用户ID
|
||||||
|
// 只有在没有设置 user.id 且启用了匿名追踪时,才添加 anonymousId
|
||||||
|
const hasUserId = this.config.user?.id || (event.user as Record<string, unknown>)?.id;
|
||||||
|
if (!hasUserId && isAnonymousTrackingEnabled(this.config)) {
|
||||||
|
const anonymousId = getAnonymousId();
|
||||||
|
if (anonymousId) {
|
||||||
|
enriched.anonymousId = anonymousId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = this.applyContextLevel(enriched);
|
||||||
|
|
||||||
|
if (includeFullContext && !this.contextReported) {
|
||||||
|
if (!this.contexts) {
|
||||||
|
this.contexts = getContexts();
|
||||||
|
}
|
||||||
|
(result as Record<string, unknown>).contexts = this.contexts;
|
||||||
|
(result as Record<string, unknown>).env = {
|
||||||
|
b: this.contexts.browser?.name,
|
||||||
|
bv: this.contexts.browser?.version,
|
||||||
|
os: this.contexts.os?.name,
|
||||||
|
osv: this.contexts.os?.version,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as unknown as SentryEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用上下文分层策略:根据事件级别裁剪堆栈和 breadcrumbs
|
||||||
|
* 注意:此方法不修改原始 event 对象,返回裁剪后的副本
|
||||||
|
*/
|
||||||
|
private applyContextLevel(event: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
const contextLevel = this.config.contextLevel as ContextLevelMap | undefined;
|
||||||
|
if (!contextLevel) <span class="branch-0 cbranch-no" title="branch not covered" >return event;</span>
|
||||||
|
|
||||||
|
const level = (event.<span class="branch-0 cbranch-no" title="branch not covered" >level || 'error') as EventLevel;</span>
|
||||||
|
const levelConfig = contextLevel[level<span class="branch-0 cbranch-no" title="branch not covered" >] || contextLevel['error'];</span>
|
||||||
|
|
||||||
|
// 创建浅拷贝用于裁剪
|
||||||
|
const enriched: Record<string, unknown> = { ...event };
|
||||||
|
|
||||||
|
// 裁剪堆栈帧(仅 error 事件有 exception)
|
||||||
|
if ('exception' in event && event.exception && typeof event.exception === 'object') {
|
||||||
|
const exc = event.exception as Record<string, unknown>;
|
||||||
|
if (exc.stacktrace && typeof exc.stacktrace === 'object') {
|
||||||
|
const st = exc.stacktrace as Record<string, unknown>;
|
||||||
|
if (Array.isArray(st.frames)) {
|
||||||
|
if (levelConfig.maxStackFrames === 0) {
|
||||||
|
// info/debug 级别不需要堆栈
|
||||||
|
delete enriched.exception;
|
||||||
|
} else {
|
||||||
|
enriched.exception = {
|
||||||
|
...exc,
|
||||||
|
stacktrace: {
|
||||||
|
...st,
|
||||||
|
frames: (st.frames as StackFrame[]).slice(0, levelConfig.maxStackFrames),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 裁剪 breadcrumbs
|
||||||
|
if (event.breadcrumbs && Array.isArray(event.breadcrumbs)) {
|
||||||
|
if (levelConfig.maxBreadcrumbs === 0) {
|
||||||
|
// info/debug 级别不需要 breadcrumbs
|
||||||
|
delete enriched.breadcrumbs;
|
||||||
|
} else {
|
||||||
|
enriched.breadcrumbs = (event.breadcrumbs as Breadcrumb[]).slice(-levelConfig.maxBreadcrumbs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enriched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/core/EventBus.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/core</a> EventBus.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>47/47</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>13/13</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>7/7</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>47/47</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">42x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">21x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">88x</span>
|
||||||
|
<span class="cline-any cline-yes">88x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">88x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">type Handler = (...args: unknown[]) => void;
|
||||||
|
|
||||||
|
export class EventBus {
|
||||||
|
private handlers: Map<string, Handler[]> = new Map();
|
||||||
|
|
||||||
|
on(event: string, handler: Handler): void {
|
||||||
|
if (!this.handlers.has(event)) {
|
||||||
|
this.handlers.set(event, []);
|
||||||
|
}
|
||||||
|
this.handlers.get(event)!.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event: string, handler: Handler): void {
|
||||||
|
const eventHandlers = this.handlers.get(event);
|
||||||
|
if (eventHandlers) {
|
||||||
|
const index = eventHandlers.indexOf(handler);
|
||||||
|
if (index > -1) {
|
||||||
|
eventHandlers.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event: string, ...args: unknown[]): void {
|
||||||
|
const eventHandlers = this.handlers.get(event);
|
||||||
|
if (eventHandlers) {
|
||||||
|
for (const handler of eventHandlers) {
|
||||||
|
try {
|
||||||
|
handler(...args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[LightSDK] EventBus handler error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
once(event: string, handler: Handler): void {
|
||||||
|
const wrapped = (...args: unknown[]) => {
|
||||||
|
this.off(event, wrapped);
|
||||||
|
handler(...args);
|
||||||
|
};
|
||||||
|
this.on(event, wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
this.handlers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,346 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/core/PluginManager.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/core</a> PluginManager.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>87/87</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>26/26</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>8/8</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>87/87</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">30x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">30x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">30x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, SentryEvent } from '../types';
|
||||||
|
|
||||||
|
export class PluginManager {
|
||||||
|
private plugins: Map<string, LightPlugin> = new Map();
|
||||||
|
private client: LightClient;
|
||||||
|
|
||||||
|
constructor(client: LightClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(plugin: LightPlugin): void {
|
||||||
|
if (this.plugins.has(plugin.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.plugins.set(plugin.name, plugin);
|
||||||
|
try {
|
||||||
|
plugin.setup(this.client);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[LightSDK] Plugin "${plugin.name}" setup error:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(name: string): void {
|
||||||
|
const plugin = this.plugins.get(name);
|
||||||
|
if (plugin) {
|
||||||
|
if (plugin.destroy) {
|
||||||
|
try {
|
||||||
|
plugin.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[LightSDK] Plugin "${name}" destroy error:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.plugins.delete(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string): LightPlugin | undefined {
|
||||||
|
return this.plugins.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
has(name: string): boolean {
|
||||||
|
return this.plugins.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBeforeReport(event: SentryEvent): SentryEvent | null {
|
||||||
|
let result: SentryEvent | null = event;
|
||||||
|
for (const plugin of this.plugins.values()) {
|
||||||
|
if (plugin.beforeReport) {
|
||||||
|
try {
|
||||||
|
const modified = plugin.beforeReport(result);
|
||||||
|
if (modified === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result = modified;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[LightSDK] Plugin "${plugin.name}" beforeReport error:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAfterReport(event: SentryEvent): void {
|
||||||
|
for (const plugin of this.plugins.values()) {
|
||||||
|
if (plugin.afterReport) {
|
||||||
|
try {
|
||||||
|
plugin.afterReport(event);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[LightSDK] Plugin "${plugin.name}" afterReport error:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
for (const plugin of this.plugins.values()) {
|
||||||
|
if (plugin.destroy) {
|
||||||
|
try {
|
||||||
|
plugin.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[LightSDK] Plugin "${plugin.name}" destroy error:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.plugins.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,679 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/core/TracingManager.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/core</a> TracingManager.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">86.86% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>172/198</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">80% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>28/35</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">78.57% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>11/14</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">86.86% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>172/198</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">17x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightConfig, SpanContext, TransactionContext, Span, Transaction, LightClient, SamplingContext } from '../types';
|
||||||
|
import { Span as SpanImpl, Transaction as TransactionImpl } from '../utils/tracing';
|
||||||
|
import { logDebug } from '../utils/logger';
|
||||||
|
|
||||||
|
export class TracingManager {
|
||||||
|
private config: LightConfig;
|
||||||
|
private _client?: LightClient;
|
||||||
|
private currentTransaction: Transaction | null = null;
|
||||||
|
private spanStack: Span[] = [];
|
||||||
|
private dsnPublicKey?: string;
|
||||||
|
|
||||||
|
constructor(config: LightConfig, client?: LightClient, dsnPublicKey?: string) {
|
||||||
|
this.config = config;
|
||||||
|
this._client = client;
|
||||||
|
this.dsnPublicKey = dsnPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig(config: LightConfig): void {
|
||||||
|
this.config = config;
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
const txImpl = this.currentTransaction as unknown as TransactionImpl;
|
||||||
|
txImpl.setUserBaggage(config.user?.id, (config.user as { segment?: string } | undefined)?.segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSample(context: TransactionContext, parentSampled?: boolean): boolean {
|
||||||
|
const sampler = this.config.tracesSampler;
|
||||||
|
if (sampler) {
|
||||||
|
const samplingContext: SamplingContext = {
|
||||||
|
...context,
|
||||||
|
parentSampled,
|
||||||
|
};
|
||||||
|
const result = sampler(samplingContext);
|
||||||
|
if (typeof result === 'boolean') {
|
||||||
|
return result;
|
||||||
|
<span class="branch-0 cbranch-no" title="branch not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const clampedRate = Math.max(0, Math.min(1, result));</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clampedRate === 0) return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clampedRate === 1) return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return Math.random() < clampedRate;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
const rate = this.config.<span class="branch-0 cbranch-no" title="branch not covered" >tracesSampleRate ?? 0;</span>
|
||||||
|
if (rate >= 1) return true;
|
||||||
|
if (rate <= 0) return <span class="branch-0 cbranch-no" title="branch not covered" >false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return Math.random() < rate;</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null {
|
||||||
|
const parentTx = this.currentTransaction;
|
||||||
|
const parentSampled = parentTx ? parentTx.sampled : undefined;
|
||||||
|
|
||||||
|
const sampled = context.sampled ?? this.shouldSample(context, parentSampled);
|
||||||
|
|
||||||
|
const transaction = new TransactionImpl(
|
||||||
|
{
|
||||||
|
...context,
|
||||||
|
sampled,
|
||||||
|
},
|
||||||
|
this._client,
|
||||||
|
this.dsnPublicKey,
|
||||||
|
this.config.release,
|
||||||
|
this.config.environment
|
||||||
|
) as unknown as Transaction;
|
||||||
|
|
||||||
|
this.currentTransaction = transaction;
|
||||||
|
this.spanStack = [transaction];
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Transaction started', {
|
||||||
|
name: transaction.name,
|
||||||
|
traceId: transaction.traceId,
|
||||||
|
spanId: transaction.spanId,
|
||||||
|
sampled,
|
||||||
|
op: transaction.op,
|
||||||
|
});
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTransaction(): Transaction | null {
|
||||||
|
return this.currentTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > setCurrentTransaction(transaction: Transaction | null): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction = transaction;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!transaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.spanStack = [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } else {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.spanStack = [transaction];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
getCurrentSpan(): Span | null {
|
||||||
|
return this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentSpan(span: Span | null): void {
|
||||||
|
if (span === null) {
|
||||||
|
if (this.spanStack.length > 1) {
|
||||||
|
this.spanStack.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.spanStack.push(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startSpan(context: SpanContext): Span | null {
|
||||||
|
const parentSpan = this.getCurrentSpan();
|
||||||
|
|
||||||
|
if (!parentSpan && !context.traceId) {
|
||||||
|
logDebug('TracingManager', 'startSpan skipped: no active span and no traceId');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let span: Span;
|
||||||
|
|
||||||
|
if (parentSpan) {
|
||||||
|
span = (parentSpan as SpanImpl).startChild(context);
|
||||||
|
} else {
|
||||||
|
span = new SpanImpl({
|
||||||
|
...context,
|
||||||
|
parentSpanId: context.parentSpanId,
|
||||||
|
}) as unknown as Span;
|
||||||
|
|
||||||
|
if (this.currentTransaction) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.currentTransaction.sampled !== false) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > (this.currentTransaction as unknown as TransactionImpl)._addSpan(span as unknown as SpanImpl);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > (span as SpanImpl & { transaction: Transaction }).transaction = this.currentTransaction;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spanStack.push(span);
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Span started', {
|
||||||
|
op: span.op,
|
||||||
|
description: span.description,
|
||||||
|
spanId: span.spanId,
|
||||||
|
parentSpanId: span.parentSpanId,
|
||||||
|
stackDepth: this.spanStack.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSpanFinished(span: Span): void {
|
||||||
|
const idx = this.spanStack.lastIndexOf(span);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.spanStack.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.spanStack.length === <span class="branch-0 cbranch-no" title="branch not covered" >0 && this.currentTransaction) <span class="branch-0 cbranch-no" title="branch not covered" >{</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.spanStack = [this.currentTransaction];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Span finished', {
|
||||||
|
op: span.op,
|
||||||
|
description: span.description,
|
||||||
|
spanId: span.spanId,
|
||||||
|
stackDepth: this.spanStack.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTransactionFinished(transaction: Transaction): void {
|
||||||
|
if (this.currentTransaction === transaction) {
|
||||||
|
this.currentTransaction = null;
|
||||||
|
this.spanStack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Transaction finished', {
|
||||||
|
name: transaction.name,
|
||||||
|
traceId: transaction.traceId,
|
||||||
|
spanCount: transaction.spans.length,
|
||||||
|
sampled: transaction.sampled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > finishSpan(span: Span): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > span.finish();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > finishTransaction(transaction: Transaction): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > transaction.finish();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
try {
|
||||||
|
this.currentTransaction.setStatus('aborted');
|
||||||
|
this.currentTransaction.finish();
|
||||||
|
<span class="branch-0 cbranch-no" title="branch not covered" > } catch {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // ignore</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
this.currentTransaction = null;
|
||||||
|
this.spanStack = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/core</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> src/core</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">60.64% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>974/1606</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">85.78% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>169/197</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">52% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>52/100</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">60.64% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>974/1606</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line medium'></div>
|
||||||
|
<div class="pad1">
|
||||||
|
<table class="coverage-summary">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||||
|
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||||
|
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||||
|
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||||
|
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||||
|
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||||
|
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td class="file low" data-value="Client.ts"><a href="Client.ts.html">Client.ts</a></td>
|
||||||
|
<td data-value="24.72" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 24%"></div><div class="cover-empty" style="width: 76%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="24.72" class="pct low">24.72%</td>
|
||||||
|
<td data-value="364" class="abs low">90/364</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="29" class="abs low">0/29</td>
|
||||||
|
<td data-value="24.72" class="pct low">24.72%</td>
|
||||||
|
<td data-value="364" class="abs low">90/364</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="ConfigManager.ts"><a href="ConfigManager.ts.html">ConfigManager.ts</a></td>
|
||||||
|
<td data-value="95.9" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 95%"></div><div class="cover-empty" style="width: 5%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="95.9" class="pct high">95.9%</td>
|
||||||
|
<td data-value="244" class="abs high">234/244</td>
|
||||||
|
<td data-value="81.81" class="pct high">81.81%</td>
|
||||||
|
<td data-value="55" class="abs high">45/55</td>
|
||||||
|
<td data-value="92.85" class="pct high">92.85%</td>
|
||||||
|
<td data-value="14" class="abs high">13/14</td>
|
||||||
|
<td data-value="95.9" class="pct high">95.9%</td>
|
||||||
|
<td data-value="244" class="abs high">234/244</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="EventBus.ts"><a href="EventBus.ts.html">EventBus.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="47" class="abs high">47/47</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="13" class="abs high">13/13</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="7" class="abs high">7/7</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="47" class="abs high">47/47</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="EventQueue.ts"><a href="EventQueue.ts.html">EventQueue.ts</a></td>
|
||||||
|
<td data-value="89.9" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 89%"></div><div class="cover-empty" style="width: 11%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="89.9" class="pct high">89.9%</td>
|
||||||
|
<td data-value="317" class="abs high">285/317</td>
|
||||||
|
<td data-value="83.82" class="pct high">83.82%</td>
|
||||||
|
<td data-value="68" class="abs high">57/68</td>
|
||||||
|
<td data-value="81.25" class="pct high">81.25%</td>
|
||||||
|
<td data-value="16" class="abs high">13/16</td>
|
||||||
|
<td data-value="89.9" class="pct high">89.9%</td>
|
||||||
|
<td data-value="317" class="abs high">285/317</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="PluginManager.ts"><a href="PluginManager.ts.html">PluginManager.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="87" class="abs high">87/87</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="26" class="abs high">26/26</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="8" class="abs high">8/8</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="87" class="abs high">87/87</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="Reporter.ts"><a href="Reporter.ts.html">Reporter.ts</a></td>
|
||||||
|
<td data-value="16.9" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 16%"></div><div class="cover-empty" style="width: 84%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="16.9" class="pct low">16.9%</td>
|
||||||
|
<td data-value="349" class="abs low">59/349</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="12" class="abs low">0/12</td>
|
||||||
|
<td data-value="16.9" class="pct low">16.9%</td>
|
||||||
|
<td data-value="349" class="abs low">59/349</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="TracingManager.ts"><a href="TracingManager.ts.html">TracingManager.ts</a></td>
|
||||||
|
<td data-value="86.86" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="86.86" class="pct high">86.86%</td>
|
||||||
|
<td data-value="198" class="abs high">172/198</td>
|
||||||
|
<td data-value="80" class="pct high">80%</td>
|
||||||
|
<td data-value="35" class="abs high">28/35</td>
|
||||||
|
<td data-value="78.57" class="pct medium">78.57%</td>
|
||||||
|
<td data-value="14" class="abs medium">11/14</td>
|
||||||
|
<td data-value="86.86" class="pct high">86.86%</td>
|
||||||
|
<td data-value="198" class="abs high">172/198</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../index.html">All files</a> src</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">50.39% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>127/252</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/21</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">50.39% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>127/252</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line medium'></div>
|
||||||
|
<div class="pad1">
|
||||||
|
<table class="coverage-summary">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||||
|
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||||
|
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||||
|
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||||
|
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||||
|
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||||
|
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td class="file medium" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
|
||||||
|
<td data-value="50.39" class="pic medium">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 50%"></div><div class="cover-empty" style="width: 50%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="50.39" class="pct medium">50.39%</td>
|
||||||
|
<td data-value="252" class="abs medium">127/252</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="21" class="abs low">0/21</td>
|
||||||
|
<td data-value="50.39" class="pct medium">50.39%</td>
|
||||||
|
<td data-value="252" class="abs medium">127/252</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../sorter.js"></script>
|
||||||
|
<script src="../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,841 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/index.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> index.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">50.39% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>127/252</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/21</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">50.39% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>127/252</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line medium'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a>
|
||||||
|
<a name='L209'></a><a href='#L209'>209</a>
|
||||||
|
<a name='L210'></a><a href='#L210'>210</a>
|
||||||
|
<a name='L211'></a><a href='#L211'>211</a>
|
||||||
|
<a name='L212'></a><a href='#L212'>212</a>
|
||||||
|
<a name='L213'></a><a href='#L213'>213</a>
|
||||||
|
<a name='L214'></a><a href='#L214'>214</a>
|
||||||
|
<a name='L215'></a><a href='#L215'>215</a>
|
||||||
|
<a name='L216'></a><a href='#L216'>216</a>
|
||||||
|
<a name='L217'></a><a href='#L217'>217</a>
|
||||||
|
<a name='L218'></a><a href='#L218'>218</a>
|
||||||
|
<a name='L219'></a><a href='#L219'>219</a>
|
||||||
|
<a name='L220'></a><a href='#L220'>220</a>
|
||||||
|
<a name='L221'></a><a href='#L221'>221</a>
|
||||||
|
<a name='L222'></a><a href='#L222'>222</a>
|
||||||
|
<a name='L223'></a><a href='#L223'>223</a>
|
||||||
|
<a name='L224'></a><a href='#L224'>224</a>
|
||||||
|
<a name='L225'></a><a href='#L225'>225</a>
|
||||||
|
<a name='L226'></a><a href='#L226'>226</a>
|
||||||
|
<a name='L227'></a><a href='#L227'>227</a>
|
||||||
|
<a name='L228'></a><a href='#L228'>228</a>
|
||||||
|
<a name='L229'></a><a href='#L229'>229</a>
|
||||||
|
<a name='L230'></a><a href='#L230'>230</a>
|
||||||
|
<a name='L231'></a><a href='#L231'>231</a>
|
||||||
|
<a name='L232'></a><a href='#L232'>232</a>
|
||||||
|
<a name='L233'></a><a href='#L233'>233</a>
|
||||||
|
<a name='L234'></a><a href='#L234'>234</a>
|
||||||
|
<a name='L235'></a><a href='#L235'>235</a>
|
||||||
|
<a name='L236'></a><a href='#L236'>236</a>
|
||||||
|
<a name='L237'></a><a href='#L237'>237</a>
|
||||||
|
<a name='L238'></a><a href='#L238'>238</a>
|
||||||
|
<a name='L239'></a><a href='#L239'>239</a>
|
||||||
|
<a name='L240'></a><a href='#L240'>240</a>
|
||||||
|
<a name='L241'></a><a href='#L241'>241</a>
|
||||||
|
<a name='L242'></a><a href='#L242'>242</a>
|
||||||
|
<a name='L243'></a><a href='#L243'>243</a>
|
||||||
|
<a name='L244'></a><a href='#L244'>244</a>
|
||||||
|
<a name='L245'></a><a href='#L245'>245</a>
|
||||||
|
<a name='L246'></a><a href='#L246'>246</a>
|
||||||
|
<a name='L247'></a><a href='#L247'>247</a>
|
||||||
|
<a name='L248'></a><a href='#L248'>248</a>
|
||||||
|
<a name='L249'></a><a href='#L249'>249</a>
|
||||||
|
<a name='L250'></a><a href='#L250'>250</a>
|
||||||
|
<a name='L251'></a><a href='#L251'>251</a>
|
||||||
|
<a name='L252'></a><a href='#L252'>252</a>
|
||||||
|
<a name='L253'></a><a href='#L253'>253</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import Client from './core/Client';
|
||||||
|
import ErrorPlugin from './plugins/ErrorPlugin';
|
||||||
|
import PerformancePlugin from './plugins/PerformancePlugin';
|
||||||
|
import NetworkPlugin from './plugins/NetworkPlugin';
|
||||||
|
import BehaviorPlugin from './plugins/BehaviorPlugin';
|
||||||
|
import OfflinePlugin from './plugins/OfflinePlugin';
|
||||||
|
import SamplingPlugin from './plugins/SamplingPlugin';
|
||||||
|
import RouterPlugin from './plugins/RouterPlugin';
|
||||||
|
import InteractionPlugin from './plugins/InteractionPlugin';
|
||||||
|
import { Span as SpanClass, Transaction as TransactionClass, httpStatusToSpanStatus, dbStatusToSpanStatus, statusToSpanStatus } from './utils/tracing';
|
||||||
|
import { serializeBaggage, parseBaggage, createBaggageFromTransaction } from './utils/baggage';
|
||||||
|
import type { LightConfig, LightClient, SentryEvent, EventLevel, UserInfo, Breadcrumb, LightPlugin, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, DSNInfo, TransactionContext, SpanContext, Transaction, Span, SamplingContext, BaggageFields, SpanStatus } from './types';
|
||||||
|
import { logDebug, setDebug } from './utils/logger';
|
||||||
|
import { setAnonymousId as setStoredAnonymousId, clearAnonymousId as clearStoredAnonymousId, getAnonymousId as getStoredAnonymousId } from './utils/identity';
|
||||||
|
|
||||||
|
let globalClient: LightClient | null = null;
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function init(config: LightConfig): LightClient {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > setDebug(config.debug === true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'init called', { dsn: config.dsn, debug: config.debug });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const client = new Client(config);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const defaultPlugins: LightPlugin[] = [</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > new OfflinePlugin(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > new SamplingPlugin(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > new ErrorPlugin(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > ];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const pluginNames = config.plugins?.filter(p => typeof p === 'string') as string[] || [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (pluginNames.includes('performance') || pluginNames.includes('all')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > defaultPlugins.push(new PerformancePlugin());</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (pluginNames.includes('network') || pluginNames.includes('all')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > defaultPlugins.push(new NetworkPlugin());</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (pluginNames.includes('behavior') || pluginNames.includes('all')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > defaultPlugins.push(new BehaviorPlugin());</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (pluginNames.includes('router') || pluginNames.includes('all')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > defaultPlugins.push(new RouterPlugin());</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (pluginNames.includes('interaction') || pluginNames.includes('all')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > defaultPlugins.push(new InteractionPlugin());</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'Registering plugins', { count: defaultPlugins.length, plugins: defaultPlugins.map(p => p.name) });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > for (const plugin of defaultPlugins) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > client.use(plugin);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (config.plugins) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > for (const plugin of config.plugins) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof plugin === 'object' && plugin !== null && 'name' in plugin && 'setup' in plugin) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > client.use(plugin as LightPlugin);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > client.init();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient = client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'init completed, globalClient set');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function getClient(): LightClient | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return globalClient;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function captureException(error: Error | unknown): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'captureException called', { hasClient: !!globalClient });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!globalClient) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.warn('[LightSDK] SDK not initialized. Call LightSentry.init() first.');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient.captureException(error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function captureMessage(message: string, level?: EventLevel): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'captureMessage called', { hasClient: !!globalClient });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!globalClient) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.warn('[LightSDK] SDK not initialized. Call LightSentry.init() first.');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient.captureMessage(message, level);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function captureEvent(event: Partial<SentryEvent> & { type: string }): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logDebug('Index', 'captureEvent called', { hasClient: !!globalClient });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!globalClient) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.warn('[LightSDK] SDK not initialized. Call LightSentry.init() first.');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient.captureEvent(event);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setUser(user: UserInfo | null): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.setUser(user);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setAnonymousId(id: string): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > setStoredAnonymousId(id);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function clearAnonymousId(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearStoredAnonymousId();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function getAnonymousId(): string | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return getStoredAnonymousId();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function startTransaction(context: TransactionContext): Transaction | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return globalClient?.startTransaction(context) || null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function getCurrentTransaction(): Transaction | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return globalClient?.getCurrentTransaction() || null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function getCurrentSpan(): Span | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return globalClient?.getCurrentSpan() || null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setSpan(span: Span | null): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.setSpan(span);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function startSpan(context: SpanContext): Span | null {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return globalClient?.startSpan(context) || null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setTag(key: string, value: string): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.setTag(key, value);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setTags(tags: Record<string, string>): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.setTags(tags);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function setExtra(key: string, value: unknown): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.setExtra(key, value);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.addBreadcrumb(breadcrumb);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >async function flush(): Promise<void> {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > await globalClient?.flush();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function disable(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.disable();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function enable(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > globalClient?.enable();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
export {
|
||||||
|
init,
|
||||||
|
getClient,
|
||||||
|
captureException,
|
||||||
|
captureMessage,
|
||||||
|
captureEvent,
|
||||||
|
setUser,
|
||||||
|
setAnonymousId,
|
||||||
|
clearAnonymousId,
|
||||||
|
getAnonymousId,
|
||||||
|
startTransaction,
|
||||||
|
getCurrentTransaction,
|
||||||
|
getCurrentSpan,
|
||||||
|
setSpan,
|
||||||
|
startSpan,
|
||||||
|
setTag,
|
||||||
|
setTags,
|
||||||
|
setExtra,
|
||||||
|
addBreadcrumb,
|
||||||
|
flush,
|
||||||
|
disable,
|
||||||
|
enable,
|
||||||
|
Client,
|
||||||
|
httpStatusToSpanStatus,
|
||||||
|
dbStatusToSpanStatus,
|
||||||
|
statusToSpanStatus,
|
||||||
|
serializeBaggage,
|
||||||
|
parseBaggage,
|
||||||
|
createBaggageFromTransaction,
|
||||||
|
ErrorPlugin,
|
||||||
|
PerformancePlugin,
|
||||||
|
NetworkPlugin,
|
||||||
|
BehaviorPlugin,
|
||||||
|
OfflinePlugin,
|
||||||
|
SamplingPlugin,
|
||||||
|
RouterPlugin,
|
||||||
|
InteractionPlugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SpanClass as Span, TransactionClass as Transaction };
|
||||||
|
|
||||||
|
export type {
|
||||||
|
LightConfig,
|
||||||
|
LightClient,
|
||||||
|
LightPlugin,
|
||||||
|
SentryEvent,
|
||||||
|
ErrorEvent,
|
||||||
|
PerformanceEvent,
|
||||||
|
NetworkEvent,
|
||||||
|
BehaviorEvent,
|
||||||
|
EventLevel,
|
||||||
|
UserInfo,
|
||||||
|
Breadcrumb,
|
||||||
|
DSNInfo,
|
||||||
|
TransactionContext,
|
||||||
|
SpanContext,
|
||||||
|
SamplingContext,
|
||||||
|
BaggageFields,
|
||||||
|
SpanStatus,
|
||||||
|
SpanJSON,
|
||||||
|
TransactionJSON,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
getClient,
|
||||||
|
captureException,
|
||||||
|
captureMessage,
|
||||||
|
captureEvent,
|
||||||
|
setUser,
|
||||||
|
setAnonymousId,
|
||||||
|
clearAnonymousId,
|
||||||
|
getAnonymousId,
|
||||||
|
startTransaction,
|
||||||
|
getCurrentTransaction,
|
||||||
|
getCurrentSpan,
|
||||||
|
setSpan,
|
||||||
|
startSpan,
|
||||||
|
setTag,
|
||||||
|
setTags,
|
||||||
|
setExtra,
|
||||||
|
addBreadcrumb,
|
||||||
|
flush,
|
||||||
|
disable,
|
||||||
|
enable,
|
||||||
|
Span: SpanClass,
|
||||||
|
Transaction: TransactionClass,
|
||||||
|
httpStatusToSpanStatus,
|
||||||
|
};
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../sorter.js"></script>
|
||||||
|
<script src="../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,982 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins/ErrorPlugin.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/plugins</a> ErrorPlugin.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">17.39% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>52/299</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/14</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">17.39% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>52/299</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a>
|
||||||
|
<a name='L209'></a><a href='#L209'>209</a>
|
||||||
|
<a name='L210'></a><a href='#L210'>210</a>
|
||||||
|
<a name='L211'></a><a href='#L211'>211</a>
|
||||||
|
<a name='L212'></a><a href='#L212'>212</a>
|
||||||
|
<a name='L213'></a><a href='#L213'>213</a>
|
||||||
|
<a name='L214'></a><a href='#L214'>214</a>
|
||||||
|
<a name='L215'></a><a href='#L215'>215</a>
|
||||||
|
<a name='L216'></a><a href='#L216'>216</a>
|
||||||
|
<a name='L217'></a><a href='#L217'>217</a>
|
||||||
|
<a name='L218'></a><a href='#L218'>218</a>
|
||||||
|
<a name='L219'></a><a href='#L219'>219</a>
|
||||||
|
<a name='L220'></a><a href='#L220'>220</a>
|
||||||
|
<a name='L221'></a><a href='#L221'>221</a>
|
||||||
|
<a name='L222'></a><a href='#L222'>222</a>
|
||||||
|
<a name='L223'></a><a href='#L223'>223</a>
|
||||||
|
<a name='L224'></a><a href='#L224'>224</a>
|
||||||
|
<a name='L225'></a><a href='#L225'>225</a>
|
||||||
|
<a name='L226'></a><a href='#L226'>226</a>
|
||||||
|
<a name='L227'></a><a href='#L227'>227</a>
|
||||||
|
<a name='L228'></a><a href='#L228'>228</a>
|
||||||
|
<a name='L229'></a><a href='#L229'>229</a>
|
||||||
|
<a name='L230'></a><a href='#L230'>230</a>
|
||||||
|
<a name='L231'></a><a href='#L231'>231</a>
|
||||||
|
<a name='L232'></a><a href='#L232'>232</a>
|
||||||
|
<a name='L233'></a><a href='#L233'>233</a>
|
||||||
|
<a name='L234'></a><a href='#L234'>234</a>
|
||||||
|
<a name='L235'></a><a href='#L235'>235</a>
|
||||||
|
<a name='L236'></a><a href='#L236'>236</a>
|
||||||
|
<a name='L237'></a><a href='#L237'>237</a>
|
||||||
|
<a name='L238'></a><a href='#L238'>238</a>
|
||||||
|
<a name='L239'></a><a href='#L239'>239</a>
|
||||||
|
<a name='L240'></a><a href='#L240'>240</a>
|
||||||
|
<a name='L241'></a><a href='#L241'>241</a>
|
||||||
|
<a name='L242'></a><a href='#L242'>242</a>
|
||||||
|
<a name='L243'></a><a href='#L243'>243</a>
|
||||||
|
<a name='L244'></a><a href='#L244'>244</a>
|
||||||
|
<a name='L245'></a><a href='#L245'>245</a>
|
||||||
|
<a name='L246'></a><a href='#L246'>246</a>
|
||||||
|
<a name='L247'></a><a href='#L247'>247</a>
|
||||||
|
<a name='L248'></a><a href='#L248'>248</a>
|
||||||
|
<a name='L249'></a><a href='#L249'>249</a>
|
||||||
|
<a name='L250'></a><a href='#L250'>250</a>
|
||||||
|
<a name='L251'></a><a href='#L251'>251</a>
|
||||||
|
<a name='L252'></a><a href='#L252'>252</a>
|
||||||
|
<a name='L253'></a><a href='#L253'>253</a>
|
||||||
|
<a name='L254'></a><a href='#L254'>254</a>
|
||||||
|
<a name='L255'></a><a href='#L255'>255</a>
|
||||||
|
<a name='L256'></a><a href='#L256'>256</a>
|
||||||
|
<a name='L257'></a><a href='#L257'>257</a>
|
||||||
|
<a name='L258'></a><a href='#L258'>258</a>
|
||||||
|
<a name='L259'></a><a href='#L259'>259</a>
|
||||||
|
<a name='L260'></a><a href='#L260'>260</a>
|
||||||
|
<a name='L261'></a><a href='#L261'>261</a>
|
||||||
|
<a name='L262'></a><a href='#L262'>262</a>
|
||||||
|
<a name='L263'></a><a href='#L263'>263</a>
|
||||||
|
<a name='L264'></a><a href='#L264'>264</a>
|
||||||
|
<a name='L265'></a><a href='#L265'>265</a>
|
||||||
|
<a name='L266'></a><a href='#L266'>266</a>
|
||||||
|
<a name='L267'></a><a href='#L267'>267</a>
|
||||||
|
<a name='L268'></a><a href='#L268'>268</a>
|
||||||
|
<a name='L269'></a><a href='#L269'>269</a>
|
||||||
|
<a name='L270'></a><a href='#L270'>270</a>
|
||||||
|
<a name='L271'></a><a href='#L271'>271</a>
|
||||||
|
<a name='L272'></a><a href='#L272'>272</a>
|
||||||
|
<a name='L273'></a><a href='#L273'>273</a>
|
||||||
|
<a name='L274'></a><a href='#L274'>274</a>
|
||||||
|
<a name='L275'></a><a href='#L275'>275</a>
|
||||||
|
<a name='L276'></a><a href='#L276'>276</a>
|
||||||
|
<a name='L277'></a><a href='#L277'>277</a>
|
||||||
|
<a name='L278'></a><a href='#L278'>278</a>
|
||||||
|
<a name='L279'></a><a href='#L279'>279</a>
|
||||||
|
<a name='L280'></a><a href='#L280'>280</a>
|
||||||
|
<a name='L281'></a><a href='#L281'>281</a>
|
||||||
|
<a name='L282'></a><a href='#L282'>282</a>
|
||||||
|
<a name='L283'></a><a href='#L283'>283</a>
|
||||||
|
<a name='L284'></a><a href='#L284'>284</a>
|
||||||
|
<a name='L285'></a><a href='#L285'>285</a>
|
||||||
|
<a name='L286'></a><a href='#L286'>286</a>
|
||||||
|
<a name='L287'></a><a href='#L287'>287</a>
|
||||||
|
<a name='L288'></a><a href='#L288'>288</a>
|
||||||
|
<a name='L289'></a><a href='#L289'>289</a>
|
||||||
|
<a name='L290'></a><a href='#L290'>290</a>
|
||||||
|
<a name='L291'></a><a href='#L291'>291</a>
|
||||||
|
<a name='L292'></a><a href='#L292'>292</a>
|
||||||
|
<a name='L293'></a><a href='#L293'>293</a>
|
||||||
|
<a name='L294'></a><a href='#L294'>294</a>
|
||||||
|
<a name='L295'></a><a href='#L295'>295</a>
|
||||||
|
<a name='L296'></a><a href='#L296'>296</a>
|
||||||
|
<a name='L297'></a><a href='#L297'>297</a>
|
||||||
|
<a name='L298'></a><a href='#L298'>298</a>
|
||||||
|
<a name='L299'></a><a href='#L299'>299</a>
|
||||||
|
<a name='L300'></a><a href='#L300'>300</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, ErrorEvent, StackFrame, LightConfig } from '../types';
|
||||||
|
import { now, isObject, matchPatterns } from '../utils/helper';
|
||||||
|
import { computeFingerprint } from '../utils/hash';
|
||||||
|
import { sanitizeUrl, shouldIgnoreUrl } from '../utils/env';
|
||||||
|
import { parseStackTrace } from '../utils/stacktrace';
|
||||||
|
import { logError } from '../utils/logger';
|
||||||
|
|
||||||
|
interface ErrorPluginConfig {
|
||||||
|
maxStackFrames?: number;
|
||||||
|
captureNodeModules?: boolean;
|
||||||
|
captureColumn?: boolean;
|
||||||
|
relativePathOnly?: boolean;
|
||||||
|
sampleRate?: number;
|
||||||
|
resourceSampleRate?: number;
|
||||||
|
ignoreErrors?: (string | RegExp)[];
|
||||||
|
ignoreUrls?: (string | RegExp)[];
|
||||||
|
includePaths?: (string | RegExp)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >class ErrorPlugin implements LightPlugin {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name = 'error';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > version = '1.0.0';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private client!: LightClient;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private config: ErrorPluginConfig = {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > maxStackFrames: 5,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > captureNodeModules: false,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > captureColumn: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > relativePathOnly: false,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resourceSampleRate: 0.1,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
|
||||||
|
private originalOnError?: typeof window.onerror;
|
||||||
|
private onUnhandledRejection?: (event: PromiseRejectionEvent) => void;
|
||||||
|
private onResourceError?: (event: Event) => void;
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > setup(client: LightClient): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client = client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.loadConfig();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.setupGlobalError();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.setupUnhandledRejection();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.setupResourceError();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >loadConfig(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const clientConfig = this.client.config as LightConfig;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig && isObject(clientConfig.error)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config = { ...this.config, ...(clientConfig.error as object) };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig.ignoreErrors) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config.ignoreErrors = [...(this.config.ignoreErrors || []), ...clientConfig.ignoreErrors];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig.ignoreUrls) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config.ignoreUrls = [...(this.config.ignoreUrls || []), ...clientConfig.ignoreUrls];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig.includePaths) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config.includePaths = [...(this.config.includePaths || []), ...clientConfig.includePaths];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >shouldIgnoreError(message: string): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!message) return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return matchPatterns(message, this.config.ignoreErrors || []);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >shouldIgnoreScriptUrl(url: string): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return shouldIgnoreUrl(url, this.config.ignoreUrls || []);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >shouldIncludePath(frames: StackFrame[]): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const patterns = this.config.includePaths || [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (patterns.length === 0) return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return frames.some(frame => </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > patterns.some(pattern => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof pattern === 'string') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return frame.filename?.includes(pattern);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return pattern.test(frame.filename || '');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > })</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > );</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >shouldSample(): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const rate = this.config.sampleRate ?? 1;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (rate >= 1) return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (rate <= 0) return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return Math.random() < rate;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >shouldSampleResource(): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const rate = this.config.resourceSampleRate ?? 0.1;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (rate >= 1) return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (rate <= 0) return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return Math.random() < rate;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >setupGlobalError(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalOnError = window.onerror;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.onerror = (message, url, lineno, colno, error) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.originalOnError) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalOnError.call(window, message, url, lineno, colno, error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (url && this.shouldIgnoreScriptUrl(url as string)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const errorEvent = this.buildErrorEvent(</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > error || (message as string),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > url as string,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > lineno as number,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > colno as number</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > );</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (errorEvent) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client.captureEvent(errorEvent);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logError('ErrorPlugin', 'Global error handler error', e);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >setupUnhandledRejection(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.onUnhandledRejection = (event) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const reason = event.reason;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const sourceUrl = this.extractUrlFromReason(reason);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (sourceUrl && this.shouldIgnoreScriptUrl(sourceUrl)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const errorEvent = this.buildErrorEvent(reason);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (errorEvent) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (errorEvent.type === 'error') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > (errorEvent as ErrorEvent).tags = {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > ...((errorEvent as ErrorEvent).tags || {}),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > unhandled: 'true',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > promise: 'true',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client.captureEvent(errorEvent);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logError('ErrorPlugin', 'Unhandledrejection handler error', e);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('unhandledrejection', this.onUnhandledRejection);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >extractUrlFromReason(reason: unknown): string | null {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (reason instanceof Error && reason.stack) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const match = reason.stack.match(/at\s+(?:.+?\s+)?\((https?:\/\/[^:]+):\d+:\d+\)/);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (match) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return match[1];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const urlMatch = reason.stack.match(/(https?:\/\/[^\s)]+):\d+:\d+/);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (urlMatch) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return urlMatch[1];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >setupResourceError(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.onResourceError = (event) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const target = event.target;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!target) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const tagName = (target as HTMLElement).tagName?.toLowerCase();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const src = (target as HTMLImageElement).src || (target as HTMLLinkElement).href;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (tagName && src && ['img', 'script', 'link', 'audio', 'video'].includes(tagName)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.shouldIgnoreScriptUrl(src)) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.shouldSampleResource()) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const fingerprint = `resource:${tagName}`;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const sanitizedSrc = sanitizeUrl(src);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client.captureEvent({</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > level: 'warning',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > message: `Resource load failed: ${tagName} ${sanitizedSrc}`,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > timestamp: now(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > fingerprint,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > tags: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resource_type: tagName,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resource_url: sanitizedSrc,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > exception: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'ResourceError',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > value: `Failed to load ${tagName} resource`,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > logError('ErrorPlugin', 'Resource error handler error', e);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('error', this.onResourceError, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >buildErrorEvent(error: Error | unknown, url?: string, lineno?: number, colno?: number): Partial<ErrorEvent> & { type: string } | null {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const timestamp = now();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (error instanceof Error) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.shouldIgnoreError(error.message)) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const frames = parseStackTrace(error.stack, {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > captureNodeModules: this.config.captureNodeModules,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > captureColumn: this.config.captureColumn,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > relativePathOnly: this.config.relativePathOnly,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > maxFrames: this.config.maxStackFrames,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.shouldIncludePath(frames)) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.shouldSample()) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const fingerprint = computeFingerprint(error.name, error.message, frames);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > level: 'error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > message: error.message,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > timestamp,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > exception: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: error.name,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > value: error.message,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > stacktrace: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > frames,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > fingerprint,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const message = String(error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.shouldIgnoreError(message)) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const frames: StackFrame[] = [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (url) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > frames.push({</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > filename: url,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > lineno,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > colno: this.config.captureColumn ? colno : undefined,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > in_app: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.shouldIncludePath(frames)) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.shouldSample()) return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > level: 'error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > message,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > timestamp,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > exception: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'Error',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > value: message,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > stacktrace: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > frames,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > destroy(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.originalOnError !== undefined) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.onerror = this.originalOnError;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.onUnhandledRejection) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('unhandledrejection', this.onUnhandledRejection);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.onResourceError) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('error', this.onResourceError, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorPlugin;
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,706 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins/InteractionPlugin.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/plugins</a> InteractionPlugin.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">16.9% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>35/207</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/10</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">16.9% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>35/207</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, Transaction, Span, SpanStatus, LightConfig } from '../types';
|
||||||
|
import { now, isObject } from '../utils/helper';
|
||||||
|
|
||||||
|
interface InteractionPluginConfig {
|
||||||
|
enable?: boolean;
|
||||||
|
elementAttribute?: string;
|
||||||
|
maxTransactionDurationMs?: number;
|
||||||
|
idleTimeoutMs?: number;
|
||||||
|
captureScroll?: boolean;
|
||||||
|
trackClick?: boolean;
|
||||||
|
trackSubmit?: boolean;
|
||||||
|
trackKeypress?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TRACKING_TYPES = ['click', 'submit'];
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >class InteractionPlugin implements LightPlugin {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name = 'interaction';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > version = '1.0.0';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private client!: LightClient;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private config: InteractionPluginConfig = {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > enable: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > elementAttribute: 'data-sentry-component',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > maxTransactionDurationMs: 30000,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > idleTimeoutMs: 1000,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > trackClick: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > trackSubmit: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > trackKeypress: false,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private currentTransaction: Transaction | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private idleTimer: ReturnType<typeof setTimeout> | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private maxDurationTimer: ReturnType<typeof setTimeout> | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private debounceTimer: ReturnType<typeof setTimeout> | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > setup(client: LightClient): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client = client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.loadConfig();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.config.enable) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.config.trackClick) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > document.addEventListener('click', this.handleClick, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.config.trackSubmit) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > document.addEventListener('submit', this.handleSubmit, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private <span class="fstat-no" title="function not covered" >loadConfig(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const clientConfig = this.client.config as LightConfig;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig && isObject((clientConfig as Record<string, unknown>).interaction)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config = { ...this.config, ...((clientConfig as Record<string, unknown>).interaction as object) };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private handleClick = (event: Event): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.isEventIgnored(event)) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const target = event.target as HTMLElement;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const elementName = this.getElementName(target, 'click');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.scheduleInteractionTransaction(elementName, 'click', target);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private handleSubmit = (event: Event): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.isEventIgnored(event)) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const target = event.target as HTMLElement;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const elementName = this.getElementName(target, 'submit');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.scheduleInteractionTransaction(elementName, 'submit', target);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >isEventIgnored(event: Event): boolean {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const target = event.target as HTMLElement;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!target) return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (target.hasAttribute && target.hasAttribute('data-sentry-ignore')) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >scheduleInteractionTransaction(name: string, op: string, element: HTMLElement): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.debounceTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.debounceTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.debounceTimer = setTimeout(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.startInteractionTransaction(name, op, element);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }, 0);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >getElementName(element: HTMLElement | null, eventType: string): string {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!element) return eventType;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const attr = this.config.elementAttribute || 'data-sentry-component';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const componentName = element.getAttribute(attr);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (componentName) return componentName;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const tag = element.tagName?.toLowerCase() || 'element';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const id = element.id ? `#${element.id}` : '';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const classes = element.className && typeof element.className === 'string'</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > ? `.${element.className.split(' ').slice(0, 3).join('.')}`</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > : '';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return `${eventType}@${tag}${id}${classes}`;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >startInteractionTransaction(name: string, op: string, element: HTMLElement): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.client) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.currentTransaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('ok');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.client.startTransaction({</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > op: `ui.${op}`,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > description: `User ${op} on ${name}`,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'user-interaction',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > tags: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > 'interaction.type': op,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > 'interaction.target': name,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > data: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > tag: element.tagName?.toLowerCase(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > id: element.id || undefined,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > text: element.textContent?.substring(0, 100) || undefined,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (transaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction = transaction;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.scheduleIdleFinish();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.scheduleMaxDuration();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >scheduleIdleFinish(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.idleTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.idleTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const idleTimeout = this.config.idleTimeoutMs ?? 1000;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.idleTimer = setTimeout(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('ok');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }, idleTimeout);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >scheduleMaxDuration(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const maxDuration = this.config.maxTransactionDurationMs ?? 30000;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = setTimeout(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('deadline_exceeded');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }, maxDuration);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >finishCurrentTransaction(status: SpanStatus = 'ok'): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.idleTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.idleTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.idleTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.debounceTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.debounceTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.debounceTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.currentTransaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction.setStatus(status);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction.finish();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > destroy(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof document !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > document.removeEventListener('click', this.handleClick, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > document.removeEventListener('submit', this.handleSubmit, true);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.idleTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.idleTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.idleTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.debounceTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.debounceTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.debounceTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('aborted');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InteractionPlugin;
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,997 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins/OfflinePlugin.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/plugins</a> OfflinePlugin.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">14.47% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>44/304</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/15</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">14.47% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>44/304</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a>
|
||||||
|
<a name='L209'></a><a href='#L209'>209</a>
|
||||||
|
<a name='L210'></a><a href='#L210'>210</a>
|
||||||
|
<a name='L211'></a><a href='#L211'>211</a>
|
||||||
|
<a name='L212'></a><a href='#L212'>212</a>
|
||||||
|
<a name='L213'></a><a href='#L213'>213</a>
|
||||||
|
<a name='L214'></a><a href='#L214'>214</a>
|
||||||
|
<a name='L215'></a><a href='#L215'>215</a>
|
||||||
|
<a name='L216'></a><a href='#L216'>216</a>
|
||||||
|
<a name='L217'></a><a href='#L217'>217</a>
|
||||||
|
<a name='L218'></a><a href='#L218'>218</a>
|
||||||
|
<a name='L219'></a><a href='#L219'>219</a>
|
||||||
|
<a name='L220'></a><a href='#L220'>220</a>
|
||||||
|
<a name='L221'></a><a href='#L221'>221</a>
|
||||||
|
<a name='L222'></a><a href='#L222'>222</a>
|
||||||
|
<a name='L223'></a><a href='#L223'>223</a>
|
||||||
|
<a name='L224'></a><a href='#L224'>224</a>
|
||||||
|
<a name='L225'></a><a href='#L225'>225</a>
|
||||||
|
<a name='L226'></a><a href='#L226'>226</a>
|
||||||
|
<a name='L227'></a><a href='#L227'>227</a>
|
||||||
|
<a name='L228'></a><a href='#L228'>228</a>
|
||||||
|
<a name='L229'></a><a href='#L229'>229</a>
|
||||||
|
<a name='L230'></a><a href='#L230'>230</a>
|
||||||
|
<a name='L231'></a><a href='#L231'>231</a>
|
||||||
|
<a name='L232'></a><a href='#L232'>232</a>
|
||||||
|
<a name='L233'></a><a href='#L233'>233</a>
|
||||||
|
<a name='L234'></a><a href='#L234'>234</a>
|
||||||
|
<a name='L235'></a><a href='#L235'>235</a>
|
||||||
|
<a name='L236'></a><a href='#L236'>236</a>
|
||||||
|
<a name='L237'></a><a href='#L237'>237</a>
|
||||||
|
<a name='L238'></a><a href='#L238'>238</a>
|
||||||
|
<a name='L239'></a><a href='#L239'>239</a>
|
||||||
|
<a name='L240'></a><a href='#L240'>240</a>
|
||||||
|
<a name='L241'></a><a href='#L241'>241</a>
|
||||||
|
<a name='L242'></a><a href='#L242'>242</a>
|
||||||
|
<a name='L243'></a><a href='#L243'>243</a>
|
||||||
|
<a name='L244'></a><a href='#L244'>244</a>
|
||||||
|
<a name='L245'></a><a href='#L245'>245</a>
|
||||||
|
<a name='L246'></a><a href='#L246'>246</a>
|
||||||
|
<a name='L247'></a><a href='#L247'>247</a>
|
||||||
|
<a name='L248'></a><a href='#L248'>248</a>
|
||||||
|
<a name='L249'></a><a href='#L249'>249</a>
|
||||||
|
<a name='L250'></a><a href='#L250'>250</a>
|
||||||
|
<a name='L251'></a><a href='#L251'>251</a>
|
||||||
|
<a name='L252'></a><a href='#L252'>252</a>
|
||||||
|
<a name='L253'></a><a href='#L253'>253</a>
|
||||||
|
<a name='L254'></a><a href='#L254'>254</a>
|
||||||
|
<a name='L255'></a><a href='#L255'>255</a>
|
||||||
|
<a name='L256'></a><a href='#L256'>256</a>
|
||||||
|
<a name='L257'></a><a href='#L257'>257</a>
|
||||||
|
<a name='L258'></a><a href='#L258'>258</a>
|
||||||
|
<a name='L259'></a><a href='#L259'>259</a>
|
||||||
|
<a name='L260'></a><a href='#L260'>260</a>
|
||||||
|
<a name='L261'></a><a href='#L261'>261</a>
|
||||||
|
<a name='L262'></a><a href='#L262'>262</a>
|
||||||
|
<a name='L263'></a><a href='#L263'>263</a>
|
||||||
|
<a name='L264'></a><a href='#L264'>264</a>
|
||||||
|
<a name='L265'></a><a href='#L265'>265</a>
|
||||||
|
<a name='L266'></a><a href='#L266'>266</a>
|
||||||
|
<a name='L267'></a><a href='#L267'>267</a>
|
||||||
|
<a name='L268'></a><a href='#L268'>268</a>
|
||||||
|
<a name='L269'></a><a href='#L269'>269</a>
|
||||||
|
<a name='L270'></a><a href='#L270'>270</a>
|
||||||
|
<a name='L271'></a><a href='#L271'>271</a>
|
||||||
|
<a name='L272'></a><a href='#L272'>272</a>
|
||||||
|
<a name='L273'></a><a href='#L273'>273</a>
|
||||||
|
<a name='L274'></a><a href='#L274'>274</a>
|
||||||
|
<a name='L275'></a><a href='#L275'>275</a>
|
||||||
|
<a name='L276'></a><a href='#L276'>276</a>
|
||||||
|
<a name='L277'></a><a href='#L277'>277</a>
|
||||||
|
<a name='L278'></a><a href='#L278'>278</a>
|
||||||
|
<a name='L279'></a><a href='#L279'>279</a>
|
||||||
|
<a name='L280'></a><a href='#L280'>280</a>
|
||||||
|
<a name='L281'></a><a href='#L281'>281</a>
|
||||||
|
<a name='L282'></a><a href='#L282'>282</a>
|
||||||
|
<a name='L283'></a><a href='#L283'>283</a>
|
||||||
|
<a name='L284'></a><a href='#L284'>284</a>
|
||||||
|
<a name='L285'></a><a href='#L285'>285</a>
|
||||||
|
<a name='L286'></a><a href='#L286'>286</a>
|
||||||
|
<a name='L287'></a><a href='#L287'>287</a>
|
||||||
|
<a name='L288'></a><a href='#L288'>288</a>
|
||||||
|
<a name='L289'></a><a href='#L289'>289</a>
|
||||||
|
<a name='L290'></a><a href='#L290'>290</a>
|
||||||
|
<a name='L291'></a><a href='#L291'>291</a>
|
||||||
|
<a name='L292'></a><a href='#L292'>292</a>
|
||||||
|
<a name='L293'></a><a href='#L293'>293</a>
|
||||||
|
<a name='L294'></a><a href='#L294'>294</a>
|
||||||
|
<a name='L295'></a><a href='#L295'>295</a>
|
||||||
|
<a name='L296'></a><a href='#L296'>296</a>
|
||||||
|
<a name='L297'></a><a href='#L297'>297</a>
|
||||||
|
<a name='L298'></a><a href='#L298'>298</a>
|
||||||
|
<a name='L299'></a><a href='#L299'>299</a>
|
||||||
|
<a name='L300'></a><a href='#L300'>300</a>
|
||||||
|
<a name='L301'></a><a href='#L301'>301</a>
|
||||||
|
<a name='L302'></a><a href='#L302'>302</a>
|
||||||
|
<a name='L303'></a><a href='#L303'>303</a>
|
||||||
|
<a name='L304'></a><a href='#L304'>304</a>
|
||||||
|
<a name='L305'></a><a href='#L305'>305</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, SentryEvent, LightConfig } from '../types';
|
||||||
|
import { now } from '../utils/helper';
|
||||||
|
|
||||||
|
const DB_NAME = 'light-sentry-offline';
|
||||||
|
const STORE_NAME = 'events';
|
||||||
|
const MAX_EVENTS = 1000;
|
||||||
|
|
||||||
|
interface OfflineEvent {
|
||||||
|
id: string;
|
||||||
|
event: SentryEvent;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >class OfflinePlugin implements LightPlugin {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name = 'offline';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > version = '1.0.0';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private client!: LightClient;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private config: { maxEvents?: number } = { maxEvents: MAX_EVENTS };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private db: IDBDatabase | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private isOnline: boolean = true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private isSyncing: boolean = false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private onOnline?: () => void;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private onOffline?: () => void;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private pendingEvents: SentryEvent[] = [];</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > setup(client: LightClient): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client = client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.loadConfig();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.initDatabase();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.setupNetworkListeners();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >loadConfig(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const clientConfig = this.client.config as LightConfig;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig && typeof clientConfig.offline === 'object') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config = { ...this.config, ...(clientConfig.offline as object) };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > private async initDatabase(): Promise<void> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof indexedDB === 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.warn('[LightSDK] OfflinePlugin: IndexedDB not available');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const request = indexedDB.open(DB_NAME, 1);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onerror = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.warn('[LightSDK] OfflinePlugin: Failed to open IndexedDB');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > reject(request.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onsuccess = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.db = request.result;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resolve();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onupgradeneeded = (event) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const db = (event.target as IDBOpenDBRequest).result;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const currentVersion = event.oldVersion;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!db.objectStoreNames.contains(STORE_NAME)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > store.createIndex('timestamp', 'timestamp', { unique: false });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > </span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 未来版本迁移可以在这里处理:</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // if (currentVersion < 2) { ... }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >setupNetworkListeners(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.isOnline = navigator.onLine;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.onOnline = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.info('[LightSDK] OfflinePlugin: Network online');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.isOnline = true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.syncPendingEvents();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.onOffline = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.info('[LightSDK] OfflinePlugin: Network offline');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.isOnline = false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('online', this.onOnline);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('offline', this.onOffline);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >generateId(): string {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > private async saveToIndexedDB(event: SentryEvent): Promise<void> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.db) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const maxEvents = this.config.maxEvents || MAX_EVENTS;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const count = await this.getEventCount();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (count >= maxEvents) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > await this.deleteOldestEvents(count - maxEvents + 1);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.db!.transaction([STORE_NAME], 'readwrite');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = transaction.objectStore(STORE_NAME);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const offlineEvent: OfflineEvent = {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > id: this.generateId(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > event: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > ...event,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > offline: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } as unknown as SentryEvent,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > timestamp: now(),</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const addRequest = store.add(offlineEvent);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > addRequest.onsuccess = () => resolve();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > addRequest.onerror = () => reject(addRequest.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >getEventCount(): Promise<number> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.db) return Promise.resolve(0);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.db!.transaction([STORE_NAME], 'readonly');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = transaction.objectStore(STORE_NAME);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const countRequest = store.count();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > countRequest.onsuccess = () => resolve(countRequest.result);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > countRequest.onerror = () => reject(countRequest.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > private async deleteOldestEvents(count: number): Promise<void> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.db || count <= 0) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.db!.transaction([STORE_NAME], 'readwrite');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = transaction.objectStore(STORE_NAME);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const index = store.index('timestamp');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const cursorRequest = index.openCursor();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > let deleted = 0;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > cursorRequest.onsuccess = (event) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (cursor && deleted < count) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > cursor.delete();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > deleted++;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > cursor.continue();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } else {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resolve();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > cursorRequest.onerror = () => reject(cursorRequest.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > private async getAllStoredEvents(): Promise<OfflineEvent[]> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.db) return [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.db!.transaction([STORE_NAME], 'readonly');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = transaction.objectStore(STORE_NAME);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const index = store.index('timestamp');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const request = index.getAll();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onsuccess = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const events = request.result || [];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 按时间排序</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > events.sort((a, b) => a.timestamp - b.timestamp);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > resolve(events);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onerror = () => reject(request.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > private async clearStoredEvents(): Promise<void> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.db) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return new Promise((resolve, reject) => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.db!.transaction([STORE_NAME], 'readwrite');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const store = transaction.objectStore(STORE_NAME);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const request = store.clear();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onsuccess = () => resolve();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > request.onerror = () => reject(request.error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > async syncPendingEvents(): Promise<void> {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.isOnline || this.isSyncing) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.isSyncing = true;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.info('[LightSDK] OfflinePlugin: Syncing pending events');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > let syncedCount = 0;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 先处理内存中的事件</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > while (this.pendingEvents.length > 0) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const event = this.pendingEvents.shift() as Partial<SentryEvent> & { type: string };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client.captureEvent(event);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > syncedCount++;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (error) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.error('[LightSDK] OfflinePlugin: Failed to re-enqueue memory event', error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 再处理 IndexedDB 中的事件</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const storedEvents = await this.getAllStoredEvents();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (storedEvents.length > 0) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > for (const offlineEvent of storedEvents) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const event = offlineEvent.event as Partial<SentryEvent> & { type: string };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client.captureEvent(event);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > syncedCount++;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (error) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.error('[LightSDK] OfflinePlugin: Failed to re-enqueue event', error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 等待队列 flush</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > await this.client.flush();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // flush 失败不影响,事件已在队列中会自动重试</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 清空已同步的事件</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > await this.clearStoredEvents();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.info(`[LightSDK] OfflinePlugin: Synced ${syncedCount} events`);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } catch (error) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.error('[LightSDK] OfflinePlugin: Sync failed', error);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > } finally {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.isSyncing = false;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > beforeReport(event: SentryEvent): SentryEvent | null {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.isOnline) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const maxEvents = this.config.maxEvents || MAX_EVENTS;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.pendingEvents.length >= maxEvents) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.pendingEvents.shift();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.pendingEvents.push(event);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.savePendingEventsToDB();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const onlineEvent = { ...event };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > delete (onlineEvent as Record<string, unknown>).offline;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return onlineEvent as SentryEvent;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >savePendingEventsToDB(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.pendingEvents.length === 0) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const events = this.pendingEvents.splice(0, this.pendingEvents.length);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > let savedCount = 0;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const saveNext = () => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (savedCount >= events.length) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const event = events[savedCount];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.saveToIndexedDB(event).then(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > savedCount++;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > saveNext();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }).catch(err => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > console.error('[LightSDK] OfflinePlugin: Failed to save event offline', err);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > // 恢复剩余未保存的事件</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.pendingEvents.unshift(...events.slice(savedCount));</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > saveNext();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > destroy(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.onOnline) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('online', this.onOnline);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.onOffline) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('offline', this.onOffline);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.db) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.db.close();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.db = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OfflinePlugin;
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,628 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins/RouterPlugin.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/plugins</a> RouterPlugin.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">14.36% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>26/181</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>0/0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">0% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>0/10</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">14.36% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>26/181</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, Transaction, SpanStatus, LightConfig } from '../types';
|
||||||
|
import { now, isObject } from '../utils/helper';
|
||||||
|
|
||||||
|
interface RouterPluginConfig {
|
||||||
|
enable?: boolean;
|
||||||
|
instrumentHistory?: boolean;
|
||||||
|
instrumentHash?: boolean;
|
||||||
|
routeNameFormatter?: (url: string) => string;
|
||||||
|
maxTransactionDurationMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >class RouterPlugin implements LightPlugin {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name = 'router';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > version = '1.0.0';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private client!: LightClient;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private config: RouterPluginConfig = {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > enable: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > instrumentHistory: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > instrumentHash: true,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > maxTransactionDurationMs: 30000,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private currentTransaction: Transaction | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private lastRoute: string = '';</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private originalPushState?: typeof history.pushState;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private originalReplaceState?: typeof history.replaceState;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private maxDurationTimer: ReturnType<typeof setTimeout> | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private routeChangeTimer: ReturnType<typeof setTimeout> | null = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > setup(client: LightClient): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.client = client;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.loadConfig();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.config.enable) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.config.instrumentHistory) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.instrumentHistory();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.config.instrumentHash) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.instrumentHash();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private <span class="fstat-no" title="function not covered" >loadConfig(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const clientConfig = this.client.config as LightConfig;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (clientConfig && isObject((clientConfig as Record<string, unknown>).router)) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.config = { ...this.config, ...((clientConfig as Record<string, unknown>).router as object) };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private <span class="fstat-no" title="function not covered" >instrumentHistory(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof history === 'undefined' || !history.pushState) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalPushState = history.pushState.bind(history);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalReplaceState = history.replaceState.bind(history);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > history.pushState = (data: unknown, title: string, url?: string | null): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalPushState!(data, title, url);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.handleRouteChange(url || location.pathname + location.search, 'pushState');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > history.replaceState = (data: unknown, title: string, url?: string | null): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.originalReplaceState!(data, title, url);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.handleRouteChange(url || location.pathname + location.search, 'replaceState');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('popstate', this.handlePopState);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private handlePopState = (): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.handleRouteChange(location.pathname + location.search, 'popstate');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private <span class="fstat-no" title="function not covered" >instrumentHash(): void {</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.addEventListener('hashchange', this.handleHashChange);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > private handleHashChange = (): void => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.handleRouteChange(location.hash || '#/', 'hashchange');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > };</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >handleRouteChange(to: string, source: string): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.routeChangeTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.routeChangeTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.routeChangeTimer = setTimeout(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.routeChangeTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.processRouteChange(to, source);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }, 50);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >processRouteChange(to: string, source: string): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const routeName = this.config.routeNameFormatter</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > ? this.config.routeNameFormatter(to)</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > : to.split('?')[0];</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (routeName === this.lastRoute) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.lastRoute = routeName;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('ok');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.startNavigationTransaction(routeName, source);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >startNavigationTransaction(name: string, source: string): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (!this.client) return;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const transaction = this.client.startTransaction({</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > name,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > op: 'navigation',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > description: `Navigation to ${name}`,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > type: 'navigation',</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > tags: {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > 'route.name': name,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > 'route.source': source,</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > },</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > });</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (transaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction = transaction;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.startMaxDurationTimer();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >startMaxDurationTimer(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > const maxDuration = this.config.maxTransactionDurationMs ?? 30000;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = setTimeout(() => {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.currentTransaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction.setStatus('deadline_exceeded');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('deadline_exceeded');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }, maxDuration);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
private <span class="fstat-no" title="function not covered" >finishCurrentTransaction(status: SpanStatus = 'ok'): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.currentTransaction) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction.setStatus(status);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction.finish();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.currentTransaction = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
<span class="fstat-no" title="function not covered" > destroy(): void {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.originalPushState && typeof history !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > history.pushState = this.originalPushState;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.originalReplaceState && typeof history !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > history.replaceState = this.originalReplaceState;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (typeof window !== 'undefined') {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('popstate', this.handlePopState);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > window.removeEventListener('hashchange', this.handleHashChange);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.maxDurationTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.maxDurationTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.maxDurationTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > if (this.routeChangeTimer) {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > clearTimeout(this.routeChangeTimer);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.routeChangeTimer = null;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" ></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > this.finishCurrentTransaction('aborted');</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouterPlugin;
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins/SamplingPlugin.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/plugins</a> SamplingPlugin.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>94/94</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">89.28% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>25/28</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>8/8</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>94/94</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LightPlugin, LightClient, SentryEvent, LightConfig } from '../types';
|
||||||
|
import { isObject } from '../utils/helper';
|
||||||
|
import { logDebug } from '../utils/logger';
|
||||||
|
|
||||||
|
interface SampleRateConfig {
|
||||||
|
error?: number;
|
||||||
|
performance?: number;
|
||||||
|
network?: number;
|
||||||
|
behavior?: number;
|
||||||
|
[key: string]: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SamplingPluginConfig {
|
||||||
|
rates?: SampleRateConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SamplingPlugin implements LightPlugin {
|
||||||
|
name = 'sampling';
|
||||||
|
version = '1.0.0';
|
||||||
|
|
||||||
|
private client!: LightClient;
|
||||||
|
private config: SamplingPluginConfig = {
|
||||||
|
rates: {
|
||||||
|
error: 1,
|
||||||
|
performance: 1,
|
||||||
|
network: 1,
|
||||||
|
behavior: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setup(client: LightClient): void {
|
||||||
|
this.client = client;
|
||||||
|
this.loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadConfig(): void {
|
||||||
|
const clientConfig = this.client.config as LightConfig;
|
||||||
|
if (clientConfig && isObject(clientConfig.sampling)) {
|
||||||
|
const samplingConfig = clientConfig.sampling as object;
|
||||||
|
if ('rates' in samplingConfig && isObject((samplingConfig as { rates?: object }).rates)) {
|
||||||
|
this.config.rates = { ...this.config.rates, ...((samplingConfig as { rates: object }).rates as object) } as SampleRateConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSample(event: SentryEvent): boolean {
|
||||||
|
const globalRate = (this.client.config as LightConfig).<span class="branch-0 cbranch-no" title="branch not covered" >sampleRate ?? 1;</span>
|
||||||
|
if (globalRate <= 0) return false;
|
||||||
|
|
||||||
|
const typeRate = this.getTypeRate(event);
|
||||||
|
|
||||||
|
if (globalRate >= 1) {
|
||||||
|
return this.randomCheck(typeRate);
|
||||||
|
}
|
||||||
|
return this.randomCheck(globalRate * typeRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTypeRate(event: SentryEvent): number {
|
||||||
|
const rates = this.config.<span class="branch-0 cbranch-no" title="branch not covered" >rates || {};</span>
|
||||||
|
const type = event.type;
|
||||||
|
|
||||||
|
if (type === 'count') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate = rates[type];
|
||||||
|
return rate !== undefined ? rate : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomCheck(rate: number): boolean {
|
||||||
|
if (rate >= 1) return true;
|
||||||
|
if (rate <= 0) return false;
|
||||||
|
return Math.random() < rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeReport(event: SentryEvent): SentryEvent | null {
|
||||||
|
const sampled = this.shouldSample(event);
|
||||||
|
if (!sampled) {
|
||||||
|
logDebug('SamplingPlugin', 'Event dropped by sampling', {
|
||||||
|
type: event.type,
|
||||||
|
globalRate: (this.client.config as LightConfig).<span class="branch-0 cbranch-no" title="branch not covered" >sampleRate ?? 1,</span>
|
||||||
|
typeRate: this.getTypeRate(event),
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
// nothing to clean up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SamplingPlugin;
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/plugins</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> src/plugins</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">18.25% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>426/2334</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">89.28% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>25/28</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">7.47% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>8/107</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">18.25% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>426/2334</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line low'></div>
|
||||||
|
<div class="pad1">
|
||||||
|
<table class="coverage-summary">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||||
|
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||||
|
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||||
|
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||||
|
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||||
|
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||||
|
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td class="file low" data-value="BehaviorPlugin.ts"><a href="BehaviorPlugin.ts.html">BehaviorPlugin.ts</a></td>
|
||||||
|
<td data-value="15.52" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 15%"></div><div class="cover-empty" style="width: 85%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="15.52" class="pct low">15.52%</td>
|
||||||
|
<td data-value="335" class="abs low">52/335</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="13" class="abs low">0/13</td>
|
||||||
|
<td data-value="15.52" class="pct low">15.52%</td>
|
||||||
|
<td data-value="335" class="abs low">52/335</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="ErrorPlugin.ts"><a href="ErrorPlugin.ts.html">ErrorPlugin.ts</a></td>
|
||||||
|
<td data-value="17.39" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 17%"></div><div class="cover-empty" style="width: 83%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="17.39" class="pct low">17.39%</td>
|
||||||
|
<td data-value="299" class="abs low">52/299</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="14" class="abs low">0/14</td>
|
||||||
|
<td data-value="17.39" class="pct low">17.39%</td>
|
||||||
|
<td data-value="299" class="abs low">52/299</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="InteractionPlugin.ts"><a href="InteractionPlugin.ts.html">InteractionPlugin.ts</a></td>
|
||||||
|
<td data-value="16.9" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 16%"></div><div class="cover-empty" style="width: 84%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="16.9" class="pct low">16.9%</td>
|
||||||
|
<td data-value="207" class="abs low">35/207</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="10" class="abs low">0/10</td>
|
||||||
|
<td data-value="16.9" class="pct low">16.9%</td>
|
||||||
|
<td data-value="207" class="abs low">35/207</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="NetworkPlugin.ts"><a href="NetworkPlugin.ts.html">NetworkPlugin.ts</a></td>
|
||||||
|
<td data-value="14.32" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 14%"></div><div class="cover-empty" style="width: 86%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="14.32" class="pct low">14.32%</td>
|
||||||
|
<td data-value="384" class="abs low">55/384</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="14" class="abs low">0/14</td>
|
||||||
|
<td data-value="14.32" class="pct low">14.32%</td>
|
||||||
|
<td data-value="384" class="abs low">55/384</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="OfflinePlugin.ts"><a href="OfflinePlugin.ts.html">OfflinePlugin.ts</a></td>
|
||||||
|
<td data-value="14.47" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 14%"></div><div class="cover-empty" style="width: 86%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="14.47" class="pct low">14.47%</td>
|
||||||
|
<td data-value="304" class="abs low">44/304</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="15" class="abs low">0/15</td>
|
||||||
|
<td data-value="14.47" class="pct low">14.47%</td>
|
||||||
|
<td data-value="304" class="abs low">44/304</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="PerformancePlugin.ts"><a href="PerformancePlugin.ts.html">PerformancePlugin.ts</a></td>
|
||||||
|
<td data-value="12.83" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 12%"></div><div class="cover-empty" style="width: 88%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="12.83" class="pct low">12.83%</td>
|
||||||
|
<td data-value="530" class="abs low">68/530</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="23" class="abs low">0/23</td>
|
||||||
|
<td data-value="12.83" class="pct low">12.83%</td>
|
||||||
|
<td data-value="530" class="abs low">68/530</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file low" data-value="RouterPlugin.ts"><a href="RouterPlugin.ts.html">RouterPlugin.ts</a></td>
|
||||||
|
<td data-value="14.36" class="pic low">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 14%"></div><div class="cover-empty" style="width: 86%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="14.36" class="pct low">14.36%</td>
|
||||||
|
<td data-value="181" class="abs low">26/181</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
|
<td data-value="0" class="pct low">0%</td>
|
||||||
|
<td data-value="10" class="abs low">0/10</td>
|
||||||
|
<td data-value="14.36" class="pct low">14.36%</td>
|
||||||
|
<td data-value="181" class="abs low">26/181</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="SamplingPlugin.ts"><a href="SamplingPlugin.ts.html">SamplingPlugin.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="94" class="abs high">94/94</td>
|
||||||
|
<td data-value="89.28" class="pct high">89.28%</td>
|
||||||
|
<td data-value="28" class="abs high">25/28</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="8" class="abs high">8/8</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="94" class="abs high">94/94</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,382 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/baggage.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> baggage.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">97.97% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>97/99</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">97.14% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>34/35</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>3/3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">97.97% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>97/99</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">36x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { BaggageFields } from '../types';
|
||||||
|
|
||||||
|
const SENTRY_PREFIX = 'sentry-';
|
||||||
|
const MAX_BAGGAGE_LENGTH = 8192;
|
||||||
|
|
||||||
|
export function serializeBaggage(fields: BaggageFields): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
|
if (value === undefined || value === null || value === '') continue;
|
||||||
|
|
||||||
|
const prefixedKey = key.startsWith(SENTRY_PREFIX) ? key : `${SENTRY_PREFIX}${key}`;
|
||||||
|
parts.push(`${prefixedKey}=${encodeURIComponent(String(value))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = parts.join(',');
|
||||||
|
if (result.length > MAX_BAGGAGE_LENGTH) {
|
||||||
|
result = result.substring(0, MAX_BAGGAGE_LENGTH);
|
||||||
|
const lastComma = result.lastIndexOf(',');
|
||||||
|
if (lastComma > 0) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > result = result.substring(0, lastComma);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseBaggage(header: string): BaggageFields {
|
||||||
|
const fields: BaggageFields = {};
|
||||||
|
|
||||||
|
if (!header) return fields;
|
||||||
|
|
||||||
|
const parts = header.split(',');
|
||||||
|
for (const part of parts) {
|
||||||
|
const eqIdx = part.indexOf('=');
|
||||||
|
if (eqIdx === -1) continue;
|
||||||
|
|
||||||
|
const rawKey = part.substring(0, eqIdx).trim();
|
||||||
|
const value = decodeURIComponent(part.substring(eqIdx + 1).trim());
|
||||||
|
|
||||||
|
let key = rawKey;
|
||||||
|
if (rawKey.startsWith(SENTRY_PREFIX)) {
|
||||||
|
key = rawKey.substring(SENTRY_PREFIX.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
fields[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBaggageFromTransaction(
|
||||||
|
traceId: string,
|
||||||
|
options?: {
|
||||||
|
dsnPublicKey?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
sampleRate?: number;
|
||||||
|
user?: { id?: string; segment?: string };
|
||||||
|
release?: string;
|
||||||
|
environment?: string;
|
||||||
|
}
|
||||||
|
): BaggageFields {
|
||||||
|
const fields: BaggageFields = {
|
||||||
|
trace_id: traceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options?.dsnPublicKey) {
|
||||||
|
fields.public_key = options.dsnPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.sampled !== undefined) {
|
||||||
|
fields.sampled = options.sampled ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.sampleRate !== undefined) {
|
||||||
|
fields.sample_rate = String(options.sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.user) {
|
||||||
|
if (options.user.id) {
|
||||||
|
fields.user_id = options.user.id;
|
||||||
|
}
|
||||||
|
if (options.user.segment) {
|
||||||
|
fields.user_segment = options.user.segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.release) {
|
||||||
|
fields.release = options.release;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.environment) {
|
||||||
|
fields.environment = options.environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/dsn.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> dsn.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">93.1% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>27/29</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>5/5</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">66.66% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>2/3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">93.1% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>27/29</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { DSNInfo } from '../types';
|
||||||
|
|
||||||
|
export function parseDSN(dsn: string): DSNInfo {
|
||||||
|
try {
|
||||||
|
const match = dsn.match(/^(https?):\/\/([^@]+)@([^/]+)\/(.+)$/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('Invalid DSN format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, protocol, publicKey, host, projectId] = match;
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol,
|
||||||
|
publicKey,
|
||||||
|
host,
|
||||||
|
projectId,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Failed to parse DSN: ${(e as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEnvelopeUrl(dsn: DSNInfo): string {
|
||||||
|
return `${dsn.protocol}://${dsn.host}/api/${dsn.projectId}/envelope/?sentry_key=${dsn.publicKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export <span class="fstat-no" title="function not covered" >function getStoreUrl(dsn: DSNInfo): string {</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return `${dsn.protocol}://${dsn.host}/api/${dsn.projectId}/store/?sentry_key=${dsn.publicKey}`;</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,964 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/env.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> env.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>293/293</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">78.87% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>56/71</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>10/10</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>293/293</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a>
|
||||||
|
<a name='L102'></a><a href='#L102'>102</a>
|
||||||
|
<a name='L103'></a><a href='#L103'>103</a>
|
||||||
|
<a name='L104'></a><a href='#L104'>104</a>
|
||||||
|
<a name='L105'></a><a href='#L105'>105</a>
|
||||||
|
<a name='L106'></a><a href='#L106'>106</a>
|
||||||
|
<a name='L107'></a><a href='#L107'>107</a>
|
||||||
|
<a name='L108'></a><a href='#L108'>108</a>
|
||||||
|
<a name='L109'></a><a href='#L109'>109</a>
|
||||||
|
<a name='L110'></a><a href='#L110'>110</a>
|
||||||
|
<a name='L111'></a><a href='#L111'>111</a>
|
||||||
|
<a name='L112'></a><a href='#L112'>112</a>
|
||||||
|
<a name='L113'></a><a href='#L113'>113</a>
|
||||||
|
<a name='L114'></a><a href='#L114'>114</a>
|
||||||
|
<a name='L115'></a><a href='#L115'>115</a>
|
||||||
|
<a name='L116'></a><a href='#L116'>116</a>
|
||||||
|
<a name='L117'></a><a href='#L117'>117</a>
|
||||||
|
<a name='L118'></a><a href='#L118'>118</a>
|
||||||
|
<a name='L119'></a><a href='#L119'>119</a>
|
||||||
|
<a name='L120'></a><a href='#L120'>120</a>
|
||||||
|
<a name='L121'></a><a href='#L121'>121</a>
|
||||||
|
<a name='L122'></a><a href='#L122'>122</a>
|
||||||
|
<a name='L123'></a><a href='#L123'>123</a>
|
||||||
|
<a name='L124'></a><a href='#L124'>124</a>
|
||||||
|
<a name='L125'></a><a href='#L125'>125</a>
|
||||||
|
<a name='L126'></a><a href='#L126'>126</a>
|
||||||
|
<a name='L127'></a><a href='#L127'>127</a>
|
||||||
|
<a name='L128'></a><a href='#L128'>128</a>
|
||||||
|
<a name='L129'></a><a href='#L129'>129</a>
|
||||||
|
<a name='L130'></a><a href='#L130'>130</a>
|
||||||
|
<a name='L131'></a><a href='#L131'>131</a>
|
||||||
|
<a name='L132'></a><a href='#L132'>132</a>
|
||||||
|
<a name='L133'></a><a href='#L133'>133</a>
|
||||||
|
<a name='L134'></a><a href='#L134'>134</a>
|
||||||
|
<a name='L135'></a><a href='#L135'>135</a>
|
||||||
|
<a name='L136'></a><a href='#L136'>136</a>
|
||||||
|
<a name='L137'></a><a href='#L137'>137</a>
|
||||||
|
<a name='L138'></a><a href='#L138'>138</a>
|
||||||
|
<a name='L139'></a><a href='#L139'>139</a>
|
||||||
|
<a name='L140'></a><a href='#L140'>140</a>
|
||||||
|
<a name='L141'></a><a href='#L141'>141</a>
|
||||||
|
<a name='L142'></a><a href='#L142'>142</a>
|
||||||
|
<a name='L143'></a><a href='#L143'>143</a>
|
||||||
|
<a name='L144'></a><a href='#L144'>144</a>
|
||||||
|
<a name='L145'></a><a href='#L145'>145</a>
|
||||||
|
<a name='L146'></a><a href='#L146'>146</a>
|
||||||
|
<a name='L147'></a><a href='#L147'>147</a>
|
||||||
|
<a name='L148'></a><a href='#L148'>148</a>
|
||||||
|
<a name='L149'></a><a href='#L149'>149</a>
|
||||||
|
<a name='L150'></a><a href='#L150'>150</a>
|
||||||
|
<a name='L151'></a><a href='#L151'>151</a>
|
||||||
|
<a name='L152'></a><a href='#L152'>152</a>
|
||||||
|
<a name='L153'></a><a href='#L153'>153</a>
|
||||||
|
<a name='L154'></a><a href='#L154'>154</a>
|
||||||
|
<a name='L155'></a><a href='#L155'>155</a>
|
||||||
|
<a name='L156'></a><a href='#L156'>156</a>
|
||||||
|
<a name='L157'></a><a href='#L157'>157</a>
|
||||||
|
<a name='L158'></a><a href='#L158'>158</a>
|
||||||
|
<a name='L159'></a><a href='#L159'>159</a>
|
||||||
|
<a name='L160'></a><a href='#L160'>160</a>
|
||||||
|
<a name='L161'></a><a href='#L161'>161</a>
|
||||||
|
<a name='L162'></a><a href='#L162'>162</a>
|
||||||
|
<a name='L163'></a><a href='#L163'>163</a>
|
||||||
|
<a name='L164'></a><a href='#L164'>164</a>
|
||||||
|
<a name='L165'></a><a href='#L165'>165</a>
|
||||||
|
<a name='L166'></a><a href='#L166'>166</a>
|
||||||
|
<a name='L167'></a><a href='#L167'>167</a>
|
||||||
|
<a name='L168'></a><a href='#L168'>168</a>
|
||||||
|
<a name='L169'></a><a href='#L169'>169</a>
|
||||||
|
<a name='L170'></a><a href='#L170'>170</a>
|
||||||
|
<a name='L171'></a><a href='#L171'>171</a>
|
||||||
|
<a name='L172'></a><a href='#L172'>172</a>
|
||||||
|
<a name='L173'></a><a href='#L173'>173</a>
|
||||||
|
<a name='L174'></a><a href='#L174'>174</a>
|
||||||
|
<a name='L175'></a><a href='#L175'>175</a>
|
||||||
|
<a name='L176'></a><a href='#L176'>176</a>
|
||||||
|
<a name='L177'></a><a href='#L177'>177</a>
|
||||||
|
<a name='L178'></a><a href='#L178'>178</a>
|
||||||
|
<a name='L179'></a><a href='#L179'>179</a>
|
||||||
|
<a name='L180'></a><a href='#L180'>180</a>
|
||||||
|
<a name='L181'></a><a href='#L181'>181</a>
|
||||||
|
<a name='L182'></a><a href='#L182'>182</a>
|
||||||
|
<a name='L183'></a><a href='#L183'>183</a>
|
||||||
|
<a name='L184'></a><a href='#L184'>184</a>
|
||||||
|
<a name='L185'></a><a href='#L185'>185</a>
|
||||||
|
<a name='L186'></a><a href='#L186'>186</a>
|
||||||
|
<a name='L187'></a><a href='#L187'>187</a>
|
||||||
|
<a name='L188'></a><a href='#L188'>188</a>
|
||||||
|
<a name='L189'></a><a href='#L189'>189</a>
|
||||||
|
<a name='L190'></a><a href='#L190'>190</a>
|
||||||
|
<a name='L191'></a><a href='#L191'>191</a>
|
||||||
|
<a name='L192'></a><a href='#L192'>192</a>
|
||||||
|
<a name='L193'></a><a href='#L193'>193</a>
|
||||||
|
<a name='L194'></a><a href='#L194'>194</a>
|
||||||
|
<a name='L195'></a><a href='#L195'>195</a>
|
||||||
|
<a name='L196'></a><a href='#L196'>196</a>
|
||||||
|
<a name='L197'></a><a href='#L197'>197</a>
|
||||||
|
<a name='L198'></a><a href='#L198'>198</a>
|
||||||
|
<a name='L199'></a><a href='#L199'>199</a>
|
||||||
|
<a name='L200'></a><a href='#L200'>200</a>
|
||||||
|
<a name='L201'></a><a href='#L201'>201</a>
|
||||||
|
<a name='L202'></a><a href='#L202'>202</a>
|
||||||
|
<a name='L203'></a><a href='#L203'>203</a>
|
||||||
|
<a name='L204'></a><a href='#L204'>204</a>
|
||||||
|
<a name='L205'></a><a href='#L205'>205</a>
|
||||||
|
<a name='L206'></a><a href='#L206'>206</a>
|
||||||
|
<a name='L207'></a><a href='#L207'>207</a>
|
||||||
|
<a name='L208'></a><a href='#L208'>208</a>
|
||||||
|
<a name='L209'></a><a href='#L209'>209</a>
|
||||||
|
<a name='L210'></a><a href='#L210'>210</a>
|
||||||
|
<a name='L211'></a><a href='#L211'>211</a>
|
||||||
|
<a name='L212'></a><a href='#L212'>212</a>
|
||||||
|
<a name='L213'></a><a href='#L213'>213</a>
|
||||||
|
<a name='L214'></a><a href='#L214'>214</a>
|
||||||
|
<a name='L215'></a><a href='#L215'>215</a>
|
||||||
|
<a name='L216'></a><a href='#L216'>216</a>
|
||||||
|
<a name='L217'></a><a href='#L217'>217</a>
|
||||||
|
<a name='L218'></a><a href='#L218'>218</a>
|
||||||
|
<a name='L219'></a><a href='#L219'>219</a>
|
||||||
|
<a name='L220'></a><a href='#L220'>220</a>
|
||||||
|
<a name='L221'></a><a href='#L221'>221</a>
|
||||||
|
<a name='L222'></a><a href='#L222'>222</a>
|
||||||
|
<a name='L223'></a><a href='#L223'>223</a>
|
||||||
|
<a name='L224'></a><a href='#L224'>224</a>
|
||||||
|
<a name='L225'></a><a href='#L225'>225</a>
|
||||||
|
<a name='L226'></a><a href='#L226'>226</a>
|
||||||
|
<a name='L227'></a><a href='#L227'>227</a>
|
||||||
|
<a name='L228'></a><a href='#L228'>228</a>
|
||||||
|
<a name='L229'></a><a href='#L229'>229</a>
|
||||||
|
<a name='L230'></a><a href='#L230'>230</a>
|
||||||
|
<a name='L231'></a><a href='#L231'>231</a>
|
||||||
|
<a name='L232'></a><a href='#L232'>232</a>
|
||||||
|
<a name='L233'></a><a href='#L233'>233</a>
|
||||||
|
<a name='L234'></a><a href='#L234'>234</a>
|
||||||
|
<a name='L235'></a><a href='#L235'>235</a>
|
||||||
|
<a name='L236'></a><a href='#L236'>236</a>
|
||||||
|
<a name='L237'></a><a href='#L237'>237</a>
|
||||||
|
<a name='L238'></a><a href='#L238'>238</a>
|
||||||
|
<a name='L239'></a><a href='#L239'>239</a>
|
||||||
|
<a name='L240'></a><a href='#L240'>240</a>
|
||||||
|
<a name='L241'></a><a href='#L241'>241</a>
|
||||||
|
<a name='L242'></a><a href='#L242'>242</a>
|
||||||
|
<a name='L243'></a><a href='#L243'>243</a>
|
||||||
|
<a name='L244'></a><a href='#L244'>244</a>
|
||||||
|
<a name='L245'></a><a href='#L245'>245</a>
|
||||||
|
<a name='L246'></a><a href='#L246'>246</a>
|
||||||
|
<a name='L247'></a><a href='#L247'>247</a>
|
||||||
|
<a name='L248'></a><a href='#L248'>248</a>
|
||||||
|
<a name='L249'></a><a href='#L249'>249</a>
|
||||||
|
<a name='L250'></a><a href='#L250'>250</a>
|
||||||
|
<a name='L251'></a><a href='#L251'>251</a>
|
||||||
|
<a name='L252'></a><a href='#L252'>252</a>
|
||||||
|
<a name='L253'></a><a href='#L253'>253</a>
|
||||||
|
<a name='L254'></a><a href='#L254'>254</a>
|
||||||
|
<a name='L255'></a><a href='#L255'>255</a>
|
||||||
|
<a name='L256'></a><a href='#L256'>256</a>
|
||||||
|
<a name='L257'></a><a href='#L257'>257</a>
|
||||||
|
<a name='L258'></a><a href='#L258'>258</a>
|
||||||
|
<a name='L259'></a><a href='#L259'>259</a>
|
||||||
|
<a name='L260'></a><a href='#L260'>260</a>
|
||||||
|
<a name='L261'></a><a href='#L261'>261</a>
|
||||||
|
<a name='L262'></a><a href='#L262'>262</a>
|
||||||
|
<a name='L263'></a><a href='#L263'>263</a>
|
||||||
|
<a name='L264'></a><a href='#L264'>264</a>
|
||||||
|
<a name='L265'></a><a href='#L265'>265</a>
|
||||||
|
<a name='L266'></a><a href='#L266'>266</a>
|
||||||
|
<a name='L267'></a><a href='#L267'>267</a>
|
||||||
|
<a name='L268'></a><a href='#L268'>268</a>
|
||||||
|
<a name='L269'></a><a href='#L269'>269</a>
|
||||||
|
<a name='L270'></a><a href='#L270'>270</a>
|
||||||
|
<a name='L271'></a><a href='#L271'>271</a>
|
||||||
|
<a name='L272'></a><a href='#L272'>272</a>
|
||||||
|
<a name='L273'></a><a href='#L273'>273</a>
|
||||||
|
<a name='L274'></a><a href='#L274'>274</a>
|
||||||
|
<a name='L275'></a><a href='#L275'>275</a>
|
||||||
|
<a name='L276'></a><a href='#L276'>276</a>
|
||||||
|
<a name='L277'></a><a href='#L277'>277</a>
|
||||||
|
<a name='L278'></a><a href='#L278'>278</a>
|
||||||
|
<a name='L279'></a><a href='#L279'>279</a>
|
||||||
|
<a name='L280'></a><a href='#L280'>280</a>
|
||||||
|
<a name='L281'></a><a href='#L281'>281</a>
|
||||||
|
<a name='L282'></a><a href='#L282'>282</a>
|
||||||
|
<a name='L283'></a><a href='#L283'>283</a>
|
||||||
|
<a name='L284'></a><a href='#L284'>284</a>
|
||||||
|
<a name='L285'></a><a href='#L285'>285</a>
|
||||||
|
<a name='L286'></a><a href='#L286'>286</a>
|
||||||
|
<a name='L287'></a><a href='#L287'>287</a>
|
||||||
|
<a name='L288'></a><a href='#L288'>288</a>
|
||||||
|
<a name='L289'></a><a href='#L289'>289</a>
|
||||||
|
<a name='L290'></a><a href='#L290'>290</a>
|
||||||
|
<a name='L291'></a><a href='#L291'>291</a>
|
||||||
|
<a name='L292'></a><a href='#L292'>292</a>
|
||||||
|
<a name='L293'></a><a href='#L293'>293</a>
|
||||||
|
<a name='L294'></a><a href='#L294'>294</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">293x</span>
|
||||||
|
<span class="cline-any cline-yes">293x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">293x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">219x</span>
|
||||||
|
<span class="cline-any cline-yes">219x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">219x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">31x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">12x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { md5 } from './hash';
|
||||||
|
|
||||||
|
export interface BrowserInfo {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
major: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OSInfo {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceInfo {
|
||||||
|
type: 'desktop' | 'mobile' | 'tablet';
|
||||||
|
vendor: string;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvInfo {
|
||||||
|
browser: BrowserInfo;
|
||||||
|
os: OSInfo;
|
||||||
|
device: DeviceInfo;
|
||||||
|
url: string;
|
||||||
|
referrer: string;
|
||||||
|
title: string;
|
||||||
|
language: string;
|
||||||
|
user_agent: string;
|
||||||
|
screen_width: number;
|
||||||
|
screen_height: number;
|
||||||
|
viewport_width: number;
|
||||||
|
viewport_height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectBrowser(ua: string): BrowserInfo {
|
||||||
|
const browsers: { name: string; pattern: RegExp; versionIndex?: number }[] = [
|
||||||
|
{ name: 'WeChat', pattern: /MicroMessenger\/([\d.]+)/i },
|
||||||
|
{ name: 'QQ Browser', pattern: /QIBrowser\/([\d.]+)/i },
|
||||||
|
{ name: 'UC Browser', pattern: /UCBrowser\/([\d.]+)/i },
|
||||||
|
{ name: 'Edge', pattern: /Edg\/([\d.]+)/i },
|
||||||
|
{ name: 'Edge', pattern: /Edge\/([\d.]+)/i },
|
||||||
|
{ name: 'Opera', pattern: /OPR\/([\d.]+)/i },
|
||||||
|
{ name: 'Opera', pattern: /Opera\/([\d.]+)/i },
|
||||||
|
{ name: 'Firefox', pattern: /Firefox\/([\d.]+)/i },
|
||||||
|
{ name: 'Safari', pattern: /Version\/([\d.]+).*Safari/i },
|
||||||
|
{ name: 'Chrome', pattern: /Chrome\/([\d.]+)/i },
|
||||||
|
{ name: 'IE', pattern: /MSIE ([\d.]+)/i },
|
||||||
|
{ name: 'IE', pattern: /Trident.*rv:([\d.]+)/i },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const b of browsers) {
|
||||||
|
const match = ua.match(b.pattern);
|
||||||
|
if (match) {
|
||||||
|
const version = match[1<span class="branch-0 cbranch-no" title="branch not covered" >] || '';</span>
|
||||||
|
const major = version.split('.')[0<span class="branch-0 cbranch-no" title="branch not covered" >] || '';</span>
|
||||||
|
return { name: b.name, version, major };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: 'Unknown', version: '', major: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectOS(ua: string): OSInfo {
|
||||||
|
const oss: { name: string; pattern: RegExp; versionIndex?: number }[] = [
|
||||||
|
{ name: 'Windows 11', pattern: /Windows NT 10\.0.*Win64.*x64/i },
|
||||||
|
{ name: 'Windows 10', pattern: /Windows NT 10\.0/i },
|
||||||
|
{ name: 'Windows 8.1', pattern: /Windows NT 6\.3/i },
|
||||||
|
{ name: 'Windows 8', pattern: /Windows NT 6\.2/i },
|
||||||
|
{ name: 'Windows 7', pattern: /Windows NT 6\.1/i },
|
||||||
|
{ name: 'Windows Vista', pattern: /Windows NT 6\.0/i },
|
||||||
|
{ name: 'Windows XP', pattern: /Windows NT 5\.1/i },
|
||||||
|
{ name: 'macOS', pattern: /Mac OS X ([\d_.]+)/i },
|
||||||
|
{ name: 'iOS', pattern: /OS ([\d_.]+) like Mac OS X/i },
|
||||||
|
{ name: 'Android', pattern: /Android ([\d.]+)/i },
|
||||||
|
{ name: 'Android', pattern: /Android/i },
|
||||||
|
{ name: 'Linux', pattern: /Linux/i },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const o of oss) {
|
||||||
|
const match = ua.match(o.pattern);
|
||||||
|
if (match) {
|
||||||
|
let version = match[1] || '';
|
||||||
|
if (version) {
|
||||||
|
version = version.replace(/_/g, '.');
|
||||||
|
}
|
||||||
|
return { name: o.name, version };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: 'Unknown', version: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectDevice(ua: string): DeviceInfo {
|
||||||
|
const isMobile = /Mobile|Android|iPhone|iPod|BlackBerry|Windows Phone|Opera Mini/i.test(ua);
|
||||||
|
const isTablet = /Tablet|iPad|PlayBook|Kindle|Silk/i.test(ua);
|
||||||
|
|
||||||
|
let type: 'desktop' | 'mobile' | 'tablet' = 'desktop';
|
||||||
|
if (isTablet) {
|
||||||
|
type = 'tablet';
|
||||||
|
} else if (isMobile) {
|
||||||
|
type = 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
|
let vendor = '';
|
||||||
|
let model = '';
|
||||||
|
|
||||||
|
const iphoneMatch = ua.match(/iPhone/i);
|
||||||
|
const ipadMatch = ua.match(/iPad/i);
|
||||||
|
const samsungMatch = ua.match(/SM-([A-Z0-9]+)/i);
|
||||||
|
const pixelMatch = ua.match(/Pixel ([0-9A-Z]+)/i);
|
||||||
|
const huaweiMatch = ua.match(/HUAWEI ([A-Z0-9]+)/i);
|
||||||
|
|
||||||
|
if (iphoneMatch) {
|
||||||
|
vendor = 'Apple';
|
||||||
|
model = 'iPhone';
|
||||||
|
} else if (ipadMatch) {
|
||||||
|
vendor = 'Apple';
|
||||||
|
model = 'iPad';
|
||||||
|
} else if (samsungMatch) {
|
||||||
|
vendor = 'Samsung';
|
||||||
|
model = samsungMatch[1];
|
||||||
|
} else if (pixelMatch) {
|
||||||
|
vendor = 'Google';
|
||||||
|
model = `Pixel ${pixelMatch[1]}`;
|
||||||
|
} else if (huaweiMatch) {
|
||||||
|
vendor = 'Huawei';
|
||||||
|
model = huaweiMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type, vendor, model };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEnvInfo(): EnvInfo {
|
||||||
|
const ua = typeof navigator !== 'undefined' ? navigator.<span class="branch-0 cbranch-no" title="branch not covered" >userAgent : '';</span>
|
||||||
|
const browser = detectBrowser(ua);
|
||||||
|
const os = detectOS(ua);
|
||||||
|
const device = detectDevice(ua);
|
||||||
|
|
||||||
|
return {
|
||||||
|
browser,
|
||||||
|
os,
|
||||||
|
device,
|
||||||
|
url: typeof location !== 'undefined' ? location.<span class="branch-0 cbranch-no" title="branch not covered" >href : '',</span>
|
||||||
|
referrer: typeof document !== 'undefined' ? document.<span class="branch-0 cbranch-no" title="branch not covered" >referrer : '',</span>
|
||||||
|
title: typeof document !== 'undefined' ? document.<span class="branch-0 cbranch-no" title="branch not covered" >title : '',</span>
|
||||||
|
language: typeof navigator !== 'undefined' ? navigator.<span class="branch-0 cbranch-no" title="branch not covered" >language : '',</span>
|
||||||
|
user_agent: ua,
|
||||||
|
screen_width: typeof screen !== 'undefined' ? screen.<span class="branch-0 cbranch-no" title="branch not covered" >width : 0,</span>
|
||||||
|
screen_height: typeof screen !== 'undefined' ? screen.<span class="branch-0 cbranch-no" title="branch not covered" >height : 0,</span>
|
||||||
|
viewport_width: typeof window !== 'undefined' ? window.<span class="branch-0 cbranch-no" title="branch not covered" >innerWidth : 0,</span>
|
||||||
|
viewport_height: typeof window !== 'undefined' ? window.<span class="branch-0 cbranch-no" title="branch not covered" >innerHeight : 0,</span>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPageUrl(): string {
|
||||||
|
return typeof location !== 'undefined' ? location.<span class="branch-0 cbranch-no" title="branch not covered" >href : '';</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReferrer(): string {
|
||||||
|
return typeof document !== 'undefined' ? document.<span class="branch-0 cbranch-no" title="branch not covered" >referrer : '';</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitizeUrl(url: string): string {
|
||||||
|
if (!url) return url;
|
||||||
|
|
||||||
|
// 过滤 javascript: 伪协议防止 XSS
|
||||||
|
if (/^\s*javascript:/i.test(url)) {
|
||||||
|
return 'javascript:[filtered]';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const sensitiveParams = [
|
||||||
|
'password', 'passwd', 'pwd',
|
||||||
|
'token', 'access_token', 'refresh_token',
|
||||||
|
'api_key', 'apikey', 'key',
|
||||||
|
'secret', 'private_key',
|
||||||
|
'code', 'auth',
|
||||||
|
'id_token', 'idToken',
|
||||||
|
'session', 'session_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
const params = urlObj.searchParams;
|
||||||
|
let modified = false;
|
||||||
|
|
||||||
|
for (const key of Array.from(params.keys())) {
|
||||||
|
const lowerKey = key.toLowerCase().replace(/[-_]/g, '_');
|
||||||
|
if (sensitiveParams.some(s => lowerKey.includes(s))) {
|
||||||
|
params.set(key, '[Filtered]');
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlObj.hash && urlObj.hash.length > 1) {
|
||||||
|
const hashContent = urlObj.hash.substring(1);
|
||||||
|
if (hashContent.includes('=')) {
|
||||||
|
const hashParams = new URLSearchParams(hashContent);
|
||||||
|
let hashModified = false;
|
||||||
|
for (const key of Array.from(hashParams.keys())) {
|
||||||
|
const lowerKey = key.toLowerCase().replace(/[-_]/g, '_');
|
||||||
|
if (sensitiveParams.some(s => lowerKey.includes(s))) {
|
||||||
|
hashParams.set(key, '[Filtered]');
|
||||||
|
hashModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hashModified) {
|
||||||
|
urlObj.hash = '#' + hashParams.toString();
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
return urlObj.toString();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
} catch {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldIgnoreUrl(url: string, ignoreUrls: (string | RegExp)[] = []): boolean {
|
||||||
|
if (!ignoreUrls || ignoreUrls.length === 0) return false;
|
||||||
|
|
||||||
|
return ignoreUrls.some(pattern => {
|
||||||
|
if (typeof pattern === 'string') {
|
||||||
|
if (pattern.includes('*')) {
|
||||||
|
// 移除开头的 ^ 和结尾的 $,允许匹配 URL 的任意部分
|
||||||
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
||||||
|
const regex = new RegExp(escaped);
|
||||||
|
return regex.test(url);
|
||||||
|
}
|
||||||
|
return url.includes(pattern);
|
||||||
|
}
|
||||||
|
return pattern.test(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const BROWSER_CODE_MAP: Record<string, string> = {
|
||||||
|
'Chrome': 'c',
|
||||||
|
'Safari': 's',
|
||||||
|
'Firefox': 'f',
|
||||||
|
'Edge': 'e',
|
||||||
|
'Opera': 'o',
|
||||||
|
'IE': 'i',
|
||||||
|
'WeChat': 'w',
|
||||||
|
'QQ Browser': 'q',
|
||||||
|
'UC Browser': 'u',
|
||||||
|
'Mobile Chrome': 'cm',
|
||||||
|
'Mobile Safari': 'sm',
|
||||||
|
};
|
||||||
|
|
||||||
|
const OS_CODE_MAP: Record<string, string> = {
|
||||||
|
'Windows 11': 'w11',
|
||||||
|
'Windows 10': 'w10',
|
||||||
|
'Windows 8.1': 'w81',
|
||||||
|
'Windows 8': 'w8',
|
||||||
|
'Windows 7': 'w7',
|
||||||
|
'Windows Vista': 'wv',
|
||||||
|
'Windows XP': 'wxp',
|
||||||
|
'macOS': 'm',
|
||||||
|
'iOS': 'i',
|
||||||
|
'Android': 'a',
|
||||||
|
'Linux': 'l',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getContexts() {
|
||||||
|
const env = getEnvInfo();
|
||||||
|
const browserCode = BROWSER_CODE_MAP[env.browser.name<span class="branch-0 cbranch-no" title="branch not covered" >] || env.browser.name.toLowerCase();</span>
|
||||||
|
const osCode = OS_CODE_MAP[env.os.name<span class="branch-0 cbranch-no" title="branch not covered" >] || env.os.name.toLowerCase();</span>
|
||||||
|
|
||||||
|
return {
|
||||||
|
browser: {
|
||||||
|
name: browserCode,
|
||||||
|
version: env.browser.major,
|
||||||
|
},
|
||||||
|
os: {
|
||||||
|
name: osCode,
|
||||||
|
version: env.os.version.split('.')[0],
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
type: env.device.type,
|
||||||
|
brand: env.device.vendor || undefined,
|
||||||
|
model: env.device.model || undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextId(): string {
|
||||||
|
const env = getEnvInfo();
|
||||||
|
const key = `${env.browser.name}|${env.browser.major}|${env.os.name}|${env.os.version.split('.')[0]}|${env.device.type}|${env.device.vendor}|${env.device.model}|${env.language}`;
|
||||||
|
return md5(key).substring(0, 16);
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/field-encoder.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> field-encoder.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>88/88</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>15/15</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>2/2</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>88/88</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||||
|
* 字段短编码映射
|
||||||
|
*
|
||||||
|
* 将常用字段名编码为 2-3 个字符的短码,减少上报体积
|
||||||
|
*
|
||||||
|
* 编码前:{ "type": "error", "level": "error", "message": "..." }
|
||||||
|
* 编码后:{ "t": "e", "l": "e", "m": "..." }
|
||||||
|
*
|
||||||
|
* 注意:只对最外层高频字段编码,不对嵌套对象深度编码
|
||||||
|
* 避免复杂嵌套带来的解析问题和兼容性风险
|
||||||
|
*/
|
||||||
|
|
||||||
|
// SDK 端:字段名编码映射(只包含最外层高频字段)
|
||||||
|
export const FIELD_ENCODE_MAP: Record<string, string> = {
|
||||||
|
// 基础字段
|
||||||
|
event_id: 'eid',
|
||||||
|
timestamp: 'ts',
|
||||||
|
start_timestamp: 'sts',
|
||||||
|
type: 't',
|
||||||
|
level: 'l',
|
||||||
|
message: 'm',
|
||||||
|
platform: 'p',
|
||||||
|
release: 'r',
|
||||||
|
environment: 'e',
|
||||||
|
fingerprint: 'fp',
|
||||||
|
context_id: 'cid',
|
||||||
|
|
||||||
|
// 用户
|
||||||
|
user: 'u',
|
||||||
|
|
||||||
|
// 标签
|
||||||
|
tags: 'tg',
|
||||||
|
|
||||||
|
// 异常
|
||||||
|
exception: 'ex',
|
||||||
|
|
||||||
|
// 请求
|
||||||
|
request: 'req',
|
||||||
|
|
||||||
|
// 上下文
|
||||||
|
contexts: 'ctx',
|
||||||
|
|
||||||
|
// 面包屑
|
||||||
|
breadcrumbs: 'bc',
|
||||||
|
|
||||||
|
// Transaction
|
||||||
|
transaction: 'tx',
|
||||||
|
duration: 'd',
|
||||||
|
spans: 'sp',
|
||||||
|
|
||||||
|
// 额外信息
|
||||||
|
extra: 'xt',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 服务端:字段名解码映射(反向)
|
||||||
|
export const FIELD_DECODE_MAP: Record<string, string> = Object.fromEntries(
|
||||||
|
Object.entries(FIELD_ENCODE_MAP).map(([k, v]) => [v, k])
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码事件字段名(SDK 端使用)
|
||||||
|
* 只对最外层字段编码,避免深度编码的复杂性
|
||||||
|
*/
|
||||||
|
export function encodeFields(obj: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
|
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
const shortKey = FIELD_ENCODE_MAP[key] || key;
|
||||||
|
result[shortKey] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码事件字段名(服务端使用)
|
||||||
|
* 只对最外层字段解码
|
||||||
|
*/
|
||||||
|
export function decodeFields(obj: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
|
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
const fullKey = FIELD_DECODE_MAP[key] || key;
|
||||||
|
result[fullKey] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/hash.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> hash.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>20/20</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">83.33% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>5/6</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>3/3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>20/20</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">288x</span>
|
||||||
|
<span class="cline-any cline-yes">288x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">export function hashString(str: string): number {
|
||||||
|
let hash = 5381;
|
||||||
|
let i = str.length;
|
||||||
|
while (i) {
|
||||||
|
hash = (hash * 33) ^ str.charCodeAt(--i);
|
||||||
|
}
|
||||||
|
return hash >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(str: string): string {
|
||||||
|
// 使用 hashString 替代不安全的 md5
|
||||||
|
// 用于非加密场景(ID生成、指纹计算)
|
||||||
|
return hashString(str).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeFingerprint(type: string, message: string, frames: { filename: string; lineno?: number }[] = []): string {
|
||||||
|
const keyFrames = frames.slice(0, 3).map(f => `${f.filename}:${f.<span class="branch-0 cbranch-no" title="branch not covered" >lineno || 0}</span>`).join('|');
|
||||||
|
const normalizedMessage = message.replace(/['"]?\d+['"]?/g, '').replace(/\s+/g, ' ').trim();
|
||||||
|
return md5(`${type}:${normalizedMessage}:${keyFrames}`);
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/helper.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> helper.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">96.15% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>50/52</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">95.83% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>23/24</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>9/9</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">96.15% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>50/52</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">96x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">293x</span>
|
||||||
|
<span class="cline-any cline-yes">293x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">190x</span>
|
||||||
|
<span class="cline-any cline-yes">190x</span>
|
||||||
|
<span class="cline-any cline-yes">190x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">38x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">7x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">export function generateEventId(): string {
|
||||||
|
return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, () => {
|
||||||
|
return ((Math.random() * 16) | 0).toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function now(): number {
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uuid(): string {
|
||||||
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||||
|
return crypto.randomUUID().replace(/-/g, '');
|
||||||
|
<span class="branch-0 cbranch-no" title="branch not covered" > }</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > return generateEventId();</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" >}</span>
|
||||||
|
|
||||||
|
export function isFunction(fn: unknown): fn is (...args: unknown[]) => unknown {
|
||||||
|
return typeof fn === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isString(value: unknown): value is string {
|
||||||
|
return typeof value === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function safeGet<T>(fn: () => T, defaultValue: T): T {
|
||||||
|
try {
|
||||||
|
const result = fn();
|
||||||
|
return result === undefined ? defaultValue : result;
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncate(str: string, maxLen: number): string {
|
||||||
|
if (str.length <= maxLen) return str;
|
||||||
|
return str.slice(0, maxLen) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchPatterns(value: string, patterns: (string | RegExp)[]): boolean {
|
||||||
|
if (!patterns || patterns.length === 0) return false;
|
||||||
|
return patterns.some(pattern => {
|
||||||
|
if (typeof pattern === 'string') {
|
||||||
|
return value.includes(pattern);
|
||||||
|
}
|
||||||
|
return pattern.test(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/identity.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> identity.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>70/70</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>15/15</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>4/4</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>70/70</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">20x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">4x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-yes">16x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { uuid } from './helper';
|
||||||
|
|
||||||
|
const ANONYMOUS_ID_KEY = 'light_sentry_aid';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取匿名用户ID
|
||||||
|
* 优先从 localStorage 获取已存在的ID,不存在则生成并存储
|
||||||
|
*/
|
||||||
|
export function getAnonymousId(): string | null {
|
||||||
|
// 浏览器环境检查
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);
|
||||||
|
|
||||||
|
if (!anonymousId) {
|
||||||
|
anonymousId = uuid();
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return anonymousId;
|
||||||
|
} catch {
|
||||||
|
// localStorage 不可用(如隐私模式、跨域限制等)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置匿名用户ID
|
||||||
|
* 用于覆盖自动生成的ID
|
||||||
|
*/
|
||||||
|
export function setAnonymousId(id: string): boolean {
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, id);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除匿名用户ID
|
||||||
|
* 通常在用户登出后调用
|
||||||
|
*/
|
||||||
|
export function clearAnonymousId(): boolean {
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(ANONYMOUS_ID_KEY);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否启用了匿名用户追踪
|
||||||
|
*/
|
||||||
|
export function isAnonymousTrackingEnabled(config: { trackAnonymousUsers?: boolean }): boolean {
|
||||||
|
// 默认为 true,除非明确设置为 false
|
||||||
|
return config.trackAnonymousUsers !== false;
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> src/utils</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">97.36% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>1108/1138</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">87.87% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>319/363</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">96.92% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>63/65</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">97.36% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>1108/1138</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<div class="pad1">
|
||||||
|
<table class="coverage-summary">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||||
|
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||||
|
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||||
|
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||||
|
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||||
|
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||||
|
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td class="file high" data-value="baggage.ts"><a href="baggage.ts.html">baggage.ts</a></td>
|
||||||
|
<td data-value="97.97" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="97.97" class="pct high">97.97%</td>
|
||||||
|
<td data-value="99" class="abs high">97/99</td>
|
||||||
|
<td data-value="97.14" class="pct high">97.14%</td>
|
||||||
|
<td data-value="35" class="abs high">34/35</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="3" class="abs high">3/3</td>
|
||||||
|
<td data-value="97.97" class="pct high">97.97%</td>
|
||||||
|
<td data-value="99" class="abs high">97/99</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="dsn.ts"><a href="dsn.ts.html">dsn.ts</a></td>
|
||||||
|
<td data-value="93.1" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 93%"></div><div class="cover-empty" style="width: 7%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="93.1" class="pct high">93.1%</td>
|
||||||
|
<td data-value="29" class="abs high">27/29</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="5" class="abs high">5/5</td>
|
||||||
|
<td data-value="66.66" class="pct medium">66.66%</td>
|
||||||
|
<td data-value="3" class="abs medium">2/3</td>
|
||||||
|
<td data-value="93.1" class="pct high">93.1%</td>
|
||||||
|
<td data-value="29" class="abs high">27/29</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="env.ts"><a href="env.ts.html">env.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="293" class="abs high">293/293</td>
|
||||||
|
<td data-value="78.87" class="pct medium">78.87%</td>
|
||||||
|
<td data-value="71" class="abs medium">56/71</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="10" class="abs high">10/10</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="293" class="abs high">293/293</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="field-encoder.ts"><a href="field-encoder.ts.html">field-encoder.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="88" class="abs high">88/88</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="15" class="abs high">15/15</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="2" class="abs high">2/2</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="88" class="abs high">88/88</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="hash.ts"><a href="hash.ts.html">hash.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="20" class="abs high">20/20</td>
|
||||||
|
<td data-value="83.33" class="pct high">83.33%</td>
|
||||||
|
<td data-value="6" class="abs high">5/6</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="3" class="abs high">3/3</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="20" class="abs high">20/20</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="helper.ts"><a href="helper.ts.html">helper.ts</a></td>
|
||||||
|
<td data-value="96.15" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 96%"></div><div class="cover-empty" style="width: 4%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="96.15" class="pct high">96.15%</td>
|
||||||
|
<td data-value="52" class="abs high">50/52</td>
|
||||||
|
<td data-value="95.83" class="pct high">95.83%</td>
|
||||||
|
<td data-value="24" class="abs high">23/24</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="9" class="abs high">9/9</td>
|
||||||
|
<td data-value="96.15" class="pct high">96.15%</td>
|
||||||
|
<td data-value="52" class="abs high">50/52</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="identity.ts"><a href="identity.ts.html">identity.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="70" class="abs high">70/70</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="15" class="abs high">15/15</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="4" class="abs high">4/4</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="70" class="abs high">70/70</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="logger.ts"><a href="logger.ts.html">logger.ts</a></td>
|
||||||
|
<td data-value="100" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="31" class="abs high">31/31</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="13" class="abs high">13/13</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="4" class="abs high">4/4</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="31" class="abs high">31/31</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="stacktrace.ts"><a href="stacktrace.ts.html">stacktrace.ts</a></td>
|
||||||
|
<td data-value="98" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="98" class="pct high">98%</td>
|
||||||
|
<td data-value="100" class="abs high">98/100</td>
|
||||||
|
<td data-value="93.02" class="pct high">93.02%</td>
|
||||||
|
<td data-value="43" class="abs high">40/43</td>
|
||||||
|
<td data-value="100" class="pct high">100%</td>
|
||||||
|
<td data-value="1" class="abs high">1/1</td>
|
||||||
|
<td data-value="98" class="pct high">98%</td>
|
||||||
|
<td data-value="100" class="abs high">98/100</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="file high" data-value="tracing.ts"><a href="tracing.ts.html">tracing.ts</a></td>
|
||||||
|
<td data-value="93.82" class="pic high">
|
||||||
|
<div class="chart"><div class="cover-fill" style="width: 93%"></div><div class="cover-empty" style="width: 7%"></div></div>
|
||||||
|
</td>
|
||||||
|
<td data-value="93.82" class="pct high">93.82%</td>
|
||||||
|
<td data-value="356" class="abs high">334/356</td>
|
||||||
|
<td data-value="83.08" class="pct high">83.08%</td>
|
||||||
|
<td data-value="136" class="abs high">113/136</td>
|
||||||
|
<td data-value="96.15" class="pct high">96.15%</td>
|
||||||
|
<td data-value="26" class="abs high">25/26</td>
|
||||||
|
<td data-value="93.82" class="pct high">93.82%</td>
|
||||||
|
<td data-value="356" class="abs high">334/356</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/logger.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> logger.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>31/31</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>13/13</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>4/4</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>31/31</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">42x</span>
|
||||||
|
<span class="cline-any cline-yes">42x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">289x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">289x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">8x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">3x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">5x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">let debugEnabled = false;
|
||||||
|
|
||||||
|
export function setDebug(enabled: boolean): void {
|
||||||
|
debugEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDebugEnabled(): boolean {
|
||||||
|
return debugEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logDebug(tag: string, message: string, data?: unknown): void {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const prefix = `[LightSDK][${tag}]`;
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.log(`${prefix} ${timestamp} ${message}`, data);
|
||||||
|
} else {
|
||||||
|
console.log(`${prefix} ${timestamp} ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logError(tag: string, message: string, error?: unknown): void {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const prefix = `[LightSDK][${tag}][ERROR]`;
|
||||||
|
if (error !== undefined) {
|
||||||
|
console.error(`${prefix} ${timestamp} ${message}`, error);
|
||||||
|
} else {
|
||||||
|
console.error(`${prefix} ${timestamp} ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,385 @@
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Code coverage report for src/utils/stacktrace.ts</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="../../prettify.css" />
|
||||||
|
<link rel="stylesheet" href="../../base.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style type='text/css'>
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
background-image: url(../../sort-arrow-sprite.png);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='pad1'>
|
||||||
|
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/utils</a> stacktrace.ts</h1>
|
||||||
|
<div class='clearfix'>
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">98% </span>
|
||||||
|
<span class="quiet">Statements</span>
|
||||||
|
<span class='fraction'>98/100</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">93.02% </span>
|
||||||
|
<span class="quiet">Branches</span>
|
||||||
|
<span class='fraction'>40/43</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">100% </span>
|
||||||
|
<span class="quiet">Functions</span>
|
||||||
|
<span class='fraction'>1/1</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='fl pad1y space-right2'>
|
||||||
|
<span class="strong">98% </span>
|
||||||
|
<span class="quiet">Lines</span>
|
||||||
|
<span class='fraction'>98/100</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="quiet">
|
||||||
|
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||||
|
</p>
|
||||||
|
<template id="filterTemplate">
|
||||||
|
<div class="quiet">
|
||||||
|
Filter:
|
||||||
|
<input type="search" id="fileSearch">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class='status-line high'></div>
|
||||||
|
<pre><table class="coverage">
|
||||||
|
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||||
|
<a name='L2'></a><a href='#L2'>2</a>
|
||||||
|
<a name='L3'></a><a href='#L3'>3</a>
|
||||||
|
<a name='L4'></a><a href='#L4'>4</a>
|
||||||
|
<a name='L5'></a><a href='#L5'>5</a>
|
||||||
|
<a name='L6'></a><a href='#L6'>6</a>
|
||||||
|
<a name='L7'></a><a href='#L7'>7</a>
|
||||||
|
<a name='L8'></a><a href='#L8'>8</a>
|
||||||
|
<a name='L9'></a><a href='#L9'>9</a>
|
||||||
|
<a name='L10'></a><a href='#L10'>10</a>
|
||||||
|
<a name='L11'></a><a href='#L11'>11</a>
|
||||||
|
<a name='L12'></a><a href='#L12'>12</a>
|
||||||
|
<a name='L13'></a><a href='#L13'>13</a>
|
||||||
|
<a name='L14'></a><a href='#L14'>14</a>
|
||||||
|
<a name='L15'></a><a href='#L15'>15</a>
|
||||||
|
<a name='L16'></a><a href='#L16'>16</a>
|
||||||
|
<a name='L17'></a><a href='#L17'>17</a>
|
||||||
|
<a name='L18'></a><a href='#L18'>18</a>
|
||||||
|
<a name='L19'></a><a href='#L19'>19</a>
|
||||||
|
<a name='L20'></a><a href='#L20'>20</a>
|
||||||
|
<a name='L21'></a><a href='#L21'>21</a>
|
||||||
|
<a name='L22'></a><a href='#L22'>22</a>
|
||||||
|
<a name='L23'></a><a href='#L23'>23</a>
|
||||||
|
<a name='L24'></a><a href='#L24'>24</a>
|
||||||
|
<a name='L25'></a><a href='#L25'>25</a>
|
||||||
|
<a name='L26'></a><a href='#L26'>26</a>
|
||||||
|
<a name='L27'></a><a href='#L27'>27</a>
|
||||||
|
<a name='L28'></a><a href='#L28'>28</a>
|
||||||
|
<a name='L29'></a><a href='#L29'>29</a>
|
||||||
|
<a name='L30'></a><a href='#L30'>30</a>
|
||||||
|
<a name='L31'></a><a href='#L31'>31</a>
|
||||||
|
<a name='L32'></a><a href='#L32'>32</a>
|
||||||
|
<a name='L33'></a><a href='#L33'>33</a>
|
||||||
|
<a name='L34'></a><a href='#L34'>34</a>
|
||||||
|
<a name='L35'></a><a href='#L35'>35</a>
|
||||||
|
<a name='L36'></a><a href='#L36'>36</a>
|
||||||
|
<a name='L37'></a><a href='#L37'>37</a>
|
||||||
|
<a name='L38'></a><a href='#L38'>38</a>
|
||||||
|
<a name='L39'></a><a href='#L39'>39</a>
|
||||||
|
<a name='L40'></a><a href='#L40'>40</a>
|
||||||
|
<a name='L41'></a><a href='#L41'>41</a>
|
||||||
|
<a name='L42'></a><a href='#L42'>42</a>
|
||||||
|
<a name='L43'></a><a href='#L43'>43</a>
|
||||||
|
<a name='L44'></a><a href='#L44'>44</a>
|
||||||
|
<a name='L45'></a><a href='#L45'>45</a>
|
||||||
|
<a name='L46'></a><a href='#L46'>46</a>
|
||||||
|
<a name='L47'></a><a href='#L47'>47</a>
|
||||||
|
<a name='L48'></a><a href='#L48'>48</a>
|
||||||
|
<a name='L49'></a><a href='#L49'>49</a>
|
||||||
|
<a name='L50'></a><a href='#L50'>50</a>
|
||||||
|
<a name='L51'></a><a href='#L51'>51</a>
|
||||||
|
<a name='L52'></a><a href='#L52'>52</a>
|
||||||
|
<a name='L53'></a><a href='#L53'>53</a>
|
||||||
|
<a name='L54'></a><a href='#L54'>54</a>
|
||||||
|
<a name='L55'></a><a href='#L55'>55</a>
|
||||||
|
<a name='L56'></a><a href='#L56'>56</a>
|
||||||
|
<a name='L57'></a><a href='#L57'>57</a>
|
||||||
|
<a name='L58'></a><a href='#L58'>58</a>
|
||||||
|
<a name='L59'></a><a href='#L59'>59</a>
|
||||||
|
<a name='L60'></a><a href='#L60'>60</a>
|
||||||
|
<a name='L61'></a><a href='#L61'>61</a>
|
||||||
|
<a name='L62'></a><a href='#L62'>62</a>
|
||||||
|
<a name='L63'></a><a href='#L63'>63</a>
|
||||||
|
<a name='L64'></a><a href='#L64'>64</a>
|
||||||
|
<a name='L65'></a><a href='#L65'>65</a>
|
||||||
|
<a name='L66'></a><a href='#L66'>66</a>
|
||||||
|
<a name='L67'></a><a href='#L67'>67</a>
|
||||||
|
<a name='L68'></a><a href='#L68'>68</a>
|
||||||
|
<a name='L69'></a><a href='#L69'>69</a>
|
||||||
|
<a name='L70'></a><a href='#L70'>70</a>
|
||||||
|
<a name='L71'></a><a href='#L71'>71</a>
|
||||||
|
<a name='L72'></a><a href='#L72'>72</a>
|
||||||
|
<a name='L73'></a><a href='#L73'>73</a>
|
||||||
|
<a name='L74'></a><a href='#L74'>74</a>
|
||||||
|
<a name='L75'></a><a href='#L75'>75</a>
|
||||||
|
<a name='L76'></a><a href='#L76'>76</a>
|
||||||
|
<a name='L77'></a><a href='#L77'>77</a>
|
||||||
|
<a name='L78'></a><a href='#L78'>78</a>
|
||||||
|
<a name='L79'></a><a href='#L79'>79</a>
|
||||||
|
<a name='L80'></a><a href='#L80'>80</a>
|
||||||
|
<a name='L81'></a><a href='#L81'>81</a>
|
||||||
|
<a name='L82'></a><a href='#L82'>82</a>
|
||||||
|
<a name='L83'></a><a href='#L83'>83</a>
|
||||||
|
<a name='L84'></a><a href='#L84'>84</a>
|
||||||
|
<a name='L85'></a><a href='#L85'>85</a>
|
||||||
|
<a name='L86'></a><a href='#L86'>86</a>
|
||||||
|
<a name='L87'></a><a href='#L87'>87</a>
|
||||||
|
<a name='L88'></a><a href='#L88'>88</a>
|
||||||
|
<a name='L89'></a><a href='#L89'>89</a>
|
||||||
|
<a name='L90'></a><a href='#L90'>90</a>
|
||||||
|
<a name='L91'></a><a href='#L91'>91</a>
|
||||||
|
<a name='L92'></a><a href='#L92'>92</a>
|
||||||
|
<a name='L93'></a><a href='#L93'>93</a>
|
||||||
|
<a name='L94'></a><a href='#L94'>94</a>
|
||||||
|
<a name='L95'></a><a href='#L95'>95</a>
|
||||||
|
<a name='L96'></a><a href='#L96'>96</a>
|
||||||
|
<a name='L97'></a><a href='#L97'>97</a>
|
||||||
|
<a name='L98'></a><a href='#L98'>98</a>
|
||||||
|
<a name='L99'></a><a href='#L99'>99</a>
|
||||||
|
<a name='L100'></a><a href='#L100'>100</a>
|
||||||
|
<a name='L101'></a><a href='#L101'>101</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">73x</span>
|
||||||
|
<span class="cline-any cline-yes">73x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-no"> </span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">28x</span>
|
||||||
|
<span class="cline-any cline-yes">73x</span>
|
||||||
|
<span class="cline-any cline-yes">45x</span>
|
||||||
|
<span class="cline-any cline-yes">45x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">34x</span>
|
||||||
|
<span class="cline-any cline-yes">34x</span>
|
||||||
|
<span class="cline-any cline-yes">45x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">9x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">10x</span>
|
||||||
|
<span class="cline-any cline-yes">45x</span>
|
||||||
|
<span class="cline-any cline-yes">73x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
|
<span class="cline-any cline-yes">26x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-yes">23x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { StackFrame } from '../types';
|
||||||
|
|
||||||
|
export interface ParseStackOptions {
|
||||||
|
captureNodeModules?: boolean;
|
||||||
|
captureColumn?: boolean;
|
||||||
|
relativePathOnly?: boolean;
|
||||||
|
maxFrames?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseStackTrace(stack?: string, options: ParseStackOptions = {}): StackFrame[] {
|
||||||
|
if (!stack) return [];
|
||||||
|
|
||||||
|
const {
|
||||||
|
captureNodeModules = false,
|
||||||
|
captureColumn = true,
|
||||||
|
relativePathOnly = false,
|
||||||
|
maxFrames,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const frames: StackFrame[] = [];
|
||||||
|
const lines = stack.split('\n');
|
||||||
|
const origin = typeof location !== 'undefined' ? `${location.protocol}//${location.host}` : '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(/^\s*at\s*(.+?)\s*\((.+?):(\d+):(\d+)\)\s*$/);
|
||||||
|
if (match) {
|
||||||
|
const [, fn, filename, lineNum, colNum] = match;
|
||||||
|
|
||||||
|
const isNodeModules = filename.includes('node_modules');
|
||||||
|
if (!captureNodeModules && isNodeModules) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalFilename = filename;
|
||||||
|
if (relativePathOnly && <span class="branch-0 cbranch-no" title="branch not covered" >origin && filename.startsWith(origin)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span></span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > finalFilename = filename.substring(origin.length);</span>
|
||||||
|
<span class="cstat-no" title="statement not covered" > }</span>
|
||||||
|
|
||||||
|
frames.push({
|
||||||
|
filename: finalFilename,
|
||||||
|
function: fn,
|
||||||
|
lineno: parseInt(lineNum, 10),
|
||||||
|
colno: captureColumn ? parseInt(colNum, 10) : undefined,
|
||||||
|
in_app: !isNodeModules,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const v8Match = line.match(/^\s*at\s*(.+?)@(.+?):(\d+):(\d+)\s*$/);
|
||||||
|
if (v8Match) {
|
||||||
|
const [, fn, filename, lineNum, colNum] = v8Match;
|
||||||
|
|
||||||
|
const isNodeModules = filename.includes('node_modules');
|
||||||
|
if (!captureNodeModules && isNodeModules) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalFilename = filename;
|
||||||
|
if (relativePathOnly && origin && filename.startsWith(origin)) {
|
||||||
|
finalFilename = filename.substring(origin.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.push({
|
||||||
|
filename: finalFilename,
|
||||||
|
function: <span class="branch-0 cbranch-no" title="branch not covered" >fn || '<anonymous>',</span>
|
||||||
|
lineno: parseInt(lineNum, 10),
|
||||||
|
colno: captureColumn ? parseInt(colNum, 10) : undefined,
|
||||||
|
in_app: !isNodeModules,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlMatch = line.match(/^\s*at\s*(.+?):(\d+):(\d+)\s*$/);
|
||||||
|
if (urlMatch) {
|
||||||
|
const [, filename, lineNum, colNum] = urlMatch;
|
||||||
|
|
||||||
|
const isNodeModules = filename.includes('node_modules');
|
||||||
|
if (!captureNodeModules && isNodeModules) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalFilename = filename;
|
||||||
|
if (relativePathOnly && origin && filename.startsWith(origin)) {
|
||||||
|
finalFilename = filename.substring(origin.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.push({
|
||||||
|
filename: finalFilename,
|
||||||
|
lineno: parseInt(lineNum, 10),
|
||||||
|
colno: captureColumn ? parseInt(colNum, 10) : undefined,
|
||||||
|
in_app: !isNodeModules,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reversed = frames.reverse();
|
||||||
|
if (maxFrames !== undefined && maxFrames > 0) {
|
||||||
|
return reversed.slice(0, maxFrames);
|
||||||
|
}
|
||||||
|
return reversed;
|
||||||
|
}
|
||||||
|
</pre></td></tr></table></pre>
|
||||||
|
|
||||||
|
<div class='push'></div><!-- for sticky footer -->
|
||||||
|
</div><!-- /wrapper -->
|
||||||
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
|
Code coverage generated by
|
||||||
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
|
at 2026-06-25T14:49:54.914Z
|
||||||
|
</div>
|
||||||
|
<script src="../../prettify.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
prettyPrint();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="../../sorter.js"></script>
|
||||||
|
<script src="../../block-navigation.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,4 +1,4 @@
|
||||||
import type { LightConfig, LightClient, LightPlugin, DSNInfo, SentryEvent, EventLevel, UserInfo, Breadcrumb } from '../types';
|
import type { LightConfig, LightClient, LightPlugin, DSNInfo, SentryEvent, EventLevel, UserInfo, Breadcrumb, TransactionContext, SpanContext, Transaction, Span } from '../types';
|
||||||
declare class Client implements LightClient {
|
declare class Client implements LightClient {
|
||||||
dsn: DSNInfo;
|
dsn: DSNInfo;
|
||||||
private configManager;
|
private configManager;
|
||||||
|
|
@ -6,6 +6,7 @@ declare class Client implements LightClient {
|
||||||
private queue;
|
private queue;
|
||||||
private reporter;
|
private reporter;
|
||||||
private pluginManager;
|
private pluginManager;
|
||||||
|
private tracingManager;
|
||||||
private breadcrumbs;
|
private breadcrumbs;
|
||||||
private enabled;
|
private enabled;
|
||||||
private get maxBreadcrumbs();
|
private get maxBreadcrumbs();
|
||||||
|
|
@ -22,8 +23,15 @@ declare class Client implements LightClient {
|
||||||
captureEvent(event: Partial<SentryEvent> & {
|
captureEvent(event: Partial<SentryEvent> & {
|
||||||
type: string;
|
type: string;
|
||||||
}): void;
|
}): void;
|
||||||
|
private attachTraceContext;
|
||||||
private buildErrorEvent;
|
private buildErrorEvent;
|
||||||
setUser(user: UserInfo | null): void;
|
setUser(user: UserInfo | null): void;
|
||||||
|
setAnonymousId(id: string): void;
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null;
|
||||||
|
getCurrentTransaction(): Transaction | null;
|
||||||
|
getCurrentSpan(): Span | null;
|
||||||
|
setSpan(span: Span | null): void;
|
||||||
|
startSpan(context: SpanContext): Span | null;
|
||||||
setTag(key: string, value: string): void;
|
setTag(key: string, value: string): void;
|
||||||
setTags(tags: Record<string, string>): void;
|
setTags(tags: Record<string, string>): void;
|
||||||
setExtra(key: string, value: unknown): void;
|
setExtra(key: string, value: unknown): void;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export declare class ConfigManager {
|
||||||
setTag(key: string, value: string): void;
|
setTag(key: string, value: string): void;
|
||||||
setExtra(key: string, value: unknown): void;
|
setExtra(key: string, value: unknown): void;
|
||||||
isIgnoredError(message: string): boolean;
|
isIgnoredError(message: string): boolean;
|
||||||
|
shouldSample(): boolean;
|
||||||
/**
|
/**
|
||||||
* 获取编码后的环境信息(延迟初始化,只在首次上报时计算)
|
* 获取编码后的环境信息(延迟初始化,只在首次上报时计算)
|
||||||
* 注意:env.ts 的 getContexts 已经返回编码后的数据
|
* 注意:env.ts 的 getContexts 已经返回编码后的数据
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { LightConfig, SpanContext, TransactionContext, Span, Transaction, LightClient } from '../types';
|
||||||
|
export declare class TracingManager {
|
||||||
|
private config;
|
||||||
|
private _client?;
|
||||||
|
private currentTransaction;
|
||||||
|
private spanStack;
|
||||||
|
private dsnPublicKey?;
|
||||||
|
constructor(config: LightConfig, client?: LightClient, dsnPublicKey?: string);
|
||||||
|
updateConfig(config: LightConfig): void;
|
||||||
|
private shouldSample;
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null;
|
||||||
|
getCurrentTransaction(): Transaction | null;
|
||||||
|
setCurrentTransaction(transaction: Transaction | null): void;
|
||||||
|
getCurrentSpan(): Span | null;
|
||||||
|
setCurrentSpan(span: Span | null): void;
|
||||||
|
startSpan(context: SpanContext): Span | null;
|
||||||
|
onSpanFinished(span: Span): void;
|
||||||
|
onTransactionFinished(transaction: Transaction): void;
|
||||||
|
finishSpan(span: Span): void;
|
||||||
|
finishTransaction(transaction: Transaction): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -5,7 +5,11 @@ import NetworkPlugin from './plugins/NetworkPlugin';
|
||||||
import BehaviorPlugin from './plugins/BehaviorPlugin';
|
import BehaviorPlugin from './plugins/BehaviorPlugin';
|
||||||
import OfflinePlugin from './plugins/OfflinePlugin';
|
import OfflinePlugin from './plugins/OfflinePlugin';
|
||||||
import SamplingPlugin from './plugins/SamplingPlugin';
|
import SamplingPlugin from './plugins/SamplingPlugin';
|
||||||
import type { LightConfig, LightClient, SentryEvent, EventLevel, UserInfo, Breadcrumb, LightPlugin, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, DSNInfo } from './types';
|
import RouterPlugin from './plugins/RouterPlugin';
|
||||||
|
import InteractionPlugin from './plugins/InteractionPlugin';
|
||||||
|
import { Span as SpanClass, Transaction as TransactionClass, httpStatusToSpanStatus, dbStatusToSpanStatus, statusToSpanStatus } from './utils/tracing';
|
||||||
|
import { serializeBaggage, parseBaggage, createBaggageFromTransaction } from './utils/baggage';
|
||||||
|
import type { LightConfig, LightClient, SentryEvent, EventLevel, UserInfo, Breadcrumb, LightPlugin, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, DSNInfo, TransactionContext, SpanContext, Transaction, Span, SamplingContext, BaggageFields, SpanStatus } from './types';
|
||||||
declare function init(config: LightConfig): LightClient;
|
declare function init(config: LightConfig): LightClient;
|
||||||
declare function getClient(): LightClient | null;
|
declare function getClient(): LightClient | null;
|
||||||
declare function captureException(error: Error | unknown): void;
|
declare function captureException(error: Error | unknown): void;
|
||||||
|
|
@ -14,6 +18,14 @@ declare function captureEvent(event: Partial<SentryEvent> & {
|
||||||
type: string;
|
type: string;
|
||||||
}): void;
|
}): void;
|
||||||
declare function setUser(user: UserInfo | null): void;
|
declare function setUser(user: UserInfo | null): void;
|
||||||
|
declare function setAnonymousId(id: string): void;
|
||||||
|
declare function clearAnonymousId(): void;
|
||||||
|
declare function getAnonymousId(): string | null;
|
||||||
|
declare function startTransaction(context: TransactionContext): Transaction | null;
|
||||||
|
declare function getCurrentTransaction(): Transaction | null;
|
||||||
|
declare function getCurrentSpan(): Span | null;
|
||||||
|
declare function setSpan(span: Span | null): void;
|
||||||
|
declare function startSpan(context: SpanContext): Span | null;
|
||||||
declare function setTag(key: string, value: string): void;
|
declare function setTag(key: string, value: string): void;
|
||||||
declare function setTags(tags: Record<string, string>): void;
|
declare function setTags(tags: Record<string, string>): void;
|
||||||
declare function setExtra(key: string, value: unknown): void;
|
declare function setExtra(key: string, value: unknown): void;
|
||||||
|
|
@ -21,8 +33,9 @@ declare function addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
||||||
declare function flush(): Promise<void>;
|
declare function flush(): Promise<void>;
|
||||||
declare function disable(): void;
|
declare function disable(): void;
|
||||||
declare function enable(): void;
|
declare function enable(): void;
|
||||||
export { init, getClient, captureException, captureMessage, captureEvent, setUser, setTag, setTags, setExtra, addBreadcrumb, flush, disable, enable, Client, ErrorPlugin, PerformancePlugin, NetworkPlugin, BehaviorPlugin, OfflinePlugin, SamplingPlugin, };
|
export { init, getClient, captureException, captureMessage, captureEvent, setUser, setAnonymousId, clearAnonymousId, getAnonymousId, startTransaction, getCurrentTransaction, getCurrentSpan, setSpan, startSpan, setTag, setTags, setExtra, addBreadcrumb, flush, disable, enable, Client, httpStatusToSpanStatus, dbStatusToSpanStatus, statusToSpanStatus, serializeBaggage, parseBaggage, createBaggageFromTransaction, ErrorPlugin, PerformancePlugin, NetworkPlugin, BehaviorPlugin, OfflinePlugin, SamplingPlugin, RouterPlugin, InteractionPlugin, };
|
||||||
export type { LightConfig, LightClient, LightPlugin, SentryEvent, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, EventLevel, UserInfo, Breadcrumb, DSNInfo, };
|
export { SpanClass as Span, TransactionClass as Transaction };
|
||||||
|
export type { LightConfig, LightClient, LightPlugin, SentryEvent, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, EventLevel, UserInfo, Breadcrumb, DSNInfo, TransactionContext, SpanContext, SamplingContext, BaggageFields, SpanStatus, SpanJSON, TransactionJSON, };
|
||||||
declare const _default: {
|
declare const _default: {
|
||||||
init: typeof init;
|
init: typeof init;
|
||||||
getClient: typeof getClient;
|
getClient: typeof getClient;
|
||||||
|
|
@ -30,6 +43,14 @@ declare const _default: {
|
||||||
captureMessage: typeof captureMessage;
|
captureMessage: typeof captureMessage;
|
||||||
captureEvent: typeof captureEvent;
|
captureEvent: typeof captureEvent;
|
||||||
setUser: typeof setUser;
|
setUser: typeof setUser;
|
||||||
|
setAnonymousId: typeof setAnonymousId;
|
||||||
|
clearAnonymousId: typeof clearAnonymousId;
|
||||||
|
getAnonymousId: typeof getAnonymousId;
|
||||||
|
startTransaction: typeof startTransaction;
|
||||||
|
getCurrentTransaction: typeof getCurrentTransaction;
|
||||||
|
getCurrentSpan: typeof getCurrentSpan;
|
||||||
|
setSpan: typeof setSpan;
|
||||||
|
startSpan: typeof startSpan;
|
||||||
setTag: typeof setTag;
|
setTag: typeof setTag;
|
||||||
setTags: typeof setTags;
|
setTags: typeof setTags;
|
||||||
setExtra: typeof setExtra;
|
setExtra: typeof setExtra;
|
||||||
|
|
@ -37,5 +58,8 @@ declare const _default: {
|
||||||
flush: typeof flush;
|
flush: typeof flush;
|
||||||
disable: typeof disable;
|
disable: typeof disable;
|
||||||
enable: typeof enable;
|
enable: typeof enable;
|
||||||
|
Span: typeof SpanClass;
|
||||||
|
Transaction: typeof TransactionClass;
|
||||||
|
httpStatusToSpanStatus: typeof httpStatusToSpanStatus;
|
||||||
};
|
};
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,24 @@
|
||||||
|
import type { LightPlugin, LightClient } from '../types';
|
||||||
|
declare class InteractionPlugin implements LightPlugin {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
private client;
|
||||||
|
private config;
|
||||||
|
private currentTransaction;
|
||||||
|
private idleTimer;
|
||||||
|
private maxDurationTimer;
|
||||||
|
private debounceTimer;
|
||||||
|
setup(client: LightClient): void;
|
||||||
|
private loadConfig;
|
||||||
|
private handleClick;
|
||||||
|
private handleSubmit;
|
||||||
|
private isEventIgnored;
|
||||||
|
private scheduleInteractionTransaction;
|
||||||
|
private getElementName;
|
||||||
|
private startInteractionTransaction;
|
||||||
|
private scheduleIdleFinish;
|
||||||
|
private scheduleMaxDuration;
|
||||||
|
private finishCurrentTransaction;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
export default InteractionPlugin;
|
||||||
|
|
@ -10,7 +10,12 @@ declare class NetworkPlugin implements LightPlugin {
|
||||||
setup(client: LightClient): void;
|
setup(client: LightClient): void;
|
||||||
private loadConfig;
|
private loadConfig;
|
||||||
private shouldIgnore;
|
private shouldIgnore;
|
||||||
|
private shouldTracePropagate;
|
||||||
|
private isSameOrigin;
|
||||||
|
private getBaggageHeader;
|
||||||
private shouldSample;
|
private shouldSample;
|
||||||
|
private createHttpSpan;
|
||||||
|
private finishHttpSpan;
|
||||||
private patchFetch;
|
private patchFetch;
|
||||||
private patchXHR;
|
private patchXHR;
|
||||||
private reportNetwork;
|
private reportNetwork;
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,30 @@ declare class PerformancePlugin implements LightPlugin {
|
||||||
private lastLongTaskEndTime;
|
private lastLongTaskEndTime;
|
||||||
private ttiReported;
|
private ttiReported;
|
||||||
private ttiTimer;
|
private ttiTimer;
|
||||||
|
private pageLoadTransaction;
|
||||||
|
private measurements;
|
||||||
|
private resourceSpanCount;
|
||||||
setup(client: LightClient): void;
|
setup(client: LightClient): void;
|
||||||
|
private startPageLoadTransaction;
|
||||||
private loadConfig;
|
private loadConfig;
|
||||||
private shouldSample;
|
private shouldSample;
|
||||||
private observeWebVitals;
|
private observeWebVitals;
|
||||||
private observeFP;
|
private observePaint;
|
||||||
private observeLCP;
|
private observeLCP;
|
||||||
private observeFID;
|
private observeFID;
|
||||||
private observeCLS;
|
private observeCLS;
|
||||||
private observeFCP;
|
|
||||||
private observeTTFB;
|
private observeTTFB;
|
||||||
private observeLongTasks;
|
private observeLongTasks;
|
||||||
private scheduleTTI;
|
private scheduleTTI;
|
||||||
private observeNavigation;
|
private observeNavigation;
|
||||||
|
private buildNavigationSpans;
|
||||||
|
private finishPageLoadTransaction;
|
||||||
private observeResources;
|
private observeResources;
|
||||||
private getRating;
|
private getRating;
|
||||||
private reportMetric;
|
private reportMetric;
|
||||||
|
private recordMeasurement;
|
||||||
|
private createNavigationSpan;
|
||||||
|
private createResourceSpan;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
export default PerformancePlugin;
|
export default PerformancePlugin;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import type { LightPlugin, LightClient } from '../types';
|
||||||
|
declare class RouterPlugin implements LightPlugin {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
private client;
|
||||||
|
private config;
|
||||||
|
private currentTransaction;
|
||||||
|
private lastRoute;
|
||||||
|
private originalPushState?;
|
||||||
|
private originalReplaceState?;
|
||||||
|
private maxDurationTimer;
|
||||||
|
private routeChangeTimer;
|
||||||
|
setup(client: LightClient): void;
|
||||||
|
private loadConfig;
|
||||||
|
private instrumentHistory;
|
||||||
|
private handlePopState;
|
||||||
|
private instrumentHash;
|
||||||
|
private handleHashChange;
|
||||||
|
private handleRouteChange;
|
||||||
|
private processRouteChange;
|
||||||
|
private startNavigationTransaction;
|
||||||
|
private startMaxDurationTimer;
|
||||||
|
private finishCurrentTransaction;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
export default RouterPlugin;
|
||||||
|
|
@ -44,10 +44,31 @@ export interface DeviceContext {
|
||||||
brand?: string;
|
brand?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
export interface TraceContext {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id?: string;
|
||||||
|
op?: string;
|
||||||
|
status?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
export interface BaggageFields {
|
||||||
|
trace_id?: string;
|
||||||
|
public_key?: string;
|
||||||
|
sample_rate?: string;
|
||||||
|
sampled?: string;
|
||||||
|
user_id?: string;
|
||||||
|
user_segment?: string;
|
||||||
|
release?: string;
|
||||||
|
environment?: string;
|
||||||
|
replay_id?: string;
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
}
|
||||||
export interface Contexts {
|
export interface Contexts {
|
||||||
browser?: BrowserContext;
|
browser?: BrowserContext;
|
||||||
os?: OSContext;
|
os?: OSContext;
|
||||||
device?: DeviceContext;
|
device?: DeviceContext;
|
||||||
|
trace?: TraceContext;
|
||||||
}
|
}
|
||||||
export interface Breadcrumb {
|
export interface Breadcrumb {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -64,6 +85,7 @@ export interface BaseEvent {
|
||||||
release?: string;
|
release?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
user?: UserInfo;
|
user?: UserInfo;
|
||||||
|
anonymousId?: string;
|
||||||
tags?: Record<string, string>;
|
tags?: Record<string, string>;
|
||||||
extra?: Record<string, unknown>;
|
extra?: Record<string, unknown>;
|
||||||
breadcrumbs?: Breadcrumb[];
|
breadcrumbs?: Breadcrumb[];
|
||||||
|
|
@ -111,7 +133,91 @@ export interface CountEvent extends BaseEvent {
|
||||||
ts_start: number;
|
ts_start: number;
|
||||||
ts_end: number;
|
ts_end: number;
|
||||||
}
|
}
|
||||||
export type SentryEvent = ErrorEvent | PerformanceEvent | NetworkEvent | BehaviorEvent | CountEvent;
|
export type SpanStatus = 'ok' | 'cancelled' | 'unknown' | 'unknown_error' | 'invalid_argument' | 'deadline_exceeded' | 'not_found' | 'already_exists' | 'permission_denied' | 'resource_exhausted' | 'failed_precondition' | 'aborted' | 'out_of_range' | 'unimplemented' | 'internal_error' | 'unavailable' | 'data_loss' | 'unauthenticated';
|
||||||
|
export interface SpanContext {
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
startTimestamp?: number;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
traceId?: string;
|
||||||
|
spanId?: string;
|
||||||
|
parentSpanId?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
}
|
||||||
|
export interface SamplingContext extends TransactionContext {
|
||||||
|
parentSampled?: boolean;
|
||||||
|
}
|
||||||
|
export interface TransactionContext extends SpanContext {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
export interface SpanJSON {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
export interface TransactionJSON extends SpanJSON {
|
||||||
|
transaction: string;
|
||||||
|
type?: string;
|
||||||
|
spans: SpanJSON[];
|
||||||
|
sampled?: boolean;
|
||||||
|
}
|
||||||
|
export interface Span {
|
||||||
|
readonly traceId: string;
|
||||||
|
readonly spanId: string;
|
||||||
|
readonly parentSpanId?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags: Record<string, string>;
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
readonly startTimestamp: number;
|
||||||
|
readonly endTimestamp?: number;
|
||||||
|
readonly transaction?: Transaction;
|
||||||
|
setTag(key: string, value: string): this;
|
||||||
|
setData(key: string, value: unknown): this;
|
||||||
|
setStatus(status: SpanStatus): this;
|
||||||
|
setHttpStatus(statusCode: number): this;
|
||||||
|
setStatusFromResponse(status: string | number | undefined, tagKey?: string): this;
|
||||||
|
startChild(context?: SpanContext): Span;
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
toTraceparent(): string;
|
||||||
|
toJSON(): SpanJSON;
|
||||||
|
}
|
||||||
|
export interface Transaction extends Omit<Span, 'toJSON'> {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
spans: Span[];
|
||||||
|
sampled?: boolean;
|
||||||
|
maxSpans?: number;
|
||||||
|
toJSON(): TransactionJSON;
|
||||||
|
toEvent(): TransactionEvent;
|
||||||
|
}
|
||||||
|
export interface MeasurementValue {
|
||||||
|
value: number;
|
||||||
|
unit?: string;
|
||||||
|
rating?: 'good' | 'needs-improvement' | 'poor';
|
||||||
|
}
|
||||||
|
export interface TransactionEvent extends BaseEvent {
|
||||||
|
type: 'transaction';
|
||||||
|
transaction: string;
|
||||||
|
transaction_info?: {
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
spans: Array<ReturnType<Span['toJSON']>>;
|
||||||
|
start_timestamp: number;
|
||||||
|
measurements?: Record<string, MeasurementValue>;
|
||||||
|
}
|
||||||
|
export type SentryEvent = ErrorEvent | PerformanceEvent | NetworkEvent | BehaviorEvent | CountEvent | TransactionEvent;
|
||||||
export interface DSNInfo {
|
export interface DSNInfo {
|
||||||
protocol: string;
|
protocol: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
|
|
@ -134,6 +240,13 @@ export interface LightConfig {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
sampleRate?: number;
|
sampleRate?: number;
|
||||||
|
tracesSampleRate?: number;
|
||||||
|
tracesSampler?: (context: SamplingContext) => number | boolean;
|
||||||
|
/**
|
||||||
|
* @deprecated 请使用 tracePropagationTargets 替代
|
||||||
|
*/
|
||||||
|
tracingOrigins?: (string | RegExp)[];
|
||||||
|
tracePropagationTargets?: (string | RegExp)[];
|
||||||
maxQueueSize?: number;
|
maxQueueSize?: number;
|
||||||
flushInterval?: number;
|
flushInterval?: number;
|
||||||
maxRetries?: number;
|
maxRetries?: number;
|
||||||
|
|
@ -144,6 +257,7 @@ export interface LightConfig {
|
||||||
maxBreadcrumbs?: number;
|
maxBreadcrumbs?: number;
|
||||||
beforeSend?: (event: SentryEvent) => SentryEvent | null;
|
beforeSend?: (event: SentryEvent) => SentryEvent | null;
|
||||||
user?: UserInfo;
|
user?: UserInfo;
|
||||||
|
trackAnonymousUsers?: boolean;
|
||||||
plugins?: (LightPlugin | string)[];
|
plugins?: (LightPlugin | string)[];
|
||||||
contextLevel?: Partial<ContextLevelMap>;
|
contextLevel?: Partial<ContextLevelMap>;
|
||||||
sampling?: SamplingConfig;
|
sampling?: SamplingConfig;
|
||||||
|
|
@ -169,10 +283,16 @@ export interface LightClient {
|
||||||
type: string;
|
type: string;
|
||||||
}): void;
|
}): void;
|
||||||
setUser(user: UserInfo | null): void;
|
setUser(user: UserInfo | null): void;
|
||||||
|
setAnonymousId(id: string): void;
|
||||||
setTag(key: string, value: string): void;
|
setTag(key: string, value: string): void;
|
||||||
setTags(tags: Record<string, string>): void;
|
setTags(tags: Record<string, string>): void;
|
||||||
setExtra(key: string, value: unknown): void;
|
setExtra(key: string, value: unknown): void;
|
||||||
addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null;
|
||||||
|
getCurrentTransaction(): Transaction | null;
|
||||||
|
getCurrentSpan(): Span | null;
|
||||||
|
setSpan(span: Span | null): void;
|
||||||
|
startSpan(context: SpanContext): Span | null;
|
||||||
flush(): Promise<void>;
|
flush(): Promise<void>;
|
||||||
disable(): void;
|
disable(): void;
|
||||||
enable(): void;
|
enable(): void;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { BaggageFields } from '../types';
|
||||||
|
export declare function serializeBaggage(fields: BaggageFields): string;
|
||||||
|
export declare function parseBaggage(header: string): BaggageFields;
|
||||||
|
export declare function createBaggageFromTransaction(traceId: string, options?: {
|
||||||
|
dsnPublicKey?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
sampleRate?: number;
|
||||||
|
user?: {
|
||||||
|
id?: string;
|
||||||
|
segment?: string;
|
||||||
|
};
|
||||||
|
release?: string;
|
||||||
|
environment?: string;
|
||||||
|
}): BaggageFields;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* 获取匿名用户ID
|
||||||
|
* 优先从 localStorage 获取已存在的ID,不存在则生成并存储
|
||||||
|
*/
|
||||||
|
export declare function getAnonymousId(): string | null;
|
||||||
|
/**
|
||||||
|
* 设置匿名用户ID
|
||||||
|
* 用于覆盖自动生成的ID
|
||||||
|
*/
|
||||||
|
export declare function setAnonymousId(id: string): boolean;
|
||||||
|
/**
|
||||||
|
* 清除匿名用户ID
|
||||||
|
* 通常在用户登出后调用
|
||||||
|
*/
|
||||||
|
export declare function clearAnonymousId(): boolean;
|
||||||
|
/**
|
||||||
|
* 检查是否启用了匿名用户追踪
|
||||||
|
*/
|
||||||
|
export declare function isAnonymousTrackingEnabled(config: {
|
||||||
|
trackAnonymousUsers?: boolean;
|
||||||
|
}): boolean;
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
import type { Span as SpanInterface, SpanContext, Transaction as TransactionInterface, TransactionContext, SpanStatus, LightClient, BaggageFields } from '../types';
|
||||||
|
declare function generateTraceId(): string;
|
||||||
|
declare function generateSpanId(): string;
|
||||||
|
export declare function httpStatusToSpanStatus(statusCode: number): SpanStatus;
|
||||||
|
export declare function dbStatusToSpanStatus(dbStatusCode?: string | number): SpanStatus;
|
||||||
|
export declare function statusToSpanStatus(status: string | number | undefined, op: string | undefined): SpanStatus;
|
||||||
|
export declare class Span implements SpanInterface {
|
||||||
|
readonly traceId: string;
|
||||||
|
readonly spanId: string;
|
||||||
|
readonly parentSpanId?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags: Record<string, string>;
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
readonly startTimestamp: number;
|
||||||
|
endTimestamp?: number;
|
||||||
|
transaction?: TransactionInterface;
|
||||||
|
protected _isFinished: boolean;
|
||||||
|
protected _client?: LightClient;
|
||||||
|
constructor(context: SpanContext, transaction?: TransactionInterface, client?: LightClient);
|
||||||
|
setTag(key: string, value: string): this;
|
||||||
|
setData(key: string, value: unknown): this;
|
||||||
|
setStatus(status: SpanStatus): this;
|
||||||
|
setHttpStatus(statusCode: number): this;
|
||||||
|
setStatusFromResponse(status: string | number | undefined, tagKey?: string): this;
|
||||||
|
startChild(context?: SpanContext): Span;
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
toTraceparent(): string;
|
||||||
|
toJSON(): {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id: string;
|
||||||
|
op: string;
|
||||||
|
description: string;
|
||||||
|
status: SpanStatus;
|
||||||
|
tags: {
|
||||||
|
[x: string]: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
[x: string]: unknown;
|
||||||
|
};
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export declare class Transaction extends Span implements TransactionInterface {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
spans: Span[];
|
||||||
|
sampled?: boolean;
|
||||||
|
maxSpans: number;
|
||||||
|
private _baggage?;
|
||||||
|
private _dsnPublicKey?;
|
||||||
|
private _release?;
|
||||||
|
private _environment?;
|
||||||
|
private _droppedSpans;
|
||||||
|
constructor(context: TransactionContext, client?: LightClient, dsnPublicKey?: string, release?: string, environment?: string, maxSpans?: number);
|
||||||
|
_addSpan(span: Span): void;
|
||||||
|
private _ensureBaggage;
|
||||||
|
setBaggage(key: string, value: string): this;
|
||||||
|
setUserBaggage(userId?: string, segment?: string): this;
|
||||||
|
toBaggage(): string | undefined;
|
||||||
|
getBaggage(): BaggageFields | undefined;
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
private _getSource;
|
||||||
|
toEvent(): {
|
||||||
|
type: "transaction";
|
||||||
|
transaction: string;
|
||||||
|
transaction_info: {
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
level: "info";
|
||||||
|
timestamp: number;
|
||||||
|
start_timestamp: number;
|
||||||
|
spans: {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id: string;
|
||||||
|
op: string;
|
||||||
|
description: string;
|
||||||
|
status: SpanStatus;
|
||||||
|
tags: {
|
||||||
|
[x: string]: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
[x: string]: unknown;
|
||||||
|
};
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
}[];
|
||||||
|
contexts: {
|
||||||
|
trace: {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id: string;
|
||||||
|
op: string;
|
||||||
|
status: SpanStatus;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
[x: string]: string;
|
||||||
|
};
|
||||||
|
extra: Record<string, unknown>;
|
||||||
|
measurements: Record<string, {
|
||||||
|
value: number;
|
||||||
|
unit?: string;
|
||||||
|
rating?: "good" | "needs-improvement" | "poor";
|
||||||
|
}>;
|
||||||
|
platform: string;
|
||||||
|
};
|
||||||
|
toJSON(): {
|
||||||
|
transaction: string;
|
||||||
|
type: string;
|
||||||
|
spans: {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id: string;
|
||||||
|
op: string;
|
||||||
|
description: string;
|
||||||
|
status: SpanStatus;
|
||||||
|
tags: {
|
||||||
|
[x: string]: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
[x: string]: unknown;
|
||||||
|
};
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
}[];
|
||||||
|
sampled: boolean;
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id: string;
|
||||||
|
op: string;
|
||||||
|
description: string;
|
||||||
|
status: SpanStatus;
|
||||||
|
tags: {
|
||||||
|
[x: string]: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
[x: string]: unknown;
|
||||||
|
};
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export { generateTraceId, generateSpanId };
|
||||||
|
|
@ -0,0 +1,348 @@
|
||||||
|
# 分布式追踪(Distributed Tracing)设计文档
|
||||||
|
|
||||||
|
## 一、背景与目标
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
当前 SDK 已支持错误采集、性能指标(Web Vitals)、网络请求监控、用户行为采集等能力,但缺乏**前后端链路打通**的分布式追踪能力。当用户遇到"接口慢"或"报错"时,无法关联前端操作与后端服务的内部调用链路,排查问题效率低。
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
- **第一阶段(P0)**:实现 Trace/Span 核心模型 + 前后端 Trace 传播 + 手动 API
|
||||||
|
- **第二阶段(P1)**:自动 Instrumentation(Page Load、路由变化、用户交互)
|
||||||
|
- **第三阶段(P2)**:采样策略升级 + Baggage 支持 + 完整开发者体验
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、核心概念
|
||||||
|
|
||||||
|
### 2.1 Trace
|
||||||
|
一条完整的分布式调用链路,全局唯一 `trace_id` 标识。一条 Trace 跨越多个服务、多个进程。
|
||||||
|
|
||||||
|
### 2.2 Transaction
|
||||||
|
Trace 中的"根 Span",代表一个完整的用户操作或业务流程。前端常见的 Transaction 类型:
|
||||||
|
- `pageload`:页面加载
|
||||||
|
- `navigation`:路由切换(SPA)
|
||||||
|
- `user-interaction`:用户交互(点击、提交等)
|
||||||
|
- `custom`:自定义事务
|
||||||
|
|
||||||
|
### 2.3 Span
|
||||||
|
Transaction 中的具体操作步骤,有父子关系。常见 Span 类型:
|
||||||
|
- `http`:HTTP 请求
|
||||||
|
- `resource`:资源加载
|
||||||
|
- `browser`:浏览器内部操作(DOM 解析、脚本执行等)
|
||||||
|
- `db`:数据库查询(后端)
|
||||||
|
- `middleware`:中间件(后端)
|
||||||
|
|
||||||
|
### 2.4 Span 关系
|
||||||
|
```
|
||||||
|
Trace (trace_id = abc123)
|
||||||
|
└── Transaction: "page-load" (span_id = s1)
|
||||||
|
├── Span: "http GET /api/user" (span_id = s2, parent = s1)
|
||||||
|
│ └── 后端服务的多个 Span(通过 trace header 关联)
|
||||||
|
├── Span: "resource /static/app.js" (span_id = s3, parent = s1)
|
||||||
|
└── Span: "dom-parse" (span_id = s4, parent = s1)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、数据模型设计
|
||||||
|
|
||||||
|
### 3.1 Span 接口
|
||||||
|
```typescript
|
||||||
|
interface Span {
|
||||||
|
trace_id: string; // 16/32 位十六进制,全局唯一
|
||||||
|
span_id: string; // 16 位十六进制,Span 唯一
|
||||||
|
parent_span_id?: string; // 父 Span ID
|
||||||
|
op?: string; // 操作类型:http、resource、browser 等
|
||||||
|
description?: string; // 描述信息
|
||||||
|
start_timestamp: number; // 开始时间(毫秒时间戳)
|
||||||
|
timestamp: number; // 结束时间(毫秒时间戳)
|
||||||
|
status?: SpanStatus; // 状态
|
||||||
|
tags?: Record<string, string>; // 标签
|
||||||
|
data?: Record<string, unknown>; // 附加数据
|
||||||
|
same_process_as_parent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpanStatus =
|
||||||
|
| 'ok'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'unknown'
|
||||||
|
| 'invalid_argument'
|
||||||
|
| 'deadline_exceeded'
|
||||||
|
| 'not_found'
|
||||||
|
| 'already_exists'
|
||||||
|
| 'permission_denied'
|
||||||
|
| 'resource_exhausted'
|
||||||
|
| 'failed_precondition'
|
||||||
|
| 'aborted'
|
||||||
|
| 'out_of_range'
|
||||||
|
| 'unimplemented'
|
||||||
|
| 'internal_error'
|
||||||
|
| 'unavailable'
|
||||||
|
| 'data_loss'
|
||||||
|
| 'unauthenticated';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Transaction 接口
|
||||||
|
```typescript
|
||||||
|
interface Transaction extends Span {
|
||||||
|
name: string; // 事务名称(如 "GET /api/users")
|
||||||
|
spans: Span[]; // 子 Span 列表
|
||||||
|
transaction: string; // 事务名称冗余字段
|
||||||
|
type?: string; // 事务类型:page-load、navigation 等
|
||||||
|
sampled?: boolean; // 是否采样
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 上报事件类型
|
||||||
|
新增 `transaction` 事件类型:
|
||||||
|
```typescript
|
||||||
|
interface TransactionEvent extends BaseEvent {
|
||||||
|
type: 'transaction';
|
||||||
|
transaction: string;
|
||||||
|
spans: Span[];
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、前后端 Trace 传播
|
||||||
|
|
||||||
|
### 4.1 协议选择
|
||||||
|
采用 **Sentry Trace 协议**(`sentry-trace` header),同时支持 W3C Trace Context 作为兼容。
|
||||||
|
|
||||||
|
### 4.2 sentry-trace Header 格式
|
||||||
|
```
|
||||||
|
sentry-trace: {trace_id}-{span_id}-{sampled}
|
||||||
|
```
|
||||||
|
- `trace_id`: 32 位十六进制字符串
|
||||||
|
- `span_id`: 16 位十六进制字符串
|
||||||
|
- `sampled`: 可选,`1` = 采样,`0` = 不采样
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```
|
||||||
|
sentry-trace: 8839b8a3914c485bb1f52ce3c08c9c0b-b89e261f348c022b-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 baggage Header(后续阶段)
|
||||||
|
```
|
||||||
|
baggage: sentry-trace_id=xxx,sentry-public_key=xxx,sentry-sample_rate=1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 追踪域名白名单
|
||||||
|
为避免向第三方接口泄漏 trace 信息,需要配置 `tracingOrigins`:
|
||||||
|
```typescript
|
||||||
|
LightSentry.init({
|
||||||
|
dsn: '...',
|
||||||
|
tracingOrigins: [
|
||||||
|
'api.example.com',
|
||||||
|
/^https:\/\/internal\./,
|
||||||
|
'localhost',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
默认匹配同源域名。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、核心模块设计
|
||||||
|
|
||||||
|
### 5.1 TracingManager
|
||||||
|
职责:
|
||||||
|
- 维护当前活跃的 Transaction
|
||||||
|
- 维护 Span 栈(当前执行上下文)
|
||||||
|
- 生成 trace_id / span_id
|
||||||
|
- 管理 Span 的父子关系
|
||||||
|
|
||||||
|
关键方法:
|
||||||
|
```typescript
|
||||||
|
class TracingManager {
|
||||||
|
startTransaction(context: TransactionContext): Transaction;
|
||||||
|
getCurrentTransaction(): Transaction | null;
|
||||||
|
setCurrentTransaction(transaction: Transaction | null): void;
|
||||||
|
startSpan(context: SpanContext): Span | null;
|
||||||
|
getCurrentSpan(): Span | null;
|
||||||
|
finishSpan(span: Span): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Span 类
|
||||||
|
```typescript
|
||||||
|
class Span {
|
||||||
|
traceId: string;
|
||||||
|
spanId: string;
|
||||||
|
parentSpanId?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
startTimestamp: number;
|
||||||
|
endTimestamp?: number;
|
||||||
|
status?: string;
|
||||||
|
tags: Record<string, string>;
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
|
||||||
|
setTag(key: string, value: string): this;
|
||||||
|
setData(key: string, value: unknown): this;
|
||||||
|
setStatus(status: string): this;
|
||||||
|
startChild(context: SpanContext): Span;
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
toJSON(): Span;
|
||||||
|
toTraceparent(): string; // 生成 sentry-trace header 值
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Transaction 类
|
||||||
|
```typescript
|
||||||
|
class Transaction extends Span {
|
||||||
|
name: string;
|
||||||
|
spans: Span[];
|
||||||
|
type?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
toJSON(): Transaction;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、插件改造
|
||||||
|
|
||||||
|
### 6.1 NetworkPlugin 改造
|
||||||
|
|
||||||
|
**改造前**:上报独立的 `network` 事件
|
||||||
|
**改造后**:创建 `http` Span,关联到当前 Transaction,并注入 trace header
|
||||||
|
|
||||||
|
具体改动:
|
||||||
|
1. 请求开始时,如果存在活跃 Transaction,创建 Span
|
||||||
|
2. 向匹配的域名注入 `sentry-trace` header
|
||||||
|
3. 请求结束时,设置 Span status 和 data,finish Span
|
||||||
|
4. 保持原有 `network` 事件上报(兼容)
|
||||||
|
|
||||||
|
### 6.2 PerformancePlugin 改造(第二阶段)
|
||||||
|
|
||||||
|
改造为 Page Load Transaction:
|
||||||
|
- Navigation Timing → 拆解为多个 Span
|
||||||
|
- Resource Timing → 作为 resource Span
|
||||||
|
- Web Vitals → 作为标记点或 measurement
|
||||||
|
|
||||||
|
### 6.3 新增 RouterPlugin(第二阶段)
|
||||||
|
|
||||||
|
监听路由变化:
|
||||||
|
- History API(pushState / replaceState / popstate)
|
||||||
|
- hashchange
|
||||||
|
- 每次路由变化创建新的 navigation Transaction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、对外 API
|
||||||
|
|
||||||
|
### 7.1 顶层 API
|
||||||
|
```typescript
|
||||||
|
// 开始事务
|
||||||
|
LightSentry.startTransaction({
|
||||||
|
name: 'search-products',
|
||||||
|
op: 'user-interaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取当前 Span
|
||||||
|
const span = LightSentry.getCurrentSpan();
|
||||||
|
|
||||||
|
// 创建子 Span(基于当前活跃 Span)
|
||||||
|
const childSpan = LightSentry.startSpan({
|
||||||
|
op: 'http',
|
||||||
|
description: 'GET /api/search',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手动设置 Span
|
||||||
|
LightSentry.setSpan(span);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 配置项
|
||||||
|
```typescript
|
||||||
|
interface LightConfig {
|
||||||
|
// ...
|
||||||
|
tracesSampleRate?: number; // 全局 Trace 采样率 0~1,默认 0
|
||||||
|
tracesSampler?: (context: any) => number; // 动态采样函数
|
||||||
|
tracingOrigins?: (string | RegExp)[]; // 追踪域名白名单
|
||||||
|
tracePropagationTargets?: (string | RegExp)[]; // trace header 传播目标
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、采样策略(第一阶段简化版)
|
||||||
|
|
||||||
|
第一阶段采用简单策略:
|
||||||
|
1. 配置 `tracesSampleRate`(默认 0 = 关闭)
|
||||||
|
2. Transaction 创建时决定是否采样
|
||||||
|
3. 采样决策通过 `sentry-trace` 的 `sampled` 标志传递给后端
|
||||||
|
4. 未采样的 Transaction 不上报,但仍传递 trace_id(用于关联)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、实施计划
|
||||||
|
|
||||||
|
### 第一阶段:核心能力(已完成)
|
||||||
|
- [x] 设计文档
|
||||||
|
- [x] 新增 Trace/Span 类型定义
|
||||||
|
- [x] 实现 Span 类
|
||||||
|
- [x] 实现 Transaction 类
|
||||||
|
- [x] 实现 TracingManager
|
||||||
|
- [x] 改造 NetworkPlugin(注入 trace header + 创建 HTTP Span)
|
||||||
|
- [x] 对外暴露 startTransaction / startSpan API
|
||||||
|
- [x] 集成到 Client
|
||||||
|
- [x] 编译验证
|
||||||
|
- [x] 错误事件自动关联 Trace 上下文
|
||||||
|
- [x] Transaction 完成后自动上报
|
||||||
|
- [x] Span 栈生命周期管理
|
||||||
|
- [x] 未采样 Transaction 不收集 Span(性能优化)
|
||||||
|
|
||||||
|
### 第二阶段:自动 Instrumentation(已完成)
|
||||||
|
- [x] PerformancePlugin 改造:Page Load Transaction
|
||||||
|
- [x] Navigation Timing 拆解为 browser.* Span 树
|
||||||
|
- [x] Resource Timing 转为 resource.* Span
|
||||||
|
- [x] Web Vitals 作为 measurements 附加到 Transaction
|
||||||
|
- [x] LCP/FP/FCP 等关键指标标记
|
||||||
|
- [x] 新增 RouterPlugin(路由变化追踪)
|
||||||
|
- [x] History API 监听(pushState / replaceState / popstate)
|
||||||
|
- [x] Hash 路由监听(hashchange)
|
||||||
|
- [x] 路由切换期间的 Span 自动关联
|
||||||
|
- [x] 新增 InteractionPlugin(用户交互追踪)
|
||||||
|
- [x] 点击/表单提交等交互自动创建 Transaction
|
||||||
|
- [x] 交互期间的网络请求自动关联
|
||||||
|
- [x] Error 与 Trace 深度集成
|
||||||
|
- [x] 错误事件自动附加 contexts.trace
|
||||||
|
- [ ] 错误作为 error Span 加入当前 Transaction(可选)
|
||||||
|
|
||||||
|
### 第三阶段:进阶能力(已完成)
|
||||||
|
- [x] Baggage header 支持
|
||||||
|
- [x] Transaction 携带 user、release 等信息
|
||||||
|
- [x] NetworkPlugin 自动注入 baggage header
|
||||||
|
- [x] 后端可以读取 baggage 传递的信息
|
||||||
|
- [x] 动态采样 tracesSampler 实现
|
||||||
|
- [x] tracesSampler 函数支持
|
||||||
|
- [x] 根据 transaction 上下文动态决定采样率
|
||||||
|
- [x] 完整 Span Status 映射增强
|
||||||
|
- [x] 数据库操作状态映射(dbStatusToSpanStatus)
|
||||||
|
- [x] 自定义操作状态映射(statusToSpanStatus)
|
||||||
|
- [x] Span status 与 error 关联(setStatusFromResponse)
|
||||||
|
- [x] 错误作为 error Span 加入 Transaction(可选功能)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、兼容性考虑
|
||||||
|
|
||||||
|
1. **向后兼容**:默认 `tracesSampleRate = 0`,即默认关闭,不影响现有功能
|
||||||
|
2. **事件类型新增**:新增 `transaction` 事件类型,后端需支持
|
||||||
|
3. **NetworkPlugin**:保持原有 `network` 事件上报,同时新增 Span(可配置开关)
|
||||||
|
4. **浏览器兼容性**:不依赖新 API,使用现有能力实现
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、参考资料
|
||||||
|
|
||||||
|
- [Sentry Tracing 文档](https://docs.sentry.io/product/sentry-basics/concepts/tracing/)
|
||||||
|
- [Sentry Performance 数据模型](https://develop.sentry.dev/sdk/performance/)
|
||||||
|
- [W3C Trace Context](https://www.w3.org/TR/trace-context/)
|
||||||
|
|
@ -9,12 +9,27 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vitest/coverage-v8": "^1.6.0",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.20.0",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"typescript": "^5.4.0",
|
"typescript": "^5.4.0",
|
||||||
"vitest": "^1.4.0"
|
"vitest": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ampproject/remapping": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@asamuzakjp/css-color": {
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
||||||
|
|
@ -29,6 +44,63 @@
|
||||||
"lru-cache": "^10.4.3"
|
"lru-cache": "^10.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-string-parser": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/parser": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.29.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"parser": "bin/babel-parser.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/types": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-string-parser": "^7.29.7",
|
||||||
|
"@babel/helper-validator-identifier": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@bcoe/v8-coverage": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@csstools/color-helpers": {
|
"node_modules/@csstools/color-helpers": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||||
|
|
@ -535,6 +607,16 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@istanbuljs/schema": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jest/schemas": {
|
"node_modules/@jest/schemas": {
|
||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz",
|
"resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz",
|
||||||
|
|
@ -548,6 +630,27 @@
|
||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.13",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
|
|
@ -555,6 +658,17 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.31",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.62.2",
|
"version": "4.62.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
||||||
|
|
@ -919,15 +1033,43 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/coverage-v8": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz",
|
||||||
"integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==",
|
"integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "1.6.1",
|
"@ampproject/remapping": "^2.2.1",
|
||||||
"@vitest/utils": "1.6.1",
|
"@bcoe/v8-coverage": "^0.2.3",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
|
"istanbul-lib-report": "^3.0.1",
|
||||||
|
"istanbul-lib-source-maps": "^5.0.4",
|
||||||
|
"istanbul-reports": "^3.1.6",
|
||||||
|
"magic-string": "^0.30.5",
|
||||||
|
"magicast": "^0.3.3",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"std-env": "^3.5.0",
|
||||||
|
"strip-literal": "^2.0.0",
|
||||||
|
"test-exclude": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vitest": "1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/expect": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "1.6.0",
|
||||||
|
"@vitest/utils": "1.6.0",
|
||||||
"chai": "^4.3.10"
|
"chai": "^4.3.10"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|
@ -935,13 +1077,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-1.6.0.tgz",
|
||||||
"integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==",
|
"integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "1.6.1",
|
"@vitest/utils": "1.6.0",
|
||||||
"p-limit": "^5.0.0",
|
"p-limit": "^5.0.0",
|
||||||
"pathe": "^1.1.1"
|
"pathe": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
|
@ -950,9 +1092,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-1.6.0.tgz",
|
||||||
"integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==",
|
"integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -965,9 +1107,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-1.6.0.tgz",
|
||||||
"integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
|
"integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -978,9 +1120,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-1.6.0.tgz",
|
||||||
"integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==",
|
"integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1059,6 +1201,24 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.15",
|
||||||
|
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||||
|
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "6.7.14",
|
||||||
"resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz",
|
||||||
|
|
@ -1128,6 +1288,13 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/confbox": {
|
"node_modules/confbox": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
|
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
|
||||||
|
|
@ -1410,6 +1577,13 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|
@ -1497,6 +1671,28 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
|
@ -1510,6 +1706,16 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
|
@ -1565,6 +1771,13 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-escaper": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
|
|
@ -1616,6 +1829,25 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/is-potential-custom-element-name": {
|
"node_modules/is-potential-custom-element-name": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||||
|
|
@ -1643,6 +1875,60 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/istanbul-lib-coverage": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/istanbul-lib-report": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"istanbul-lib-coverage": "^3.0.0",
|
||||||
|
"make-dir": "^4.0.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/istanbul-lib-source-maps": {
|
||||||
|
"version": "5.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
|
||||||
|
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.23",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"istanbul-lib-coverage": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/istanbul-reports": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"html-escaper": "^2.0.0",
|
||||||
|
"istanbul-lib-report": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||||
|
|
@ -1735,6 +2021,34 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magicast": {
|
||||||
|
"version": "0.3.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/magicast/-/magicast-0.3.5.tgz",
|
||||||
|
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.25.4",
|
||||||
|
"@babel/types": "^7.25.4",
|
||||||
|
"source-map-js": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/make-dir": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|
@ -1788,6 +2102,19 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimatch": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mlly": {
|
"node_modules/mlly": {
|
||||||
"version": "1.8.2",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz",
|
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz",
|
||||||
|
|
@ -1870,6 +2197,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/onetime": {
|
"node_modules/onetime": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz",
|
||||||
|
|
@ -1915,6 +2252,16 @@
|
||||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
|
@ -2128,6 +2475,19 @@
|
||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|
@ -2221,6 +2581,19 @@
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/symbol-tree": {
|
"node_modules/symbol-tree": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
|
|
@ -2228,6 +2601,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/test-exclude": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@istanbuljs/schema": "^0.1.2",
|
||||||
|
"glob": "^7.1.4",
|
||||||
|
"minimatch": "^3.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinybench": {
|
"node_modules/tinybench": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz",
|
"resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
|
|
@ -2397,9 +2785,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-node": {
|
"node_modules/vite-node": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-1.6.0.tgz",
|
||||||
"integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==",
|
"integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2850,17 +3238,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/vitest/-/vitest-1.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/vitest/-/vitest-1.6.0.tgz",
|
||||||
"integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
|
"integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "1.6.1",
|
"@vitest/expect": "1.6.0",
|
||||||
"@vitest/runner": "1.6.1",
|
"@vitest/runner": "1.6.0",
|
||||||
"@vitest/snapshot": "1.6.1",
|
"@vitest/snapshot": "1.6.0",
|
||||||
"@vitest/spy": "1.6.1",
|
"@vitest/spy": "1.6.0",
|
||||||
"@vitest/utils": "1.6.1",
|
"@vitest/utils": "1.6.0",
|
||||||
"acorn-walk": "^8.3.2",
|
"acorn-walk": "^8.3.2",
|
||||||
"chai": "^4.3.10",
|
"chai": "^4.3.10",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
|
|
@ -2874,7 +3262,7 @@
|
||||||
"tinybench": "^2.5.1",
|
"tinybench": "^2.5.1",
|
||||||
"tinypool": "^0.8.3",
|
"tinypool": "^0.8.3",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-node": "1.6.1",
|
"vite-node": "1.6.0",
|
||||||
"why-is-node-running": "^2.2.2"
|
"why-is-node-running": "^2.2.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -2889,8 +3277,8 @@
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@types/node": "^18.0.0 || >=20.0.0",
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
"@vitest/browser": "1.6.1",
|
"@vitest/browser": "1.6.0",
|
||||||
"@vitest/ui": "1.6.1",
|
"@vitest/ui": "1.6.0",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*"
|
"jsdom": "*"
|
||||||
},
|
},
|
||||||
|
|
@ -3009,6 +3397,13 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.21.0",
|
"version": "8.21.0",
|
||||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz",
|
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vitest/coverage-v8": "^1.6.0",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.20.0",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"typescript": "^5.4.0",
|
"typescript": "^5.4.0",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import type { LightConfig, LightClient, LightPlugin, DSNInfo, SentryEvent, EventLevel, UserInfo, Breadcrumb, ErrorEvent } from '../types';
|
import type { LightConfig, LightClient, LightPlugin, DSNInfo, SentryEvent, EventLevel, UserInfo, Breadcrumb, ErrorEvent, TransactionContext, SpanContext, Transaction, Span } from '../types';
|
||||||
import { parseDSN } from '../utils/dsn';
|
import { parseDSN } from '../utils/dsn';
|
||||||
import { now } from '../utils/helper';
|
import { now } from '../utils/helper';
|
||||||
import { computeFingerprint } from '../utils/hash';
|
import { computeFingerprint } from '../utils/hash';
|
||||||
import { parseStackTrace } from '../utils/stacktrace';
|
import { parseStackTrace } from '../utils/stacktrace';
|
||||||
import { logDebug, setDebug } from '../utils/logger';
|
import { logDebug, setDebug } from '../utils/logger';
|
||||||
|
import { setAnonymousId as setStoredAnonymousId } from '../utils/identity';
|
||||||
import { ConfigManager } from './ConfigManager';
|
import { ConfigManager } from './ConfigManager';
|
||||||
import { EventBus } from './EventBus';
|
import { EventBus } from './EventBus';
|
||||||
import { EventQueue } from './EventQueue';
|
import { EventQueue } from './EventQueue';
|
||||||
import { Reporter } from './Reporter';
|
import { Reporter } from './Reporter';
|
||||||
import { PluginManager } from './PluginManager';
|
import { PluginManager } from './PluginManager';
|
||||||
|
import { TracingManager } from './TracingManager';
|
||||||
|
|
||||||
class Client implements LightClient {
|
class Client implements LightClient {
|
||||||
dsn: DSNInfo;
|
dsn: DSNInfo;
|
||||||
|
|
@ -18,6 +20,7 @@ class Client implements LightClient {
|
||||||
private queue: EventQueue;
|
private queue: EventQueue;
|
||||||
private reporter: Reporter;
|
private reporter: Reporter;
|
||||||
private pluginManager: PluginManager;
|
private pluginManager: PluginManager;
|
||||||
|
private tracingManager: TracingManager;
|
||||||
private breadcrumbs: Breadcrumb[] = [];
|
private breadcrumbs: Breadcrumb[] = [];
|
||||||
private enabled: boolean = true;
|
private enabled: boolean = true;
|
||||||
|
|
||||||
|
|
@ -46,6 +49,7 @@ class Client implements LightClient {
|
||||||
this.configManager.get('retryDelay') ?? 1000
|
this.configManager.get('retryDelay') ?? 1000
|
||||||
);
|
);
|
||||||
this.pluginManager = new PluginManager(this);
|
this.pluginManager = new PluginManager(this);
|
||||||
|
this.tracingManager = new TracingManager(config, this, this.dsn.publicKey);
|
||||||
this.queue = new EventQueue(
|
this.queue = new EventQueue(
|
||||||
this.configManager.get('maxQueueSize') ?? 100,
|
this.configManager.get('maxQueueSize') ?? 100,
|
||||||
this.configManager.get('flushInterval') ?? 5000,
|
this.configManager.get('flushInterval') ?? 5000,
|
||||||
|
|
@ -175,6 +179,7 @@ class Client implements LightClient {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.attachTraceContext(errorEvent);
|
||||||
errorEvent.breadcrumbs = [...this.breadcrumbs];
|
errorEvent.breadcrumbs = [...this.breadcrumbs];
|
||||||
logDebug('Client', 'Enqueue error event', { breadcrumbs: this.breadcrumbs.length });
|
logDebug('Client', 'Enqueue error event', { breadcrumbs: this.breadcrumbs.length });
|
||||||
this.queue.enqueue(errorEvent);
|
this.queue.enqueue(errorEvent);
|
||||||
|
|
@ -193,6 +198,7 @@ class Client implements LightClient {
|
||||||
|
|
||||||
if (this.configManager.isIgnoredError(message)) return;
|
if (this.configManager.isIgnoredError(message)) return;
|
||||||
|
|
||||||
|
this.attachTraceContext(event);
|
||||||
this.queue.enqueue(event);
|
this.queue.enqueue(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,9 +212,34 @@ class Client implements LightClient {
|
||||||
...event,
|
...event,
|
||||||
} as SentryEvent;
|
} as SentryEvent;
|
||||||
|
|
||||||
|
this.attachTraceContext(fullEvent);
|
||||||
this.queue.enqueue(fullEvent);
|
this.queue.enqueue(fullEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private attachTraceContext(event: SentryEvent): void {
|
||||||
|
const currentSpan = this.tracingManager.getCurrentSpan();
|
||||||
|
const currentTransaction = this.tracingManager.getCurrentTransaction();
|
||||||
|
|
||||||
|
if (currentSpan || currentTransaction) {
|
||||||
|
const traceId = currentSpan?.traceId || currentTransaction?.traceId;
|
||||||
|
const spanId = currentSpan?.spanId || currentTransaction?.spanId;
|
||||||
|
const parentSpanId = currentSpan?.parentSpanId;
|
||||||
|
const op = currentSpan?.op || currentTransaction?.op;
|
||||||
|
const status = currentSpan?.status || currentTransaction?.status;
|
||||||
|
|
||||||
|
event.contexts = {
|
||||||
|
...event.contexts,
|
||||||
|
trace: {
|
||||||
|
trace_id: traceId || '',
|
||||||
|
span_id: spanId || '',
|
||||||
|
parent_span_id: parentSpanId,
|
||||||
|
op,
|
||||||
|
status,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private buildErrorEvent(error: Error | unknown): ErrorEvent {
|
private buildErrorEvent(error: Error | unknown): ErrorEvent {
|
||||||
const timestamp = now();
|
const timestamp = now();
|
||||||
|
|
||||||
|
|
@ -243,6 +274,42 @@ class Client implements LightClient {
|
||||||
|
|
||||||
setUser(user: UserInfo | null): void {
|
setUser(user: UserInfo | null): void {
|
||||||
this.configManager.setUser(user);
|
this.configManager.setUser(user);
|
||||||
|
this.tracingManager.updateConfig(this.configManager.getAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnonymousId(id: string): void {
|
||||||
|
setStoredAnonymousId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null {
|
||||||
|
if (!this.enabled) return null;
|
||||||
|
|
||||||
|
const transaction = this.tracingManager.startTransaction(context);
|
||||||
|
if (transaction && transaction.sampled) {
|
||||||
|
logDebug('Client', 'Transaction started and sampled', {
|
||||||
|
name: transaction.name,
|
||||||
|
traceId: transaction.traceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTransaction(): Transaction | null {
|
||||||
|
return this.tracingManager.getCurrentTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSpan(): Span | null {
|
||||||
|
return this.tracingManager.getCurrentSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpan(span: Span | null): void {
|
||||||
|
this.tracingManager.setCurrentSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
startSpan(context: SpanContext): Span | null {
|
||||||
|
if (!this.enabled) return null;
|
||||||
|
return this.tracingManager.startSpan(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTag(key: string, value: string): void {
|
setTag(key: string, value: string): void {
|
||||||
|
|
@ -286,8 +353,9 @@ class Client implements LightClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.queue.destroy();
|
|
||||||
this.pluginManager.destroy();
|
this.pluginManager.destroy();
|
||||||
|
this.tracingManager.destroy();
|
||||||
|
this.queue.destroy();
|
||||||
this.eventBus.destroy();
|
this.eventBus.destroy();
|
||||||
this.breadcrumbs = [];
|
this.breadcrumbs = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { LightConfig, SentryEvent, UserInfo, ContextLevelMap, ErrorEvent, StackFrame, Breadcrumb, EventLevel } from '../types';
|
import type { LightConfig, SentryEvent, UserInfo, ContextLevelMap, ErrorEvent, StackFrame, Breadcrumb, EventLevel } from '../types';
|
||||||
import { getContexts, getContextId, getPageUrl, getReferrer } from '../utils/env';
|
import { getContexts, getContextId, getPageUrl, getReferrer } from '../utils/env';
|
||||||
import { matchPatterns } from '../utils/helper';
|
import { matchPatterns } from '../utils/helper';
|
||||||
|
import { getAnonymousId, isAnonymousTrackingEnabled } from '../utils/identity';
|
||||||
|
|
||||||
const DEFAULT_CONFIG: Partial<LightConfig> = {
|
const DEFAULT_CONFIG: Partial<LightConfig> = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -72,6 +73,13 @@ export class ConfigManager {
|
||||||
return matchPatterns(message, this.config.ignoreErrors || []);
|
return matchPatterns(message, this.config.ignoreErrors || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldSample(): boolean {
|
||||||
|
const rate = this.config.sampleRate ?? 1;
|
||||||
|
if (rate >= 1) return true;
|
||||||
|
if (rate <= 0) return false;
|
||||||
|
return Math.random() < rate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取编码后的环境信息(延迟初始化,只在首次上报时计算)
|
* 获取编码后的环境信息(延迟初始化,只在首次上报时计算)
|
||||||
* 注意:env.ts 的 getContexts 已经返回编码后的数据
|
* 注意:env.ts 的 getContexts 已经返回编码后的数据
|
||||||
|
|
@ -135,6 +143,7 @@ export class ConfigManager {
|
||||||
if (this.config.environment) {
|
if (this.config.environment) {
|
||||||
enriched.environment = this.config.environment;
|
enriched.environment = this.config.environment;
|
||||||
}
|
}
|
||||||
|
enriched.platform = 'javascript';
|
||||||
if (this.config.user) {
|
if (this.config.user) {
|
||||||
enriched.user = this.config.user;
|
enriched.user = this.config.user;
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +165,16 @@ export class ConfigManager {
|
||||||
}
|
}
|
||||||
enriched.context_id = this.contextId;
|
enriched.context_id = this.contextId;
|
||||||
|
|
||||||
|
// 处理匿名用户ID
|
||||||
|
// 只有在没有设置 user.id 且启用了匿名追踪时,才添加 anonymousId
|
||||||
|
const hasUserId = this.config.user?.id || (event.user as Record<string, unknown>)?.id;
|
||||||
|
if (!hasUserId && isAnonymousTrackingEnabled(this.config)) {
|
||||||
|
const anonymousId = getAnonymousId();
|
||||||
|
if (anonymousId) {
|
||||||
|
enriched.anonymousId = anonymousId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = this.applyContextLevel(enriched);
|
const result = this.applyContextLevel(enriched);
|
||||||
|
|
||||||
if (includeFullContext && !this.contextReported) {
|
if (includeFullContext && !this.contextReported) {
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,7 @@ export class EventQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupExpiredDedupeEntries(): void {
|
private cleanupExpiredDedupeEntries(): void {
|
||||||
// 每 100 条或每 10 次清理一次,避免过于频繁
|
if (Math.random() > 0.1) return;
|
||||||
if (this.dedupeMap.size < 100 && Math.random() > 0.1) return;
|
|
||||||
|
|
||||||
const currentTime = now();
|
const currentTime = now();
|
||||||
const expiryTime = 60000;
|
const expiryTime = 60000;
|
||||||
|
|
@ -310,6 +309,7 @@ export class EventQueue {
|
||||||
window.removeEventListener('pagehide', this.onPagehide);
|
window.removeEventListener('pagehide', this.onPagehide);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.queue = [];
|
||||||
this.dedupeMap.clear();
|
this.dedupeMap.clear();
|
||||||
this.pendingCounts.clear();
|
this.pendingCounts.clear();
|
||||||
this.errorRateWindow = {};
|
this.errorRateWindow = {};
|
||||||
|
|
|
||||||
|
|
@ -238,8 +238,6 @@ export class Reporter {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'error':
|
case 'error':
|
||||||
return 'event';
|
return 'event';
|
||||||
case 'performance':
|
|
||||||
return 'transaction';
|
|
||||||
default:
|
default:
|
||||||
return event.type;
|
return event.type;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
import type { LightConfig, SpanContext, TransactionContext, Span, Transaction, LightClient, SamplingContext } from '../types';
|
||||||
|
import { Span as SpanImpl, Transaction as TransactionImpl } from '../utils/tracing';
|
||||||
|
import { logDebug } from '../utils/logger';
|
||||||
|
|
||||||
|
export class TracingManager {
|
||||||
|
private config: LightConfig;
|
||||||
|
private _client?: LightClient;
|
||||||
|
private currentTransaction: Transaction | null = null;
|
||||||
|
private spanStack: Span[] = [];
|
||||||
|
private dsnPublicKey?: string;
|
||||||
|
|
||||||
|
constructor(config: LightConfig, client?: LightClient, dsnPublicKey?: string) {
|
||||||
|
this.config = config;
|
||||||
|
this._client = client;
|
||||||
|
this.dsnPublicKey = dsnPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig(config: LightConfig): void {
|
||||||
|
this.config = config;
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
const txImpl = this.currentTransaction as unknown as TransactionImpl;
|
||||||
|
txImpl.setUserBaggage(config.user?.id, (config.user as { segment?: string } | undefined)?.segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSample(context: TransactionContext, parentSampled?: boolean): boolean {
|
||||||
|
const sampler = this.config.tracesSampler;
|
||||||
|
if (sampler) {
|
||||||
|
const samplingContext: SamplingContext = {
|
||||||
|
...context,
|
||||||
|
parentSampled,
|
||||||
|
};
|
||||||
|
const result = sampler(samplingContext);
|
||||||
|
if (typeof result === 'boolean') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const clampedRate = Math.max(0, Math.min(1, result));
|
||||||
|
if (clampedRate === 0) return false;
|
||||||
|
if (clampedRate === 1) return true;
|
||||||
|
return Math.random() < clampedRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate = this.config.tracesSampleRate ?? 0;
|
||||||
|
if (rate >= 1) return true;
|
||||||
|
if (rate <= 0) return false;
|
||||||
|
return Math.random() < rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null {
|
||||||
|
const parentTx = this.currentTransaction;
|
||||||
|
const parentSampled = parentTx ? parentTx.sampled : undefined;
|
||||||
|
|
||||||
|
const sampled = context.sampled ?? this.shouldSample(context, parentSampled);
|
||||||
|
|
||||||
|
const transaction = new TransactionImpl(
|
||||||
|
{
|
||||||
|
...context,
|
||||||
|
sampled,
|
||||||
|
},
|
||||||
|
this._client,
|
||||||
|
this.dsnPublicKey,
|
||||||
|
this.config.release,
|
||||||
|
this.config.environment
|
||||||
|
) as unknown as Transaction;
|
||||||
|
|
||||||
|
this.currentTransaction = transaction;
|
||||||
|
this.spanStack = [transaction];
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Transaction started', {
|
||||||
|
name: transaction.name,
|
||||||
|
traceId: transaction.traceId,
|
||||||
|
spanId: transaction.spanId,
|
||||||
|
sampled,
|
||||||
|
op: transaction.op,
|
||||||
|
});
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTransaction(): Transaction | null {
|
||||||
|
return this.currentTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentTransaction(transaction: Transaction | null): void {
|
||||||
|
this.currentTransaction = transaction;
|
||||||
|
if (!transaction) {
|
||||||
|
this.spanStack = [];
|
||||||
|
} else {
|
||||||
|
this.spanStack = [transaction];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSpan(): Span | null {
|
||||||
|
return this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentSpan(span: Span | null): void {
|
||||||
|
if (span === null) {
|
||||||
|
if (this.spanStack.length > 1) {
|
||||||
|
this.spanStack.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.spanStack.push(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startSpan(context: SpanContext): Span | null {
|
||||||
|
const parentSpan = this.getCurrentSpan();
|
||||||
|
|
||||||
|
if (!parentSpan && !context.traceId) {
|
||||||
|
logDebug('TracingManager', 'startSpan skipped: no active span and no traceId');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let span: Span;
|
||||||
|
|
||||||
|
if (parentSpan) {
|
||||||
|
span = (parentSpan as SpanImpl).startChild(context);
|
||||||
|
} else {
|
||||||
|
span = new SpanImpl({
|
||||||
|
...context,
|
||||||
|
parentSpanId: context.parentSpanId,
|
||||||
|
}) as unknown as Span;
|
||||||
|
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
if (this.currentTransaction.sampled !== false) {
|
||||||
|
(this.currentTransaction as unknown as TransactionImpl)._addSpan(span as unknown as SpanImpl);
|
||||||
|
}
|
||||||
|
(span as SpanImpl & { transaction: Transaction }).transaction = this.currentTransaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spanStack.push(span);
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Span started', {
|
||||||
|
op: span.op,
|
||||||
|
description: span.description,
|
||||||
|
spanId: span.spanId,
|
||||||
|
parentSpanId: span.parentSpanId,
|
||||||
|
stackDepth: this.spanStack.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSpanFinished(span: Span): void {
|
||||||
|
const idx = this.spanStack.lastIndexOf(span);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.spanStack.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.spanStack.length === 0 && this.currentTransaction) {
|
||||||
|
this.spanStack = [this.currentTransaction];
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Span finished', {
|
||||||
|
op: span.op,
|
||||||
|
description: span.description,
|
||||||
|
spanId: span.spanId,
|
||||||
|
stackDepth: this.spanStack.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTransactionFinished(transaction: Transaction): void {
|
||||||
|
if (this.currentTransaction === transaction) {
|
||||||
|
this.currentTransaction = null;
|
||||||
|
this.spanStack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug('TracingManager', 'Transaction finished', {
|
||||||
|
name: transaction.name,
|
||||||
|
traceId: transaction.traceId,
|
||||||
|
spanCount: transaction.spans.length,
|
||||||
|
sampled: transaction.sampled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finishSpan(span: Span): void {
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
finishTransaction(transaction: Transaction): void {
|
||||||
|
transaction.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
try {
|
||||||
|
this.currentTransaction.setStatus('aborted');
|
||||||
|
this.currentTransaction.finish();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentTransaction = null;
|
||||||
|
this.spanStack = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/index.ts
81
src/index.ts
|
|
@ -5,8 +5,13 @@ import NetworkPlugin from './plugins/NetworkPlugin';
|
||||||
import BehaviorPlugin from './plugins/BehaviorPlugin';
|
import BehaviorPlugin from './plugins/BehaviorPlugin';
|
||||||
import OfflinePlugin from './plugins/OfflinePlugin';
|
import OfflinePlugin from './plugins/OfflinePlugin';
|
||||||
import SamplingPlugin from './plugins/SamplingPlugin';
|
import SamplingPlugin from './plugins/SamplingPlugin';
|
||||||
import type { LightConfig, LightClient, SentryEvent, EventLevel, UserInfo, Breadcrumb, LightPlugin, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, DSNInfo } from './types';
|
import RouterPlugin from './plugins/RouterPlugin';
|
||||||
|
import InteractionPlugin from './plugins/InteractionPlugin';
|
||||||
|
import { Span as SpanClass, Transaction as TransactionClass, httpStatusToSpanStatus, dbStatusToSpanStatus, statusToSpanStatus } from './utils/tracing';
|
||||||
|
import { serializeBaggage, parseBaggage, createBaggageFromTransaction } from './utils/baggage';
|
||||||
|
import type { LightConfig, LightClient, SentryEvent, EventLevel, UserInfo, Breadcrumb, LightPlugin, ErrorEvent, PerformanceEvent, NetworkEvent, BehaviorEvent, DSNInfo, TransactionContext, SpanContext, Transaction, Span, SamplingContext, BaggageFields, SpanStatus } from './types';
|
||||||
import { logDebug, setDebug } from './utils/logger';
|
import { logDebug, setDebug } from './utils/logger';
|
||||||
|
import { setAnonymousId as setStoredAnonymousId, clearAnonymousId as clearStoredAnonymousId, getAnonymousId as getStoredAnonymousId } from './utils/identity';
|
||||||
|
|
||||||
let globalClient: LightClient | null = null;
|
let globalClient: LightClient | null = null;
|
||||||
|
|
||||||
|
|
@ -33,6 +38,12 @@ function init(config: LightConfig): LightClient {
|
||||||
if (pluginNames.includes('behavior') || pluginNames.includes('all')) {
|
if (pluginNames.includes('behavior') || pluginNames.includes('all')) {
|
||||||
defaultPlugins.push(new BehaviorPlugin());
|
defaultPlugins.push(new BehaviorPlugin());
|
||||||
}
|
}
|
||||||
|
if (pluginNames.includes('router') || pluginNames.includes('all')) {
|
||||||
|
defaultPlugins.push(new RouterPlugin());
|
||||||
|
}
|
||||||
|
if (pluginNames.includes('interaction') || pluginNames.includes('all')) {
|
||||||
|
defaultPlugins.push(new InteractionPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
logDebug('Index', 'Registering plugins', { count: defaultPlugins.length, plugins: defaultPlugins.map(p => p.name) });
|
logDebug('Index', 'Registering plugins', { count: defaultPlugins.length, plugins: defaultPlugins.map(p => p.name) });
|
||||||
|
|
||||||
|
|
@ -90,6 +101,38 @@ function setUser(user: UserInfo | null): void {
|
||||||
globalClient?.setUser(user);
|
globalClient?.setUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAnonymousId(id: string): void {
|
||||||
|
setStoredAnonymousId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAnonymousId(): void {
|
||||||
|
clearStoredAnonymousId();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnonymousId(): string | null {
|
||||||
|
return getStoredAnonymousId();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTransaction(context: TransactionContext): Transaction | null {
|
||||||
|
return globalClient?.startTransaction(context) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentTransaction(): Transaction | null {
|
||||||
|
return globalClient?.getCurrentTransaction() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSpan(): Span | null {
|
||||||
|
return globalClient?.getCurrentSpan() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSpan(span: Span | null): void {
|
||||||
|
globalClient?.setSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSpan(context: SpanContext): Span | null {
|
||||||
|
return globalClient?.startSpan(context) || null;
|
||||||
|
}
|
||||||
|
|
||||||
function setTag(key: string, value: string): void {
|
function setTag(key: string, value: string): void {
|
||||||
globalClient?.setTag(key, value);
|
globalClient?.setTag(key, value);
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +168,14 @@ export {
|
||||||
captureMessage,
|
captureMessage,
|
||||||
captureEvent,
|
captureEvent,
|
||||||
setUser,
|
setUser,
|
||||||
|
setAnonymousId,
|
||||||
|
clearAnonymousId,
|
||||||
|
getAnonymousId,
|
||||||
|
startTransaction,
|
||||||
|
getCurrentTransaction,
|
||||||
|
getCurrentSpan,
|
||||||
|
setSpan,
|
||||||
|
startSpan,
|
||||||
setTag,
|
setTag,
|
||||||
setTags,
|
setTags,
|
||||||
setExtra,
|
setExtra,
|
||||||
|
|
@ -133,14 +184,24 @@ export {
|
||||||
disable,
|
disable,
|
||||||
enable,
|
enable,
|
||||||
Client,
|
Client,
|
||||||
|
httpStatusToSpanStatus,
|
||||||
|
dbStatusToSpanStatus,
|
||||||
|
statusToSpanStatus,
|
||||||
|
serializeBaggage,
|
||||||
|
parseBaggage,
|
||||||
|
createBaggageFromTransaction,
|
||||||
ErrorPlugin,
|
ErrorPlugin,
|
||||||
PerformancePlugin,
|
PerformancePlugin,
|
||||||
NetworkPlugin,
|
NetworkPlugin,
|
||||||
BehaviorPlugin,
|
BehaviorPlugin,
|
||||||
OfflinePlugin,
|
OfflinePlugin,
|
||||||
SamplingPlugin,
|
SamplingPlugin,
|
||||||
|
RouterPlugin,
|
||||||
|
InteractionPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { SpanClass as Span, TransactionClass as Transaction };
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
LightConfig,
|
LightConfig,
|
||||||
LightClient,
|
LightClient,
|
||||||
|
|
@ -154,6 +215,13 @@ export type {
|
||||||
UserInfo,
|
UserInfo,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
DSNInfo,
|
DSNInfo,
|
||||||
|
TransactionContext,
|
||||||
|
SpanContext,
|
||||||
|
SamplingContext,
|
||||||
|
BaggageFields,
|
||||||
|
SpanStatus,
|
||||||
|
SpanJSON,
|
||||||
|
TransactionJSON,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -163,6 +231,14 @@ export default {
|
||||||
captureMessage,
|
captureMessage,
|
||||||
captureEvent,
|
captureEvent,
|
||||||
setUser,
|
setUser,
|
||||||
|
setAnonymousId,
|
||||||
|
clearAnonymousId,
|
||||||
|
getAnonymousId,
|
||||||
|
startTransaction,
|
||||||
|
getCurrentTransaction,
|
||||||
|
getCurrentSpan,
|
||||||
|
setSpan,
|
||||||
|
startSpan,
|
||||||
setTag,
|
setTag,
|
||||||
setTags,
|
setTags,
|
||||||
setExtra,
|
setExtra,
|
||||||
|
|
@ -170,4 +246,7 @@ export default {
|
||||||
flush,
|
flush,
|
||||||
disable,
|
disable,
|
||||||
enable,
|
enable,
|
||||||
|
Span: SpanClass,
|
||||||
|
Transaction: TransactionClass,
|
||||||
|
httpStatusToSpanStatus,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { now, isObject, matchPatterns } from '../utils/helper';
|
||||||
import { computeFingerprint } from '../utils/hash';
|
import { computeFingerprint } from '../utils/hash';
|
||||||
import { sanitizeUrl, shouldIgnoreUrl } from '../utils/env';
|
import { sanitizeUrl, shouldIgnoreUrl } from '../utils/env';
|
||||||
import { parseStackTrace } from '../utils/stacktrace';
|
import { parseStackTrace } from '../utils/stacktrace';
|
||||||
|
import { logError } from '../utils/logger';
|
||||||
|
|
||||||
interface ErrorPluginConfig {
|
interface ErrorPluginConfig {
|
||||||
maxStackFrames?: number;
|
maxStackFrames?: number;
|
||||||
|
|
@ -120,7 +121,7 @@ class ErrorPlugin implements LightPlugin {
|
||||||
this.client.captureEvent(errorEvent);
|
this.client.captureEvent(errorEvent);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[LightSDK] ErrorPlugin global error handler error:', e);
|
logError('ErrorPlugin', 'Global error handler error', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -153,7 +154,7 @@ class ErrorPlugin implements LightPlugin {
|
||||||
this.client.captureEvent(errorEvent);
|
this.client.captureEvent(errorEvent);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[LightSDK] ErrorPlugin unhandledrejection handler error:', e);
|
logError('ErrorPlugin', 'Unhandledrejection handler error', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -207,7 +208,7 @@ class ErrorPlugin implements LightPlugin {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[LightSDK] ErrorPlugin resource error handler error:', e);
|
logError('ErrorPlugin', 'Resource error handler error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import type { LightPlugin, LightClient, Transaction, Span, SpanStatus, LightConfig } from '../types';
|
||||||
|
import { now, isObject } from '../utils/helper';
|
||||||
|
|
||||||
|
interface InteractionPluginConfig {
|
||||||
|
enable?: boolean;
|
||||||
|
elementAttribute?: string;
|
||||||
|
maxTransactionDurationMs?: number;
|
||||||
|
idleTimeoutMs?: number;
|
||||||
|
captureScroll?: boolean;
|
||||||
|
trackClick?: boolean;
|
||||||
|
trackSubmit?: boolean;
|
||||||
|
trackKeypress?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TRACKING_TYPES = ['click', 'submit'];
|
||||||
|
|
||||||
|
class InteractionPlugin implements LightPlugin {
|
||||||
|
name = 'interaction';
|
||||||
|
version = '1.0.0';
|
||||||
|
|
||||||
|
private client!: LightClient;
|
||||||
|
private config: InteractionPluginConfig = {
|
||||||
|
enable: true,
|
||||||
|
elementAttribute: 'data-sentry-component',
|
||||||
|
maxTransactionDurationMs: 30000,
|
||||||
|
idleTimeoutMs: 1000,
|
||||||
|
trackClick: true,
|
||||||
|
trackSubmit: true,
|
||||||
|
trackKeypress: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
private currentTransaction: Transaction | null = null;
|
||||||
|
private idleTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private maxDurationTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
setup(client: LightClient): void {
|
||||||
|
this.client = client;
|
||||||
|
this.loadConfig();
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
if (!this.config.enable) return;
|
||||||
|
|
||||||
|
if (this.config.trackClick) {
|
||||||
|
document.addEventListener('click', this.handleClick, true);
|
||||||
|
}
|
||||||
|
if (this.config.trackSubmit) {
|
||||||
|
document.addEventListener('submit', this.handleSubmit, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadConfig(): void {
|
||||||
|
const clientConfig = this.client.config as LightConfig;
|
||||||
|
if (clientConfig && isObject((clientConfig as Record<string, unknown>).interaction)) {
|
||||||
|
this.config = { ...this.config, ...((clientConfig as Record<string, unknown>).interaction as object) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClick = (event: Event): void => {
|
||||||
|
if (this.isEventIgnored(event)) return;
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
const elementName = this.getElementName(target, 'click');
|
||||||
|
this.scheduleInteractionTransaction(elementName, 'click', target);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleSubmit = (event: Event): void => {
|
||||||
|
if (this.isEventIgnored(event)) return;
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
const elementName = this.getElementName(target, 'submit');
|
||||||
|
this.scheduleInteractionTransaction(elementName, 'submit', target);
|
||||||
|
};
|
||||||
|
|
||||||
|
private isEventIgnored(event: Event): boolean {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (!target) return true;
|
||||||
|
|
||||||
|
if (target.hasAttribute && target.hasAttribute('data-sentry-ignore')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleInteractionTransaction(name: string, op: string, element: HTMLElement): void {
|
||||||
|
if (this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debounceTimer = setTimeout(() => {
|
||||||
|
this.startInteractionTransaction(name, op, element);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getElementName(element: HTMLElement | null, eventType: string): string {
|
||||||
|
if (!element) return eventType;
|
||||||
|
|
||||||
|
const attr = this.config.elementAttribute || 'data-sentry-component';
|
||||||
|
const componentName = element.getAttribute(attr);
|
||||||
|
if (componentName) return componentName;
|
||||||
|
|
||||||
|
const tag = element.tagName?.toLowerCase() || 'element';
|
||||||
|
const id = element.id ? `#${element.id}` : '';
|
||||||
|
const classes = element.className && typeof element.className === 'string'
|
||||||
|
? `.${element.className.split(' ').slice(0, 3).join('.')}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return `${eventType}@${tag}${id}${classes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startInteractionTransaction(name: string, op: string, element: HTMLElement): void {
|
||||||
|
if (!this.client) return;
|
||||||
|
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
this.finishCurrentTransaction('ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const transaction = this.client.startTransaction({
|
||||||
|
name,
|
||||||
|
op: `ui.${op}`,
|
||||||
|
description: `User ${op} on ${name}`,
|
||||||
|
type: 'user-interaction',
|
||||||
|
tags: {
|
||||||
|
'interaction.type': op,
|
||||||
|
'interaction.target': name,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
tag: element.tagName?.toLowerCase(),
|
||||||
|
id: element.id || undefined,
|
||||||
|
text: element.textContent?.substring(0, 100) || undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transaction) {
|
||||||
|
this.currentTransaction = transaction;
|
||||||
|
this.scheduleIdleFinish();
|
||||||
|
this.scheduleMaxDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleIdleFinish(): void {
|
||||||
|
if (this.idleTimer) {
|
||||||
|
clearTimeout(this.idleTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idleTimeout = this.config.idleTimeoutMs ?? 1000;
|
||||||
|
|
||||||
|
this.idleTimer = setTimeout(() => {
|
||||||
|
this.finishCurrentTransaction('ok');
|
||||||
|
}, idleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleMaxDuration(): void {
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDuration = this.config.maxTransactionDurationMs ?? 30000;
|
||||||
|
this.maxDurationTimer = setTimeout(() => {
|
||||||
|
this.finishCurrentTransaction('deadline_exceeded');
|
||||||
|
}, maxDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishCurrentTransaction(status: SpanStatus = 'ok'): void {
|
||||||
|
if (this.idleTimer) {
|
||||||
|
clearTimeout(this.idleTimer);
|
||||||
|
this.idleTimer = null;
|
||||||
|
}
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
this.maxDurationTimer = null;
|
||||||
|
}
|
||||||
|
if (this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
this.currentTransaction.setStatus(status);
|
||||||
|
this.currentTransaction.finish();
|
||||||
|
this.currentTransaction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.removeEventListener('click', this.handleClick, true);
|
||||||
|
document.removeEventListener('submit', this.handleSubmit, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.idleTimer) {
|
||||||
|
clearTimeout(this.idleTimer);
|
||||||
|
this.idleTimer = null;
|
||||||
|
}
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
this.maxDurationTimer = null;
|
||||||
|
}
|
||||||
|
if (this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finishCurrentTransaction('aborted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InteractionPlugin;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { LightPlugin, LightClient, NetworkEvent, LightConfig } from '../types';
|
import type { LightPlugin, LightClient, NetworkEvent, LightConfig, Span, Transaction } from '../types';
|
||||||
import { now, isObject } from '../utils/helper';
|
import { now, isObject } from '../utils/helper';
|
||||||
import { sanitizeUrl, shouldIgnoreUrl } from '../utils/env';
|
import { sanitizeUrl, shouldIgnoreUrl } from '../utils/env';
|
||||||
|
|
||||||
|
|
@ -17,11 +17,12 @@ interface NetworkPluginConfig {
|
||||||
slowThreshold?: number;
|
slowThreshold?: number;
|
||||||
slowSampleRate?: number;
|
slowSampleRate?: number;
|
||||||
ignoreStatusCodes?: number[];
|
ignoreStatusCodes?: number[];
|
||||||
|
tracePropagationTargets?: (string | RegExp)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkPlugin implements LightPlugin {
|
class NetworkPlugin implements LightPlugin {
|
||||||
name = 'network';
|
name = 'network';
|
||||||
version = '1.0.0';
|
version = '1.1.0';
|
||||||
|
|
||||||
private client!: LightClient;
|
private client!: LightClient;
|
||||||
private config: NetworkPluginConfig = {
|
private config: NetworkPluginConfig = {
|
||||||
|
|
@ -63,12 +64,63 @@ class NetworkPlugin implements LightPlugin {
|
||||||
if (clientConfig.ignoreUrls) {
|
if (clientConfig.ignoreUrls) {
|
||||||
this.config.ignoreUrls = [...(this.config.ignoreUrls || []), ...clientConfig.ignoreUrls];
|
this.config.ignoreUrls = [...(this.config.ignoreUrls || []), ...clientConfig.ignoreUrls];
|
||||||
}
|
}
|
||||||
|
if (clientConfig.tracePropagationTargets) {
|
||||||
|
this.config.tracePropagationTargets = [...(this.config.tracePropagationTargets || []), ...clientConfig.tracePropagationTargets];
|
||||||
|
} else if (clientConfig.tracingOrigins) {
|
||||||
|
this.config.tracePropagationTargets = [...(this.config.tracePropagationTargets || []), ...clientConfig.tracingOrigins];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldIgnore(url: string): boolean {
|
private shouldIgnore(url: string): boolean {
|
||||||
return shouldIgnoreUrl(url, this.config.ignoreUrls || []);
|
return shouldIgnoreUrl(url, this.config.ignoreUrls || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldTracePropagate(url: string): boolean {
|
||||||
|
const targets = this.config.tracePropagationTargets;
|
||||||
|
if (!targets || targets.length === 0) {
|
||||||
|
return this.isSameOrigin(url);
|
||||||
|
}
|
||||||
|
return targets.some(pattern => {
|
||||||
|
if (typeof pattern === 'string') {
|
||||||
|
return url.includes(pattern);
|
||||||
|
}
|
||||||
|
return pattern.test(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSameOrigin(url: string): boolean {
|
||||||
|
if (typeof location === 'undefined') return false;
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url, location.origin);
|
||||||
|
return urlObj.origin === location.origin;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBaggageHeader(): string | undefined {
|
||||||
|
if (!this.client) return undefined;
|
||||||
|
|
||||||
|
const clientInternal = this.client as unknown as {
|
||||||
|
tracingManager?: {
|
||||||
|
getCurrentTransaction?: () => Transaction | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = clientInternal.tracingManager?.getCurrentTransaction?.();
|
||||||
|
if (!tx) return undefined;
|
||||||
|
|
||||||
|
const transactionImpl = tx as unknown as {
|
||||||
|
toBaggage?: () => string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (transactionImpl.toBaggage) {
|
||||||
|
return transactionImpl.toBaggage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private shouldSample(isError: boolean, duration: number): boolean {
|
private shouldSample(isError: boolean, duration: number): boolean {
|
||||||
let rate = isError ? (this.config.errorSampleRate ?? 1.0) : (this.config.successSampleRate ?? 0.01);
|
let rate = isError ? (this.config.errorSampleRate ?? 1.0) : (this.config.successSampleRate ?? 0.01);
|
||||||
|
|
||||||
|
|
@ -81,6 +133,34 @@ class NetworkPlugin implements LightPlugin {
|
||||||
return Math.random() < rate;
|
return Math.random() < rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createHttpSpan(method: string, url: string): Span | null {
|
||||||
|
const currentSpan = this.client.getCurrentSpan();
|
||||||
|
if (!currentSpan) return null;
|
||||||
|
|
||||||
|
const span = currentSpan.startChild({
|
||||||
|
op: 'http.client',
|
||||||
|
description: `${method.toUpperCase()} ${url}`,
|
||||||
|
tags: {
|
||||||
|
'http.method': method.toUpperCase(),
|
||||||
|
'http.url': sanitizeUrl(url),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishHttpSpan(span: Span, statusCode: number, duration: number, isError: boolean): void {
|
||||||
|
span.setData('http.response.duration', duration);
|
||||||
|
|
||||||
|
if (statusCode > 0) {
|
||||||
|
span.setHttpStatus(statusCode);
|
||||||
|
} else if (isError) {
|
||||||
|
span.setStatus('internal_error');
|
||||||
|
}
|
||||||
|
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
|
||||||
private patchFetch(): void {
|
private patchFetch(): void {
|
||||||
if (typeof fetch === 'undefined') return;
|
if (typeof fetch === 'undefined') return;
|
||||||
|
|
||||||
|
|
@ -97,7 +177,27 @@ class NetworkPlugin implements LightPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizedUrl = sanitizeUrl(url);
|
const sanitizedUrl = sanitizeUrl(url);
|
||||||
const promise = self.originalFetch.call(window, input, init);
|
|
||||||
|
let httpSpan = self.createHttpSpan(method, sanitizedUrl);
|
||||||
|
|
||||||
|
const shouldInjectTrace = self.shouldTracePropagate(url) && !!httpSpan;
|
||||||
|
let modifiedInit = init;
|
||||||
|
|
||||||
|
if (shouldInjectTrace && httpSpan) {
|
||||||
|
const traceHeader = httpSpan.toTraceparent();
|
||||||
|
const baggage = self.getBaggageHeader();
|
||||||
|
|
||||||
|
modifiedInit = {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
...(init?.headers || {}),
|
||||||
|
'sentry-trace': traceHeader,
|
||||||
|
...(baggage ? { 'baggage': baggage } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = self.originalFetch.call(window, input, modifiedInit);
|
||||||
|
|
||||||
promise.then(
|
promise.then(
|
||||||
(response) => {
|
(response) => {
|
||||||
|
|
@ -105,6 +205,10 @@ class NetworkPlugin implements LightPlugin {
|
||||||
const isSuccess = response.ok;
|
const isSuccess = response.ok;
|
||||||
const statusCode = response.status;
|
const statusCode = response.status;
|
||||||
|
|
||||||
|
if (httpSpan) {
|
||||||
|
self.finishHttpSpan(httpSpan, statusCode, duration, !isSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.config.ignoreStatusCodes?.includes(statusCode)) {
|
if (self.config.ignoreStatusCodes?.includes(statusCode)) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +234,10 @@ class NetworkPlugin implements LightPlugin {
|
||||||
(error) => {
|
(error) => {
|
||||||
const duration = now() - startTime;
|
const duration = now() - startTime;
|
||||||
|
|
||||||
|
if (httpSpan) {
|
||||||
|
self.finishHttpSpan(httpSpan, 0, duration, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.shouldSample(true, duration)) {
|
if (self.shouldSample(true, duration)) {
|
||||||
self.reportNetwork({
|
self.reportNetwork({
|
||||||
sub_type: 'fetch',
|
sub_type: 'fetch',
|
||||||
|
|
@ -180,12 +288,32 @@ class NetworkPlugin implements LightPlugin {
|
||||||
requestSize = body.length;
|
requestSize = body.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const httpSpan = self.createHttpSpan(method, sanitizedUrl);
|
||||||
|
const shouldInjectTrace = self.shouldTracePropagate(url) && !!httpSpan;
|
||||||
|
|
||||||
|
if (shouldInjectTrace && httpSpan) {
|
||||||
|
try {
|
||||||
|
const traceHeader = httpSpan.toTraceparent();
|
||||||
|
const baggage = self.getBaggageHeader();
|
||||||
|
this.setRequestHeader('sentry-trace', traceHeader);
|
||||||
|
if (baggage) {
|
||||||
|
this.setRequestHeader('baggage', baggage);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const originalOnLoadEnd = (this as XMLHttpRequest & { onloadend?: ((this: XMLHttpRequest, ev: ProgressEvent) => unknown) | null }).onloadend;
|
const originalOnLoadEnd = (this as XMLHttpRequest & { onloadend?: ((this: XMLHttpRequest, ev: ProgressEvent) => unknown) | null }).onloadend;
|
||||||
(this as XMLHttpRequest & { onloadend?: ((this: XMLHttpRequest, ev: ProgressEvent) => unknown) | null }).onloadend = function (ev: ProgressEvent) {
|
(this as XMLHttpRequest & { onloadend?: ((this: XMLHttpRequest, ev: ProgressEvent) => unknown) | null }).onloadend = function (ev: ProgressEvent) {
|
||||||
const duration = now() - startTime;
|
const duration = now() - startTime;
|
||||||
const status = this.status;
|
const status = this.status;
|
||||||
const isSuccess = status >= 200 && status < 300;
|
const isSuccess = status >= 200 && status < 300;
|
||||||
|
|
||||||
|
if (httpSpan) {
|
||||||
|
self.finishHttpSpan(httpSpan, status, duration, !isSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
if (!self.config.ignoreStatusCodes?.includes(status)
|
if (!self.config.ignoreStatusCodes?.includes(status)
|
||||||
&& self.shouldSample(!isSuccess, duration)
|
&& self.shouldSample(!isSuccess, duration)
|
||||||
&& (isSuccess ? self.config.captureSuccess : true)) {
|
&& (isSuccess ? self.config.captureSuccess : true)) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { LightPlugin, LightClient, PerformanceEvent, LightConfig } from '../types';
|
import type { LightPlugin, LightClient, PerformanceEvent, LightConfig, Transaction, Span } from '../types';
|
||||||
import { now, isObject } from '../utils/helper';
|
import { now, isObject } from '../utils/helper';
|
||||||
|
|
||||||
interface PerformancePluginConfig {
|
interface PerformancePluginConfig {
|
||||||
|
|
@ -9,11 +9,19 @@ interface PerformancePluginConfig {
|
||||||
longTaskSampleRate?: number;
|
longTaskSampleRate?: number;
|
||||||
captureNavigation?: boolean;
|
captureNavigation?: boolean;
|
||||||
captureFP?: boolean;
|
captureFP?: boolean;
|
||||||
|
enablePageLoadTracing?: boolean; // 是否启用 Page Load Transaction
|
||||||
|
maxResourceSpans?: number; // 最大 Resource Span 数量
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VitalMeasurement {
|
||||||
|
value: number;
|
||||||
|
unit?: string;
|
||||||
|
rating?: 'good' | 'needs-improvement' | 'poor';
|
||||||
}
|
}
|
||||||
|
|
||||||
class PerformancePlugin implements LightPlugin {
|
class PerformancePlugin implements LightPlugin {
|
||||||
name = 'performance';
|
name = 'performance';
|
||||||
version = '1.0.0';
|
version = '2.0.0';
|
||||||
|
|
||||||
private client!: LightClient;
|
private client!: LightClient;
|
||||||
private config: PerformancePluginConfig = {
|
private config: PerformancePluginConfig = {
|
||||||
|
|
@ -24,6 +32,8 @@ class PerformancePlugin implements LightPlugin {
|
||||||
longTaskSampleRate: 0.05,
|
longTaskSampleRate: 0.05,
|
||||||
captureNavigation: true,
|
captureNavigation: true,
|
||||||
captureFP: true,
|
captureFP: true,
|
||||||
|
enablePageLoadTracing: false,
|
||||||
|
maxResourceSpans: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
private observers: PerformanceObserver[] = [];
|
private observers: PerformanceObserver[] = [];
|
||||||
|
|
@ -35,16 +45,44 @@ class PerformancePlugin implements LightPlugin {
|
||||||
private ttiReported: boolean = false;
|
private ttiReported: boolean = false;
|
||||||
private ttiTimer: ReturnType<typeof setTimeout> | null = null;
|
private ttiTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
private pageLoadTransaction: Transaction | null = null;
|
||||||
|
private measurements: Record<string, VitalMeasurement> = {};
|
||||||
|
private resourceSpanCount: number = 0;
|
||||||
|
|
||||||
setup(client: LightClient): void {
|
setup(client: LightClient): void {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.loadConfig();
|
this.loadConfig();
|
||||||
|
|
||||||
|
if (this.config.enablePageLoadTracing) {
|
||||||
|
this.startPageLoadTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
this.observeWebVitals();
|
this.observeWebVitals();
|
||||||
this.observeLongTasks();
|
this.observeLongTasks();
|
||||||
this.observeNavigation();
|
this.observeNavigation();
|
||||||
this.observeResources();
|
this.observeResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startPageLoadTransaction(): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const pageUrl = typeof location !== 'undefined' ? location.pathname : '/';
|
||||||
|
const navStart = typeof performance !== 'undefined' && performance.timing?.navigationStart
|
||||||
|
? performance.timing.navigationStart
|
||||||
|
: now();
|
||||||
|
|
||||||
|
this.pageLoadTransaction = this.client.startTransaction({
|
||||||
|
name: pageUrl,
|
||||||
|
op: 'pageload',
|
||||||
|
description: `Page load - ${pageUrl}`,
|
||||||
|
startTimestamp: navStart,
|
||||||
|
type: 'pageload',
|
||||||
|
tags: {
|
||||||
|
'page.url': pageUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private loadConfig(): void {
|
private loadConfig(): void {
|
||||||
const clientConfig = this.client.config as LightConfig;
|
const clientConfig = this.client.config as LightConfig;
|
||||||
if (clientConfig && isObject(clientConfig.performance)) {
|
if (clientConfig && isObject(clientConfig.performance)) {
|
||||||
|
|
@ -69,28 +107,32 @@ class PerformancePlugin implements LightPlugin {
|
||||||
private observeWebVitals(): void {
|
private observeWebVitals(): void {
|
||||||
if (typeof PerformanceObserver === 'undefined') return;
|
if (typeof PerformanceObserver === 'undefined') return;
|
||||||
|
|
||||||
if (this.config.captureFP) {
|
this.observePaint();
|
||||||
this.observeFP();
|
|
||||||
}
|
|
||||||
this.observeLCP();
|
this.observeLCP();
|
||||||
this.observeFID();
|
this.observeFID();
|
||||||
this.observeCLS();
|
this.observeCLS();
|
||||||
this.observeFCP();
|
|
||||||
this.observeTTFB();
|
this.observeTTFB();
|
||||||
}
|
}
|
||||||
|
|
||||||
private observeFP(): void {
|
private observePaint(): void {
|
||||||
try {
|
try {
|
||||||
const po = new PerformanceObserver((entryList) => {
|
const po = new PerformanceObserver((entryList) => {
|
||||||
const entries = entryList.getEntriesByName('first-paint');
|
const fpEntries = entryList.getEntriesByName('first-paint');
|
||||||
if (entries.length > 0) {
|
if (fpEntries.length > 0 && this.config.captureFP) {
|
||||||
this.reportMetric('FP', entries[0].startTime, 'ms', {}, 'vitals');
|
this.reportMetric('FP', fpEntries[0].startTime, 'ms', {}, 'vitals');
|
||||||
|
}
|
||||||
|
const fcpEntries = entryList.getEntriesByName('first-contentful-paint');
|
||||||
|
if (fcpEntries.length > 0) {
|
||||||
|
this.fcpTime = fcpEntries[0].startTime;
|
||||||
|
this.lastLongTaskEndTime = fcpEntries[0].startTime;
|
||||||
|
this.reportMetric('FCP', fcpEntries[0].startTime, 'ms', {}, 'vitals');
|
||||||
|
this.scheduleTTI();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
po.observe({ type: 'paint', buffered: true });
|
po.observe({ type: 'paint', buffered: true });
|
||||||
this.observers.push(po);
|
this.observers.push(po);
|
||||||
} catch {
|
} catch {
|
||||||
// FP not supported
|
// Paint observer not supported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,24 +209,6 @@ class PerformancePlugin implements LightPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private observeFCP(): void {
|
|
||||||
try {
|
|
||||||
const po = new PerformanceObserver((entryList) => {
|
|
||||||
const entries = entryList.getEntriesByName('first-contentful-paint');
|
|
||||||
if (entries.length > 0) {
|
|
||||||
this.fcpTime = entries[0].startTime;
|
|
||||||
this.lastLongTaskEndTime = entries[0].startTime;
|
|
||||||
this.reportMetric('FCP', entries[0].startTime, 'ms', {}, 'vitals');
|
|
||||||
this.scheduleTTI();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
po.observe({ type: 'paint', buffered: true });
|
|
||||||
this.observers.push(po);
|
|
||||||
} catch {
|
|
||||||
// FCP not supported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private observeTTFB(): void {
|
private observeTTFB(): void {
|
||||||
try {
|
try {
|
||||||
const po = new PerformanceObserver((entryList) => {
|
const po = new PerformanceObserver((entryList) => {
|
||||||
|
|
@ -276,6 +300,12 @@ class PerformancePlugin implements LightPlugin {
|
||||||
this.reportMetric(name, value, 'ms');
|
this.reportMetric(name, value, 'ms');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.pageLoadTransaction && this.pageLoadTransaction.sampled !== false) {
|
||||||
|
this.buildNavigationSpans(timing, navigationStart);
|
||||||
|
this.finishPageLoadTransaction(timing.loadEventEnd || now());
|
||||||
|
}
|
||||||
|
|
||||||
this.navLoadTimer = null;
|
this.navLoadTimer = null;
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
@ -283,20 +313,63 @@ class PerformancePlugin implements LightPlugin {
|
||||||
window.addEventListener('load', this.onLoad);
|
window.addEventListener('load', this.onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildNavigationSpans(timing: PerformanceTiming, navigationStart: number): void {
|
||||||
|
const spanDefs: Array<{ op: string; desc: string; start: number; end: number }> = [
|
||||||
|
{ op: 'dns', desc: 'DNS Lookup', start: timing.domainLookupStart, end: timing.domainLookupEnd },
|
||||||
|
{ op: 'tcp', desc: 'TCP Connection', start: timing.connectStart, end: timing.connectEnd },
|
||||||
|
{ op: 'ssl', desc: 'SSL/TLS Negotiation', start: timing.secureConnectionStart, end: timing.connectEnd },
|
||||||
|
{ op: 'ttfb', desc: 'Time to First Byte', start: timing.requestStart, end: timing.responseStart },
|
||||||
|
{ op: 'response', desc: 'Response Download', start: timing.responseStart, end: timing.responseEnd },
|
||||||
|
{ op: 'dom_parse', desc: 'DOM Parsing', start: timing.responseEnd, end: timing.domInteractive },
|
||||||
|
{ op: 'dom_content_loaded', desc: 'DOM Content Loaded', start: timing.domLoading, end: timing.domContentLoadedEventEnd },
|
||||||
|
{ op: 'load', desc: 'Page Load', start: timing.navigationStart, end: timing.loadEventEnd },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const def of spanDefs) {
|
||||||
|
if (def.start > 0 && def.end > 0 && def.end > def.start) {
|
||||||
|
this.createNavigationSpan(
|
||||||
|
def.op,
|
||||||
|
def.desc,
|
||||||
|
def.start,
|
||||||
|
def.end
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishPageLoadTransaction(endTime: number): void {
|
||||||
|
if (!this.pageLoadTransaction) return;
|
||||||
|
|
||||||
|
if (Object.keys(this.measurements).length > 0) {
|
||||||
|
(this.pageLoadTransaction as unknown as { setData: (k: string, v: unknown) => void }).setData(
|
||||||
|
'measurements',
|
||||||
|
this.measurements
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pageLoadTransaction.setStatus('ok');
|
||||||
|
this.pageLoadTransaction.finish(endTime);
|
||||||
|
this.pageLoadTransaction = null;
|
||||||
|
}
|
||||||
|
|
||||||
private observeResources(): void {
|
private observeResources(): void {
|
||||||
if (!this.config.captureResources) return;
|
if (!this.config.captureResources && !this.config.enablePageLoadTracing) return;
|
||||||
if (typeof performance === 'undefined') return;
|
if (typeof performance === 'undefined') return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const po = new PerformanceObserver((entryList) => {
|
const po = new PerformanceObserver((entryList) => {
|
||||||
const entries = entryList.getEntriesByType('resource') as PerformanceResourceTiming[];
|
const entries = entryList.getEntriesByType('resource') as PerformanceResourceTiming[];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.duration > 1000) {
|
if (entry.duration > 1000 && this.config.captureResources) {
|
||||||
this.reportMetric('resource_slow', entry.duration, 'ms', {
|
this.reportMetric('resource_slow', entry.duration, 'ms', {
|
||||||
resource_name: entry.name.substring(0, 200),
|
resource_name: entry.name.substring(0, 200),
|
||||||
resource_type: entry.initiatorType,
|
resource_type: entry.initiatorType,
|
||||||
}, 'resource');
|
}, 'resource');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.enablePageLoadTracing && this.pageLoadTransaction) {
|
||||||
|
this.createResourceSpan(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
po.observe({ type: 'resource', buffered: true });
|
po.observe({ type: 'resource', buffered: true });
|
||||||
|
|
@ -345,6 +418,78 @@ class PerformancePlugin implements LightPlugin {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client.captureEvent(event);
|
this.client.captureEvent(event);
|
||||||
|
|
||||||
|
if (this.pageLoadTransaction && this.pageLoadTransaction.sampled !== false) {
|
||||||
|
this.recordMeasurement(name, value, unit, rating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private recordMeasurement(name: string, value: number, unit?: string, rating?: 'good' | 'needs-improvement' | 'poor'): void {
|
||||||
|
if (!this.pageLoadTransaction) return;
|
||||||
|
|
||||||
|
this.measurements[name] = {
|
||||||
|
value: Math.round(value * 100) / 100,
|
||||||
|
unit,
|
||||||
|
rating,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pageLoadTransaction.setTag(`measurement.${name}.rating`, rating || 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
private createNavigationSpan(
|
||||||
|
op: string,
|
||||||
|
description: string,
|
||||||
|
startTime: number,
|
||||||
|
endTime: number,
|
||||||
|
tags?: Record<string, string>
|
||||||
|
): void {
|
||||||
|
if (!this.pageLoadTransaction || this.pageLoadTransaction.sampled === false) return;
|
||||||
|
if (endTime <= startTime) return;
|
||||||
|
|
||||||
|
const span = this.pageLoadTransaction.startChild({
|
||||||
|
op: `browser.${op}`,
|
||||||
|
description,
|
||||||
|
startTimestamp: startTime,
|
||||||
|
tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
span.finish(endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createResourceSpan(entry: PerformanceResourceTiming): void {
|
||||||
|
if (!this.pageLoadTransaction || this.pageLoadTransaction.sampled === false) return;
|
||||||
|
|
||||||
|
const maxResources = this.config.maxResourceSpans ?? 50;
|
||||||
|
if (this.resourceSpanCount >= maxResources) return;
|
||||||
|
this.resourceSpanCount++;
|
||||||
|
|
||||||
|
const timeOrigin = typeof performance !== 'undefined' && performance.timeOrigin
|
||||||
|
? performance.timeOrigin
|
||||||
|
: (performance.timing?.navigationStart || 0);
|
||||||
|
const startTime = timeOrigin + entry.startTime;
|
||||||
|
const endTime = timeOrigin + (entry.responseEnd || entry.duration || entry.startTime);
|
||||||
|
|
||||||
|
if (endTime <= startTime) return;
|
||||||
|
|
||||||
|
const url = entry.name.substring(0, 200);
|
||||||
|
const span = this.pageLoadTransaction.startChild({
|
||||||
|
op: `resource.${entry.initiatorType || 'other'}`,
|
||||||
|
description: url,
|
||||||
|
startTimestamp: startTime,
|
||||||
|
tags: {
|
||||||
|
'resource.type': entry.initiatorType || 'other',
|
||||||
|
'resource.url': url,
|
||||||
|
'resource.transfer_size': String(entry.transferSize || 0),
|
||||||
|
'resource.decoded_size': String(entry.decodedBodySize || 0),
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
transferSize: entry.transferSize,
|
||||||
|
decodedSize: entry.decodedBodySize,
|
||||||
|
encodedSize: entry.encodedBodySize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
span.finish(endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
|
@ -370,6 +515,15 @@ class PerformancePlugin implements LightPlugin {
|
||||||
if (typeof window !== 'undefined' && this.onLoad) {
|
if (typeof window !== 'undefined' && this.onLoad) {
|
||||||
window.removeEventListener('load', this.onLoad);
|
window.removeEventListener('load', this.onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.pageLoadTransaction) {
|
||||||
|
this.pageLoadTransaction.setStatus('internal_error');
|
||||||
|
this.pageLoadTransaction.finish();
|
||||||
|
this.pageLoadTransaction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.measurements = {};
|
||||||
|
this.resourceSpanCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
import type { LightPlugin, LightClient, Transaction, SpanStatus, LightConfig } from '../types';
|
||||||
|
import { now, isObject } from '../utils/helper';
|
||||||
|
|
||||||
|
interface RouterPluginConfig {
|
||||||
|
enable?: boolean;
|
||||||
|
instrumentHistory?: boolean;
|
||||||
|
instrumentHash?: boolean;
|
||||||
|
routeNameFormatter?: (url: string) => string;
|
||||||
|
maxTransactionDurationMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouterPlugin implements LightPlugin {
|
||||||
|
name = 'router';
|
||||||
|
version = '1.0.0';
|
||||||
|
|
||||||
|
private client!: LightClient;
|
||||||
|
private config: RouterPluginConfig = {
|
||||||
|
enable: true,
|
||||||
|
instrumentHistory: true,
|
||||||
|
instrumentHash: true,
|
||||||
|
maxTransactionDurationMs: 30000,
|
||||||
|
};
|
||||||
|
|
||||||
|
private currentTransaction: Transaction | null = null;
|
||||||
|
private lastRoute: string = '';
|
||||||
|
private originalPushState?: typeof history.pushState;
|
||||||
|
private originalReplaceState?: typeof history.replaceState;
|
||||||
|
private maxDurationTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private routeChangeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
setup(client: LightClient): void {
|
||||||
|
this.client = client;
|
||||||
|
this.loadConfig();
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
if (!this.config.enable) return;
|
||||||
|
|
||||||
|
if (this.config.instrumentHistory) {
|
||||||
|
this.instrumentHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.instrumentHash) {
|
||||||
|
this.instrumentHash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadConfig(): void {
|
||||||
|
const clientConfig = this.client.config as LightConfig;
|
||||||
|
if (clientConfig && isObject((clientConfig as Record<string, unknown>).router)) {
|
||||||
|
this.config = { ...this.config, ...((clientConfig as Record<string, unknown>).router as object) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private instrumentHistory(): void {
|
||||||
|
if (typeof history === 'undefined' || !history.pushState) return;
|
||||||
|
|
||||||
|
this.originalPushState = history.pushState.bind(history);
|
||||||
|
this.originalReplaceState = history.replaceState.bind(history);
|
||||||
|
|
||||||
|
history.pushState = (data: unknown, title: string, url?: string | null): void => {
|
||||||
|
this.originalPushState!(data, title, url);
|
||||||
|
this.handleRouteChange(url || location.pathname + location.search, 'pushState');
|
||||||
|
};
|
||||||
|
|
||||||
|
history.replaceState = (data: unknown, title: string, url?: string | null): void => {
|
||||||
|
this.originalReplaceState!(data, title, url);
|
||||||
|
this.handleRouteChange(url || location.pathname + location.search, 'replaceState');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', this.handlePopState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePopState = (): void => {
|
||||||
|
this.handleRouteChange(location.pathname + location.search, 'popstate');
|
||||||
|
};
|
||||||
|
|
||||||
|
private instrumentHash(): void {
|
||||||
|
window.addEventListener('hashchange', this.handleHashChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleHashChange = (): void => {
|
||||||
|
this.handleRouteChange(location.hash || '#/', 'hashchange');
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleRouteChange(to: string, source: string): void {
|
||||||
|
if (this.routeChangeTimer) {
|
||||||
|
clearTimeout(this.routeChangeTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routeChangeTimer = setTimeout(() => {
|
||||||
|
this.routeChangeTimer = null;
|
||||||
|
this.processRouteChange(to, source);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processRouteChange(to: string, source: string): void {
|
||||||
|
const routeName = this.config.routeNameFormatter
|
||||||
|
? this.config.routeNameFormatter(to)
|
||||||
|
: to.split('?')[0];
|
||||||
|
|
||||||
|
if (routeName === this.lastRoute) return;
|
||||||
|
this.lastRoute = routeName;
|
||||||
|
|
||||||
|
this.finishCurrentTransaction('ok');
|
||||||
|
this.startNavigationTransaction(routeName, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startNavigationTransaction(name: string, source: string): void {
|
||||||
|
if (!this.client) return;
|
||||||
|
|
||||||
|
const transaction = this.client.startTransaction({
|
||||||
|
name,
|
||||||
|
op: 'navigation',
|
||||||
|
description: `Navigation to ${name}`,
|
||||||
|
type: 'navigation',
|
||||||
|
tags: {
|
||||||
|
'route.name': name,
|
||||||
|
'route.source': source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transaction) {
|
||||||
|
this.currentTransaction = transaction;
|
||||||
|
this.startMaxDurationTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startMaxDurationTimer(): void {
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDuration = this.config.maxTransactionDurationMs ?? 30000;
|
||||||
|
this.maxDurationTimer = setTimeout(() => {
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
this.currentTransaction.setStatus('deadline_exceeded');
|
||||||
|
this.finishCurrentTransaction('deadline_exceeded');
|
||||||
|
}
|
||||||
|
}, maxDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishCurrentTransaction(status: SpanStatus = 'ok'): void {
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
this.maxDurationTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTransaction) {
|
||||||
|
this.currentTransaction.setStatus(status);
|
||||||
|
this.currentTransaction.finish();
|
||||||
|
this.currentTransaction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this.originalPushState && typeof history !== 'undefined') {
|
||||||
|
history.pushState = this.originalPushState;
|
||||||
|
}
|
||||||
|
if (this.originalReplaceState && typeof history !== 'undefined') {
|
||||||
|
history.replaceState = this.originalReplaceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('popstate', this.handlePopState);
|
||||||
|
window.removeEventListener('hashchange', this.handleHashChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.maxDurationTimer) {
|
||||||
|
clearTimeout(this.maxDurationTimer);
|
||||||
|
this.maxDurationTimer = null;
|
||||||
|
}
|
||||||
|
if (this.routeChangeTimer) {
|
||||||
|
clearTimeout(this.routeChangeTimer);
|
||||||
|
this.routeChangeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finishCurrentTransaction('aborted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouterPlugin;
|
||||||
|
|
@ -54,10 +54,33 @@ export interface DeviceContext {
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TraceContext {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id?: string;
|
||||||
|
op?: string;
|
||||||
|
status?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaggageFields {
|
||||||
|
trace_id?: string;
|
||||||
|
public_key?: string;
|
||||||
|
sample_rate?: string;
|
||||||
|
sampled?: string;
|
||||||
|
user_id?: string;
|
||||||
|
user_segment?: string;
|
||||||
|
release?: string;
|
||||||
|
environment?: string;
|
||||||
|
replay_id?: string;
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Contexts {
|
export interface Contexts {
|
||||||
browser?: BrowserContext;
|
browser?: BrowserContext;
|
||||||
os?: OSContext;
|
os?: OSContext;
|
||||||
device?: DeviceContext;
|
device?: DeviceContext;
|
||||||
|
trace?: TraceContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Breadcrumb {
|
export interface Breadcrumb {
|
||||||
|
|
@ -76,6 +99,7 @@ export interface BaseEvent {
|
||||||
release?: string;
|
release?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
user?: UserInfo;
|
user?: UserInfo;
|
||||||
|
anonymousId?: string; // 匿名用户ID(未登录用户)
|
||||||
tags?: Record<string, string>;
|
tags?: Record<string, string>;
|
||||||
extra?: Record<string, unknown>;
|
extra?: Record<string, unknown>;
|
||||||
breadcrumbs?: Breadcrumb[];
|
breadcrumbs?: Breadcrumb[];
|
||||||
|
|
@ -129,7 +153,124 @@ export interface CountEvent extends BaseEvent {
|
||||||
ts_end: number;
|
ts_end: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SentryEvent = ErrorEvent | PerformanceEvent | NetworkEvent | BehaviorEvent | CountEvent;
|
export type SpanStatus =
|
||||||
|
| 'ok'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'unknown'
|
||||||
|
| 'unknown_error'
|
||||||
|
| 'invalid_argument'
|
||||||
|
| 'deadline_exceeded'
|
||||||
|
| 'not_found'
|
||||||
|
| 'already_exists'
|
||||||
|
| 'permission_denied'
|
||||||
|
| 'resource_exhausted'
|
||||||
|
| 'failed_precondition'
|
||||||
|
| 'aborted'
|
||||||
|
| 'out_of_range'
|
||||||
|
| 'unimplemented'
|
||||||
|
| 'internal_error'
|
||||||
|
| 'unavailable'
|
||||||
|
| 'data_loss'
|
||||||
|
| 'unauthenticated';
|
||||||
|
|
||||||
|
export interface SpanContext {
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
startTimestamp?: number;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
traceId?: string;
|
||||||
|
spanId?: string;
|
||||||
|
parentSpanId?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SamplingContext extends TransactionContext {
|
||||||
|
parentSampled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionContext extends SpanContext {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span JSON 输出类型
|
||||||
|
export interface SpanJSON {
|
||||||
|
trace_id: string;
|
||||||
|
span_id: string;
|
||||||
|
parent_span_id?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
start_timestamp: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction JSON 输出类型
|
||||||
|
export interface TransactionJSON extends SpanJSON {
|
||||||
|
transaction: string;
|
||||||
|
type?: string;
|
||||||
|
spans: SpanJSON[];
|
||||||
|
sampled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Span {
|
||||||
|
readonly traceId: string;
|
||||||
|
readonly spanId: string;
|
||||||
|
readonly parentSpanId?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags: Record<string, string>;
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
readonly startTimestamp: number;
|
||||||
|
readonly endTimestamp?: number;
|
||||||
|
readonly transaction?: Transaction;
|
||||||
|
|
||||||
|
setTag(key: string, value: string): this;
|
||||||
|
setData(key: string, value: unknown): this;
|
||||||
|
setStatus(status: SpanStatus): this;
|
||||||
|
setHttpStatus(statusCode: number): this;
|
||||||
|
setStatusFromResponse(status: string | number | undefined, tagKey?: string): this;
|
||||||
|
startChild(context?: SpanContext): Span;
|
||||||
|
finish(endTimestamp?: number): void;
|
||||||
|
toTraceparent(): string;
|
||||||
|
toJSON(): SpanJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Omit 排除 Span 中需要 override 的属性,Transaction 扩展其余属性
|
||||||
|
export interface Transaction extends Omit<Span, 'toJSON'> {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
spans: Span[];
|
||||||
|
sampled?: boolean;
|
||||||
|
maxSpans?: number;
|
||||||
|
|
||||||
|
toJSON(): TransactionJSON;
|
||||||
|
toEvent(): TransactionEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeasurementValue {
|
||||||
|
value: number;
|
||||||
|
unit?: string;
|
||||||
|
rating?: 'good' | 'needs-improvement' | 'poor';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionEvent extends BaseEvent {
|
||||||
|
type: 'transaction';
|
||||||
|
transaction: string;
|
||||||
|
transaction_info?: {
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
spans: Array<ReturnType<Span['toJSON']>>;
|
||||||
|
start_timestamp: number;
|
||||||
|
measurements?: Record<string, MeasurementValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SentryEvent = ErrorEvent | PerformanceEvent | NetworkEvent | BehaviorEvent | CountEvent | TransactionEvent;
|
||||||
|
|
||||||
export interface DSNInfo {
|
export interface DSNInfo {
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
|
@ -155,6 +296,13 @@ export interface LightConfig {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
sampleRate?: number;
|
sampleRate?: number;
|
||||||
|
tracesSampleRate?: number; // Trace 采样率 0~1,默认 0(关闭)
|
||||||
|
tracesSampler?: (context: SamplingContext) => number | boolean; // 动态采样函数
|
||||||
|
/**
|
||||||
|
* @deprecated 请使用 tracePropagationTargets 替代
|
||||||
|
*/
|
||||||
|
tracingOrigins?: (string | RegExp)[]; // 追踪域名白名单(已废弃)
|
||||||
|
tracePropagationTargets?: (string | RegExp)[]; // trace header 传播目标
|
||||||
maxQueueSize?: number;
|
maxQueueSize?: number;
|
||||||
flushInterval?: number;
|
flushInterval?: number;
|
||||||
maxRetries?: number;
|
maxRetries?: number;
|
||||||
|
|
@ -165,6 +313,7 @@ export interface LightConfig {
|
||||||
maxBreadcrumbs?: number;
|
maxBreadcrumbs?: number;
|
||||||
beforeSend?: (event: SentryEvent) => SentryEvent | null;
|
beforeSend?: (event: SentryEvent) => SentryEvent | null;
|
||||||
user?: UserInfo;
|
user?: UserInfo;
|
||||||
|
trackAnonymousUsers?: boolean; // 是否启用匿名用户追踪,默认 true
|
||||||
plugins?: (LightPlugin | string)[];
|
plugins?: (LightPlugin | string)[];
|
||||||
contextLevel?: Partial<ContextLevelMap>;
|
contextLevel?: Partial<ContextLevelMap>;
|
||||||
sampling?: SamplingConfig;
|
sampling?: SamplingConfig;
|
||||||
|
|
@ -190,10 +339,16 @@ export interface LightClient {
|
||||||
captureMessage(message: string, level?: EventLevel): void;
|
captureMessage(message: string, level?: EventLevel): void;
|
||||||
captureEvent(event: Partial<SentryEvent> & { type: string }): void;
|
captureEvent(event: Partial<SentryEvent> & { type: string }): void;
|
||||||
setUser(user: UserInfo | null): void;
|
setUser(user: UserInfo | null): void;
|
||||||
|
setAnonymousId(id: string): void; // 设置匿名用户ID
|
||||||
setTag(key: string, value: string): void;
|
setTag(key: string, value: string): void;
|
||||||
setTags(tags: Record<string, string>): void;
|
setTags(tags: Record<string, string>): void;
|
||||||
setExtra(key: string, value: unknown): void;
|
setExtra(key: string, value: unknown): void;
|
||||||
addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
||||||
|
startTransaction(context: TransactionContext): Transaction | null;
|
||||||
|
getCurrentTransaction(): Transaction | null;
|
||||||
|
getCurrentSpan(): Span | null;
|
||||||
|
setSpan(span: Span | null): void;
|
||||||
|
startSpan(context: SpanContext): Span | null;
|
||||||
flush(): Promise<void>;
|
flush(): Promise<void>;
|
||||||
disable(): void;
|
disable(): void;
|
||||||
enable(): void;
|
enable(): void;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import type { BaggageFields } from '../types';
|
||||||
|
|
||||||
|
const SENTRY_PREFIX = 'sentry-';
|
||||||
|
const MAX_BAGGAGE_LENGTH = 8192;
|
||||||
|
|
||||||
|
export function serializeBaggage(fields: BaggageFields): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
|
if (value === undefined || value === null || value === '') continue;
|
||||||
|
|
||||||
|
const prefixedKey = key.startsWith(SENTRY_PREFIX) ? key : `${SENTRY_PREFIX}${key}`;
|
||||||
|
parts.push(`${prefixedKey}=${encodeURIComponent(String(value))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = parts.join(',');
|
||||||
|
if (result.length > MAX_BAGGAGE_LENGTH) {
|
||||||
|
result = result.substring(0, MAX_BAGGAGE_LENGTH);
|
||||||
|
const lastComma = result.lastIndexOf(',');
|
||||||
|
if (lastComma > 0) {
|
||||||
|
result = result.substring(0, lastComma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseBaggage(header: string): BaggageFields {
|
||||||
|
const fields: BaggageFields = {};
|
||||||
|
|
||||||
|
if (!header) return fields;
|
||||||
|
|
||||||
|
const parts = header.split(',');
|
||||||
|
for (const part of parts) {
|
||||||
|
const eqIdx = part.indexOf('=');
|
||||||
|
if (eqIdx === -1) continue;
|
||||||
|
|
||||||
|
const rawKey = part.substring(0, eqIdx).trim();
|
||||||
|
const value = decodeURIComponent(part.substring(eqIdx + 1).trim());
|
||||||
|
|
||||||
|
let key = rawKey;
|
||||||
|
if (rawKey.startsWith(SENTRY_PREFIX)) {
|
||||||
|
key = rawKey.substring(SENTRY_PREFIX.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
fields[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBaggageFromTransaction(
|
||||||
|
traceId: string,
|
||||||
|
options?: {
|
||||||
|
dsnPublicKey?: string;
|
||||||
|
sampled?: boolean;
|
||||||
|
sampleRate?: number;
|
||||||
|
user?: { id?: string; segment?: string };
|
||||||
|
release?: string;
|
||||||
|
environment?: string;
|
||||||
|
}
|
||||||
|
): BaggageFields {
|
||||||
|
const fields: BaggageFields = {
|
||||||
|
trace_id: traceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options?.dsnPublicKey) {
|
||||||
|
fields.public_key = options.dsnPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.sampled !== undefined) {
|
||||||
|
fields.sampled = options.sampled ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.sampleRate !== undefined) {
|
||||||
|
fields.sample_rate = String(options.sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.user) {
|
||||||
|
if (options.user.id) {
|
||||||
|
fields.user_id = options.user.id;
|
||||||
|
}
|
||||||
|
if (options.user.segment) {
|
||||||
|
fields.user_segment = options.user.segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.release) {
|
||||||
|
fields.release = options.release;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.environment) {
|
||||||
|
fields.environment = options.environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
@ -163,6 +163,11 @@ export function getReferrer(): string {
|
||||||
export function sanitizeUrl(url: string): string {
|
export function sanitizeUrl(url: string): string {
|
||||||
if (!url) return url;
|
if (!url) return url;
|
||||||
|
|
||||||
|
// 过滤 javascript: 伪协议防止 XSS
|
||||||
|
if (/^\s*javascript:/i.test(url)) {
|
||||||
|
return 'javascript:[filtered]';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
const sensitiveParams = [
|
const sensitiveParams = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { uuid } from './helper';
|
||||||
|
|
||||||
|
const ANONYMOUS_ID_KEY = 'light_sentry_aid';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取匿名用户ID
|
||||||
|
* 优先从 localStorage 获取已存在的ID,不存在则生成并存储
|
||||||
|
*/
|
||||||
|
export function getAnonymousId(): string | null {
|
||||||
|
// 浏览器环境检查
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);
|
||||||
|
|
||||||
|
if (!anonymousId) {
|
||||||
|
anonymousId = uuid();
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return anonymousId;
|
||||||
|
} catch {
|
||||||
|
// localStorage 不可用(如隐私模式、跨域限制等)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置匿名用户ID
|
||||||
|
* 用于覆盖自动生成的ID
|
||||||
|
*/
|
||||||
|
export function setAnonymousId(id: string): boolean {
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, id);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除匿名用户ID
|
||||||
|
* 通常在用户登出后调用
|
||||||
|
*/
|
||||||
|
export function clearAnonymousId(): boolean {
|
||||||
|
if (typeof localStorage === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(ANONYMOUS_ID_KEY);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否启用了匿名用户追踪
|
||||||
|
*/
|
||||||
|
export function isAnonymousTrackingEnabled(config: { trackAnonymousUsers?: boolean }): boolean {
|
||||||
|
// 默认为 true,除非明确设置为 false
|
||||||
|
return config.trackAnonymousUsers !== false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,356 @@
|
||||||
|
import type { Span as SpanInterface, SpanContext, Transaction as TransactionInterface, TransactionContext, SpanStatus, LightClient, BaggageFields } from '../types';
|
||||||
|
import { uuid, now } from './helper';
|
||||||
|
import { createBaggageFromTransaction, serializeBaggage } from './baggage';
|
||||||
|
|
||||||
|
function generateTraceId(): string {
|
||||||
|
return uuid().replace(/-/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSpanId(): string {
|
||||||
|
return uuid().replace(/-/g, '').substring(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function httpStatusToSpanStatus(statusCode: number): SpanStatus {
|
||||||
|
if (statusCode >= 200 && statusCode < 300) return 'ok';
|
||||||
|
if (statusCode === 400) return 'invalid_argument';
|
||||||
|
if (statusCode === 401) return 'unauthenticated';
|
||||||
|
if (statusCode === 403) return 'permission_denied';
|
||||||
|
if (statusCode === 404) return 'not_found';
|
||||||
|
if (statusCode === 409) return 'already_exists';
|
||||||
|
if (statusCode === 429) return 'resource_exhausted';
|
||||||
|
if (statusCode >= 400 && statusCode < 500) return 'invalid_argument';
|
||||||
|
if (statusCode >= 500 && statusCode < 600) return 'internal_error';
|
||||||
|
return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dbStatusToSpanStatus(dbStatusCode?: string | number): SpanStatus {
|
||||||
|
if (dbStatusCode === undefined || dbStatusCode === null || dbStatusCode === '') {
|
||||||
|
return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = typeof dbStatusCode === 'string' ? parseInt(dbStatusCode, 10) : dbStatusCode;
|
||||||
|
|
||||||
|
if (code === 0 || code === 1) return 'ok';
|
||||||
|
if (code >= 10 && code < 20) return 'invalid_argument';
|
||||||
|
if (code >= 20 && code < 30) return 'not_found';
|
||||||
|
if (code >= 30 && code < 40) return 'already_exists';
|
||||||
|
if (code >= 50) return 'internal_error';
|
||||||
|
|
||||||
|
return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statusToSpanStatus(
|
||||||
|
status: string | number | undefined,
|
||||||
|
op: string | undefined
|
||||||
|
): SpanStatus {
|
||||||
|
if (status === undefined) return 'unknown_error';
|
||||||
|
|
||||||
|
if (typeof status === 'string') {
|
||||||
|
const lowerStatus = status.toLowerCase();
|
||||||
|
if (lowerStatus === 'ok' || lowerStatus === 'success') return 'ok';
|
||||||
|
if (lowerStatus === 'cancelled' || lowerStatus === 'canceled') return 'cancelled';
|
||||||
|
if (lowerStatus === 'deadline_exceeded') return 'deadline_exceeded';
|
||||||
|
if (lowerStatus === 'not_found') return 'not_found';
|
||||||
|
if (lowerStatus === 'already_exists') return 'already_exists';
|
||||||
|
if (lowerStatus === 'permission_denied') return 'permission_denied';
|
||||||
|
if (lowerStatus === 'resource_exhausted') return 'resource_exhausted';
|
||||||
|
if (lowerStatus === 'failed_precondition') return 'failed_precondition';
|
||||||
|
if (lowerStatus === 'invalid_argument') return 'invalid_argument';
|
||||||
|
if (lowerStatus === 'unauthenticated') return 'unauthenticated';
|
||||||
|
if (lowerStatus === 'internal_error') return 'internal_error';
|
||||||
|
|
||||||
|
const parsed = parseInt(status, 10);
|
||||||
|
if (!isNaN(parsed)) {
|
||||||
|
return statusToSpanStatus(parsed, op);
|
||||||
|
}
|
||||||
|
return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof status === 'number') {
|
||||||
|
if (status >= 200 && status < 600) {
|
||||||
|
return httpStatusToSpanStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op?.startsWith('db')) {
|
||||||
|
return dbStatusToSpanStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 0) return 'ok';
|
||||||
|
if (status > 0) return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Span implements SpanInterface {
|
||||||
|
readonly traceId: string;
|
||||||
|
readonly spanId: string;
|
||||||
|
readonly parentSpanId?: string;
|
||||||
|
op?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: SpanStatus;
|
||||||
|
tags: Record<string, string> = {};
|
||||||
|
data: Record<string, unknown> = {};
|
||||||
|
readonly startTimestamp: number;
|
||||||
|
endTimestamp?: number;
|
||||||
|
transaction?: TransactionInterface;
|
||||||
|
|
||||||
|
protected _isFinished = false;
|
||||||
|
protected _client?: LightClient;
|
||||||
|
|
||||||
|
constructor(context: SpanContext, transaction?: TransactionInterface, client?: LightClient) {
|
||||||
|
this.traceId = context.traceId || generateTraceId();
|
||||||
|
this.spanId = context.spanId || generateSpanId();
|
||||||
|
this.parentSpanId = context.parentSpanId;
|
||||||
|
this.op = context.op;
|
||||||
|
this.description = context.description;
|
||||||
|
this.status = context.status;
|
||||||
|
this.tags = { ...context.tags };
|
||||||
|
this.data = { ...context.data };
|
||||||
|
this.startTimestamp = context.startTimestamp || now();
|
||||||
|
this.transaction = transaction;
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTag(key: string, value: string): this {
|
||||||
|
this.tags[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(key: string, value: unknown): this {
|
||||||
|
this.data[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(status: SpanStatus): this {
|
||||||
|
this.status = status;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHttpStatus(statusCode: number): this {
|
||||||
|
this.setStatus(httpStatusToSpanStatus(statusCode));
|
||||||
|
this.setTag('http.status_code', String(statusCode));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatusFromResponse(status: string | number | undefined, tagKey?: string): this {
|
||||||
|
const spanStatus = statusToSpanStatus(status, this.op);
|
||||||
|
this.setStatus(spanStatus);
|
||||||
|
if (tagKey && status !== undefined) {
|
||||||
|
this.setTag(tagKey, String(status));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
startChild(context: SpanContext = {}): Span {
|
||||||
|
const tx = this.transaction;
|
||||||
|
const isSampled = tx?.sampled !== false;
|
||||||
|
|
||||||
|
const child = new Span(
|
||||||
|
{
|
||||||
|
...context,
|
||||||
|
traceId: this.traceId,
|
||||||
|
parentSpanId: this.spanId,
|
||||||
|
},
|
||||||
|
tx,
|
||||||
|
this._client
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tx && isSampled) {
|
||||||
|
(tx as Transaction)._addSpan(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(endTimestamp?: number): void {
|
||||||
|
if (this._isFinished) return;
|
||||||
|
this._isFinished = true;
|
||||||
|
this.endTimestamp = endTimestamp || now();
|
||||||
|
|
||||||
|
if (this._client) {
|
||||||
|
const clientInternal = this._client as unknown as { tracingManager?: { onSpanFinished: (span: Span) => void } };
|
||||||
|
if (clientInternal.tracingManager?.onSpanFinished) {
|
||||||
|
clientInternal.tracingManager.onSpanFinished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toTraceparent(): string {
|
||||||
|
const sampled = this.transaction?.sampled;
|
||||||
|
const sampledFlag = sampled === undefined ? '' : sampled ? '1' : '0';
|
||||||
|
return sampledFlag
|
||||||
|
? `${this.traceId}-${this.spanId}-${sampledFlag}`
|
||||||
|
: `${this.traceId}-${this.spanId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
trace_id: this.traceId,
|
||||||
|
span_id: this.spanId,
|
||||||
|
parent_span_id: this.parentSpanId,
|
||||||
|
op: this.op,
|
||||||
|
description: this.description,
|
||||||
|
status: this.status,
|
||||||
|
tags: Object.keys(this.tags).length > 0 ? { ...this.tags } : undefined,
|
||||||
|
data: Object.keys(this.data).length > 0 ? { ...this.data } : undefined,
|
||||||
|
start_timestamp: this.startTimestamp,
|
||||||
|
timestamp: this.endTimestamp || this.startTimestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Transaction extends Span implements TransactionInterface {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
spans: Span[] = [];
|
||||||
|
sampled?: boolean;
|
||||||
|
maxSpans: number = 1000;
|
||||||
|
private _baggage?: BaggageFields;
|
||||||
|
private _dsnPublicKey?: string;
|
||||||
|
private _release?: string;
|
||||||
|
private _environment?: string;
|
||||||
|
private _droppedSpans: number = 0;
|
||||||
|
|
||||||
|
constructor(context: TransactionContext, client?: LightClient, dsnPublicKey?: string, release?: string, environment?: string, maxSpans?: number) {
|
||||||
|
super(context, undefined, client);
|
||||||
|
this.name = context.name;
|
||||||
|
this.type = context.type;
|
||||||
|
this.sampled = context.sampled;
|
||||||
|
this.transaction = this;
|
||||||
|
this._dsnPublicKey = dsnPublicKey;
|
||||||
|
this._release = release;
|
||||||
|
this._environment = environment;
|
||||||
|
if (maxSpans !== undefined) {
|
||||||
|
this.maxSpans = maxSpans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addSpan(span: Span): void {
|
||||||
|
if (this.spans.length >= this.maxSpans) {
|
||||||
|
this._droppedSpans++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.spans.push(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ensureBaggage(): BaggageFields {
|
||||||
|
if (!this._baggage) {
|
||||||
|
this._baggage = createBaggageFromTransaction(
|
||||||
|
this.traceId,
|
||||||
|
{
|
||||||
|
dsnPublicKey: this._dsnPublicKey,
|
||||||
|
sampled: this.sampled,
|
||||||
|
release: this._release,
|
||||||
|
environment: this._environment,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this._baggage;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBaggage(key: string, value: string): this {
|
||||||
|
const baggage = this._ensureBaggage();
|
||||||
|
baggage[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserBaggage(userId?: string, segment?: string): this {
|
||||||
|
const baggage = this._ensureBaggage();
|
||||||
|
if (userId !== undefined) {
|
||||||
|
baggage.user_id = userId;
|
||||||
|
} else {
|
||||||
|
delete baggage.user_id;
|
||||||
|
}
|
||||||
|
if (segment !== undefined) {
|
||||||
|
baggage.user_segment = segment;
|
||||||
|
} else {
|
||||||
|
delete baggage.user_segment;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toBaggage(): string | undefined {
|
||||||
|
const baggage = this._ensureBaggage();
|
||||||
|
return serializeBaggage(baggage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaggage(): BaggageFields | undefined {
|
||||||
|
return this._baggage;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(endTimestamp?: number): void {
|
||||||
|
if (this._isFinished) return;
|
||||||
|
super.finish(endTimestamp);
|
||||||
|
|
||||||
|
if (this._client && this.sampled) {
|
||||||
|
const event = this.toEvent();
|
||||||
|
this._client.captureEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._client) {
|
||||||
|
const clientInternal = this._client as unknown as { tracingManager?: { onTransactionFinished: (tx: Transaction) => void } };
|
||||||
|
if (clientInternal.tracingManager?.onTransactionFinished) {
|
||||||
|
clientInternal.tracingManager.onTransactionFinished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSource(): string {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'pageload':
|
||||||
|
return 'url';
|
||||||
|
case 'navigation':
|
||||||
|
return 'route';
|
||||||
|
case 'user-interaction':
|
||||||
|
return 'ui';
|
||||||
|
default:
|
||||||
|
return 'custom';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toEvent() {
|
||||||
|
const json = this.toJSON();
|
||||||
|
const data = { ...json.data } as Record<string, unknown>;
|
||||||
|
const measurements = data.measurements as Record<string, { value: number; unit?: string; rating?: 'good' | 'needs-improvement' | 'poor' }> | undefined;
|
||||||
|
delete data.measurements;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'transaction' as const,
|
||||||
|
transaction: this.name,
|
||||||
|
transaction_info: {
|
||||||
|
source: this._getSource(),
|
||||||
|
},
|
||||||
|
level: 'info' as const,
|
||||||
|
timestamp: json.timestamp,
|
||||||
|
start_timestamp: json.start_timestamp,
|
||||||
|
spans: this.spans.map((s) => s.toJSON()),
|
||||||
|
contexts: {
|
||||||
|
trace: {
|
||||||
|
trace_id: this.traceId,
|
||||||
|
span_id: this.spanId,
|
||||||
|
parent_span_id: this.parentSpanId,
|
||||||
|
op: this.op,
|
||||||
|
status: this.status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: json.tags,
|
||||||
|
extra: Object.keys(data).length > 0 ? data : undefined,
|
||||||
|
measurements,
|
||||||
|
platform: 'javascript',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
const base = super.toJSON();
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
transaction: this.name,
|
||||||
|
type: this.type,
|
||||||
|
spans: this.spans.map((s) => s.toJSON()),
|
||||||
|
sampled: this.sampled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { generateTraceId, generateSpanId };
|
||||||
|
|
@ -245,4 +245,256 @@ describe('ConfigManager', () => {
|
||||||
expect(result.context_id).toBe('test_context_id');
|
expect(result.context_id).toBe('test_context_id');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('anonymousId', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add anonymousId when user.id is not set and tracking is enabled', () => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
trackAnonymousUsers: true,
|
||||||
|
});
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as any).anonymousId).toBeTruthy();
|
||||||
|
expect(typeof (result as any).anonymousId).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add anonymousId when user.id is set', () => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
user: { id: 'user-123' },
|
||||||
|
trackAnonymousUsers: true,
|
||||||
|
});
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as any).anonymousId).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add anonymousId when trackAnonymousUsers is false', () => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
trackAnonymousUsers: false,
|
||||||
|
});
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as any).anonymousId).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add anonymousId when event has user.id', () => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
trackAnonymousUsers: true,
|
||||||
|
});
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
user: { id: 'event-user-id' },
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as any).anonymousId).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('contextLevel', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
contextLevel: {
|
||||||
|
error: { maxStackFrames: 5, maxBreadcrumbs: 10 },
|
||||||
|
info: { maxStackFrames: 0, maxBreadcrumbs: 0 },
|
||||||
|
warning: { maxStackFrames: 3, maxBreadcrumbs: 5 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should truncate stack frames for error level', () => {
|
||||||
|
const frames = Array.from({ length: 10 }, (_, i) => ({
|
||||||
|
filename: `file${i}.js`,
|
||||||
|
function: `fn${i}`,
|
||||||
|
lineno: i * 10,
|
||||||
|
colno: i,
|
||||||
|
}));
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
exception: {
|
||||||
|
type: 'Error',
|
||||||
|
value: 'test',
|
||||||
|
stacktrace: { frames },
|
||||||
|
},
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as ErrorEvent).exception?.stacktrace?.frames).toHaveLength(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove exception entirely for info level (maxStackFrames=0)', () => {
|
||||||
|
const frames = [{ filename: 'file.js', function: 'fn', lineno: 1, colno: 1 }];
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'info',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
exception: {
|
||||||
|
type: 'Error',
|
||||||
|
value: 'test',
|
||||||
|
stacktrace: { frames },
|
||||||
|
},
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect((result as ErrorEvent).exception).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should truncate breadcrumbs for error level', () => {
|
||||||
|
const breadcrumbs = Array.from({ length: 20 }, (_, i) => ({
|
||||||
|
type: 'default',
|
||||||
|
message: `breadcrumb ${i}`,
|
||||||
|
timestamp: Date.now() + i,
|
||||||
|
}));
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
breadcrumbs,
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect(result.breadcrumbs).toHaveLength(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove breadcrumbs for info level (maxBreadcrumbs=0)', () => {
|
||||||
|
const breadcrumbs = [{ type: 'default', message: 'test', timestamp: Date.now() }];
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'info',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
breadcrumbs,
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect(result.breadcrumbs).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep last N breadcrumbs', () => {
|
||||||
|
const breadcrumbs = Array.from({ length: 20 }, (_, i) => ({
|
||||||
|
type: 'default',
|
||||||
|
message: `breadcrumb ${i}`,
|
||||||
|
timestamp: Date.now() + i,
|
||||||
|
}));
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'warning',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
breadcrumbs,
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect(result.breadcrumbs).toHaveLength(5);
|
||||||
|
// Should keep last 5
|
||||||
|
expect(result.breadcrumbs?.[0].message).toBe('breadcrumb 15');
|
||||||
|
expect(result.breadcrumbs?.[4].message).toBe('breadcrumb 19');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify original event', () => {
|
||||||
|
const frames = Array.from({ length: 10 }, (_, i) => ({
|
||||||
|
filename: `file${i}.js`,
|
||||||
|
function: `fn${i}`,
|
||||||
|
lineno: i * 10,
|
||||||
|
colno: i,
|
||||||
|
}));
|
||||||
|
const event: ErrorEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
message: 'test',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
exception: {
|
||||||
|
type: 'Error',
|
||||||
|
value: 'test',
|
||||||
|
stacktrace: { frames },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
configManager.applyToEvent(event);
|
||||||
|
// Original event should not be modified
|
||||||
|
expect(event.exception?.stacktrace?.frames).toHaveLength(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle event without exception', () => {
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle event without breadcrumbs', () => {
|
||||||
|
const event: SentryEvent = {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as ErrorEvent;
|
||||||
|
const result = configManager.applyToEvent(event);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setUser', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
user: { id: 'existing-user' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set user info', () => {
|
||||||
|
configManager.setUser({ id: 'new-user', username: 'test' });
|
||||||
|
expect(configManager.get('user')).toEqual({ id: 'new-user', username: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear user info when null is passed', () => {
|
||||||
|
configManager.setUser(null);
|
||||||
|
expect(configManager.get('user')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setTag / setTags / setExtra', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configManager = new ConfigManager({
|
||||||
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set tag when no tags exist', () => {
|
||||||
|
configManager.setTag('key1', 'value1');
|
||||||
|
expect(configManager.get('tags')).toEqual({ key1: 'value1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge tags', () => {
|
||||||
|
configManager.setTag('key1', 'value1');
|
||||||
|
configManager.setTags({ key2: 'value2', key3: 'value3' });
|
||||||
|
expect(configManager.get('tags')).toEqual({
|
||||||
|
key1: 'value1',
|
||||||
|
key2: 'value2',
|
||||||
|
key3: 'value3',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set extra when no extra exists', () => {
|
||||||
|
configManager.setExtra('key1', 'value1');
|
||||||
|
expect(configManager.get('extra')).toEqual({ key1: 'value1' });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { EventBus } from '../src/core/EventBus';
|
||||||
|
|
||||||
|
describe('EventBus', () => {
|
||||||
|
let consoleSpy: ReturnType<typeof vi.spyOn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('on / emit', () => {
|
||||||
|
it('should register a handler and call it on emit', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.on('test-event', handler);
|
||||||
|
bus.emit('test-event', 'data1', 'data2');
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handler).toHaveBeenCalledWith('data1', 'data2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call multiple handlers on same event', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler1 = vi.fn();
|
||||||
|
const handler2 = vi.fn();
|
||||||
|
bus.on('test-event', handler1);
|
||||||
|
bus.on('test-event', handler2);
|
||||||
|
bus.emit('test-event');
|
||||||
|
expect(handler1).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handler2).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call handlers for different events', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.on('event-a', handler);
|
||||||
|
bus.emit('event-b');
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle emit on non-existent event', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
expect(() => bus.emit('non-existent', 'data')).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass multiple arguments to handler', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.on('multi-arg', handler);
|
||||||
|
bus.emit('multi-arg', 1, 'two', { three: 3 }, [4, 5]);
|
||||||
|
expect(handler).toHaveBeenCalledWith(1, 'two', { three: 3 }, [4, 5]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if a handler throws', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler1 = vi.fn(() => {
|
||||||
|
throw new Error('Handler error');
|
||||||
|
});
|
||||||
|
const handler2 = vi.fn();
|
||||||
|
bus.on('error-event', handler1);
|
||||||
|
bus.on('error-event', handler2);
|
||||||
|
expect(() => bus.emit('error-event')).not.toThrow();
|
||||||
|
expect(handler2).toHaveBeenCalledTimes(1);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('off', () => {
|
||||||
|
it('should remove a registered handler', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.on('test-event', handler);
|
||||||
|
bus.off('test-event', handler);
|
||||||
|
bus.emit('test-event');
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not remove other handlers', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler1 = vi.fn();
|
||||||
|
const handler2 = vi.fn();
|
||||||
|
bus.on('test-event', handler1);
|
||||||
|
bus.on('test-event', handler2);
|
||||||
|
bus.off('test-event', handler1);
|
||||||
|
bus.emit('test-event');
|
||||||
|
expect(handler1).not.toHaveBeenCalled();
|
||||||
|
expect(handler2).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle off for non-existent event', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
expect(() => bus.off('non-existent', handler)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle off for non-existent handler', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler1 = vi.fn();
|
||||||
|
const handler2 = vi.fn();
|
||||||
|
bus.on('test-event', handler1);
|
||||||
|
bus.off('test-event', handler2);
|
||||||
|
bus.emit('test-event');
|
||||||
|
expect(handler1).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only remove one instance if handler is registered multiple times', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.on('test-event', handler);
|
||||||
|
bus.on('test-event', handler);
|
||||||
|
bus.off('test-event', handler);
|
||||||
|
bus.emit('test-event');
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('once', () => {
|
||||||
|
it('should call handler only once', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.once('once-event', handler);
|
||||||
|
bus.emit('once-event');
|
||||||
|
bus.emit('once-event');
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass arguments to once handler', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.once('once-event', handler);
|
||||||
|
bus.emit('once-event', 'arg1', 'arg2');
|
||||||
|
expect(handler).toHaveBeenCalledWith('arg1', 'arg2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with on and once mixed', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const onHandler = vi.fn();
|
||||||
|
const onceHandler = vi.fn();
|
||||||
|
bus.on('mixed-event', onHandler);
|
||||||
|
bus.once('mixed-event', onceHandler);
|
||||||
|
bus.emit('mixed-event');
|
||||||
|
bus.emit('mixed-event');
|
||||||
|
expect(onHandler).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onceHandler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if once handler throws', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn(() => {
|
||||||
|
throw new Error('Once handler error');
|
||||||
|
});
|
||||||
|
bus.once('once-error', handler);
|
||||||
|
expect(() => bus.emit('once-error')).not.toThrow();
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should clear all handlers', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler1 = vi.fn();
|
||||||
|
const handler2 = vi.fn();
|
||||||
|
bus.on('event-a', handler1);
|
||||||
|
bus.on('event-b', handler2);
|
||||||
|
bus.destroy();
|
||||||
|
bus.emit('event-a');
|
||||||
|
bus.emit('event-b');
|
||||||
|
expect(handler1).not.toHaveBeenCalled();
|
||||||
|
expect(handler2).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle destroy on empty bus', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
expect(() => bus.destroy()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with once handlers after destroy', () => {
|
||||||
|
const bus = new EventBus();
|
||||||
|
const handler = vi.fn();
|
||||||
|
bus.once('event', handler);
|
||||||
|
bus.destroy();
|
||||||
|
bus.emit('event');
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { EventQueue } from '../src/core/EventQueue';
|
||||||
|
import { EventBus } from '../src/core/EventBus';
|
||||||
|
import type { SentryEvent, ErrorEvent } from '../src/types';
|
||||||
|
|
||||||
|
describe('EventQueue', () => {
|
||||||
|
let eventQueue: EventQueue;
|
||||||
|
let eventBus: EventBus;
|
||||||
|
let flushCallback: ReturnType<typeof vi.fn>;
|
||||||
|
let syncFlushCallback: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
eventBus = new EventBus();
|
||||||
|
flushCallback = vi.fn().mockResolvedValue(undefined);
|
||||||
|
syncFlushCallback = vi.fn();
|
||||||
|
eventQueue = new EventQueue(100, 5000, eventBus, flushCallback, syncFlushCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
eventQueue.destroy();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('enqueue', () => {
|
||||||
|
it('should add event to queue', () => {
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit event when enqueueing', () => {
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
const emitSpy = vi.spyOn(eventBus, 'emit');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith('event', event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger flush when queue is full', () => {
|
||||||
|
const eventQueueSmall = new EventQueue(2, 5000, eventBus, flushCallback, syncFlushCallback);
|
||||||
|
eventQueueSmall.enqueue(createErrorEvent('error 1'));
|
||||||
|
eventQueueSmall.enqueue(createErrorEvent('error 2'));
|
||||||
|
// flushCallback should be called due to maxSize reached
|
||||||
|
expect(flushCallback).toHaveBeenCalled();
|
||||||
|
eventQueueSmall.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip deduplicated events', () => {
|
||||||
|
const event1 = createErrorEvent('Duplicate error', 'fingerprint1');
|
||||||
|
const event2 = createErrorEvent('Duplicate error', 'fingerprint1');
|
||||||
|
const event3 = createErrorEvent('Duplicate error', 'fingerprint1');
|
||||||
|
const event4 = createErrorEvent('Duplicate error', 'fingerprint1'); // count >= 3, should be deduped
|
||||||
|
|
||||||
|
eventQueue.enqueue(event1);
|
||||||
|
eventQueue.enqueue(event2);
|
||||||
|
eventQueue.enqueue(event3);
|
||||||
|
eventQueue.enqueue(event4);
|
||||||
|
|
||||||
|
// First 3 events are allowed, 4th is deduped
|
||||||
|
expect(eventQueue.size()).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not skip non-duplicate events', () => {
|
||||||
|
const event1 = createErrorEvent('Error 1', 'fp1');
|
||||||
|
const event2 = createErrorEvent('Error 2', 'fp2');
|
||||||
|
eventQueue.enqueue(event1);
|
||||||
|
eventQueue.enqueue(event2);
|
||||||
|
expect(eventQueue.size()).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('flush', () => {
|
||||||
|
it('should call flush callback with events', async () => {
|
||||||
|
const event1 = createErrorEvent('Error 1');
|
||||||
|
const event2 = createErrorEvent('Error 2');
|
||||||
|
eventQueue.enqueue(event1);
|
||||||
|
eventQueue.enqueue(event2);
|
||||||
|
|
||||||
|
await eventQueue.flush();
|
||||||
|
|
||||||
|
expect(flushCallback).toHaveBeenCalledWith(expect.arrayContaining([
|
||||||
|
expect.objectContaining({ message: 'Error 1' }),
|
||||||
|
expect.objectContaining({ message: 'Error 2' }),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not flush when queue is empty', async () => {
|
||||||
|
await eventQueue.flush();
|
||||||
|
expect(flushCallback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear queue after successful flush', async () => {
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
|
||||||
|
await eventQueue.flush();
|
||||||
|
expect(eventQueue.size()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return events to queue on flush failure', async () => {
|
||||||
|
flushCallback.mockRejectedValueOnce(new Error('Network error'));
|
||||||
|
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
|
||||||
|
await expect(eventQueue.flush()).rejects.toThrow('Network error');
|
||||||
|
// Events should be returned to queue
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent concurrent flush reentry', async () => {
|
||||||
|
flushCallback.mockImplementation(async () => {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
});
|
||||||
|
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
|
||||||
|
// Start first flush
|
||||||
|
const flushPromise = eventQueue.flush();
|
||||||
|
// Try second flush immediately
|
||||||
|
await eventQueue.flush();
|
||||||
|
|
||||||
|
await flushPromise;
|
||||||
|
// flushCallback should only be called once
|
||||||
|
expect(flushCallback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('flushSync', () => {
|
||||||
|
it('should call sync flush callback', () => {
|
||||||
|
const event = createErrorEvent('Sync error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
|
||||||
|
eventQueue.flushSync();
|
||||||
|
|
||||||
|
expect(syncFlushCallback).toHaveBeenCalledWith(expect.arrayContaining([
|
||||||
|
expect.objectContaining({ message: 'Sync error' }),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return events to queue on sync flush failure', () => {
|
||||||
|
syncFlushCallback.mockImplementationOnce(() => {
|
||||||
|
throw new Error('Sync error');
|
||||||
|
});
|
||||||
|
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
|
||||||
|
eventQueue.flushSync();
|
||||||
|
|
||||||
|
// Events should be returned to queue
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should clear all data', () => {
|
||||||
|
const event = createErrorEvent('Test error');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
|
||||||
|
eventQueue.destroy();
|
||||||
|
|
||||||
|
expect(eventQueue.size()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deduplication', () => {
|
||||||
|
it('should generate dedup key from fingerprint first', () => {
|
||||||
|
const event = createErrorEvent('Test error', 'fp1');
|
||||||
|
eventQueue.enqueue(event);
|
||||||
|
expect(eventQueue.size()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate dedup key from message if no fingerprint', () => {
|
||||||
|
const event1 = createErrorEvent('Same message');
|
||||||
|
const event2 = createErrorEvent('Same message');
|
||||||
|
const event3 = createErrorEvent('Same message');
|
||||||
|
const event4 = createErrorEvent('Same message');
|
||||||
|
|
||||||
|
eventQueue.enqueue(event1);
|
||||||
|
eventQueue.enqueue(event2);
|
||||||
|
eventQueue.enqueue(event3);
|
||||||
|
eventQueue.enqueue(event4);
|
||||||
|
|
||||||
|
expect(eventQueue.size()).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset dedup entry after expiry', async () => {
|
||||||
|
const event1 = createErrorEvent('Test', 'fp-expire');
|
||||||
|
const event2 = createErrorEvent('Test', 'fp-expire');
|
||||||
|
const event3 = createErrorEvent('Test', 'fp-expire');
|
||||||
|
|
||||||
|
eventQueue.enqueue(event1);
|
||||||
|
eventQueue.enqueue(event2);
|
||||||
|
eventQueue.enqueue(event3);
|
||||||
|
expect(eventQueue.size()).toBe(3);
|
||||||
|
|
||||||
|
// 4th event should be deduped
|
||||||
|
const event4 = createErrorEvent('Test', 'fp-expire');
|
||||||
|
eventQueue.enqueue(event4);
|
||||||
|
expect(eventQueue.size()).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('count events', () => {
|
||||||
|
it('should build count events for deduped errors', async () => {
|
||||||
|
// Enqueue 3 events to reach threshold
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error', 'count-fp'));
|
||||||
|
}
|
||||||
|
// 4th event is deduped and goes to pendingCounts
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error', 'count-fp'));
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error', 'count-fp'));
|
||||||
|
|
||||||
|
expect(eventQueue.size()).toBe(3);
|
||||||
|
|
||||||
|
// Flush should include count events
|
||||||
|
await eventQueue.flush();
|
||||||
|
|
||||||
|
expect(flushCallback).toHaveBeenCalledWith(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ type: 'count', fingerprint: 'count-fp', count: 2 }),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should flush pending counts even when regular queue is empty', async () => {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error', 'count-only-fp'));
|
||||||
|
}
|
||||||
|
// Add some deduped events (keep below infinite loop threshold of 10)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error', 'count-only-fp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush first to clear regular queue
|
||||||
|
await eventQueue.flush();
|
||||||
|
flushCallback.mockClear();
|
||||||
|
|
||||||
|
// Enqueue more deduped events
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error 2', 'count-only-fp-2'));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Dedup error 2', 'count-only-fp-2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush again - should have count events even with empty regular queue
|
||||||
|
await eventQueue.flush();
|
||||||
|
|
||||||
|
const lastCall = flushCallback.mock.calls[flushCallback.mock.calls.length - 1];
|
||||||
|
const countEvents = lastCall[0].filter((e: any) => e.type === 'count');
|
||||||
|
expect(countEvents.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('infinite loop detection', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pause queue when too many errors in 1 second', () => {
|
||||||
|
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
|
||||||
|
for (let i = 0; i < 11; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Infinite loop test', 'infinite-fp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have paused
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
// After pause, no more events should be added
|
||||||
|
const sizeAfterLoop = eventQueue.size();
|
||||||
|
eventQueue.enqueue(createErrorEvent('After pause', 'infinite-fp'));
|
||||||
|
expect(eventQueue.size()).toBe(sizeAfterLoop);
|
||||||
|
|
||||||
|
consoleWarnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit error event on infinite loop detection', () => {
|
||||||
|
const emitSpy = vi.spyOn(eventBus, 'emit');
|
||||||
|
|
||||||
|
for (let i = 0; i < 11; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Infinite loop', 'loop-fp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith('error', expect.any(Error));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resume after pause duration', () => {
|
||||||
|
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
const consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
|
||||||
|
|
||||||
|
for (let i = 0; i < 11; i++) {
|
||||||
|
eventQueue.enqueue(createErrorEvent('Resume test', 'resume-fp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeBeforeResume = eventQueue.size();
|
||||||
|
|
||||||
|
// Advance time past the 60s pause
|
||||||
|
vi.advanceTimersByTime(61000);
|
||||||
|
|
||||||
|
// Should resume accepting events
|
||||||
|
eventQueue.enqueue(createErrorEvent('After resume', 'different-fp'));
|
||||||
|
expect(eventQueue.size()).toBe(sizeBeforeResume + 1);
|
||||||
|
|
||||||
|
consoleWarnSpy.mockRestore();
|
||||||
|
consoleInfoSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('non-error events', () => {
|
||||||
|
it('should enqueue non-error events without dedup', () => {
|
||||||
|
const transactionEvent: SentryEvent = {
|
||||||
|
type: 'transaction',
|
||||||
|
level: 'info',
|
||||||
|
transaction: 'test-tx',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
eventQueue.enqueue(transactionEvent);
|
||||||
|
eventQueue.enqueue(transactionEvent);
|
||||||
|
expect(eventQueue.size()).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('start/stop timer', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start timer and flush on interval when queue has events', async () => {
|
||||||
|
const eq = new EventQueue(100, 1000, eventBus, flushCallback, syncFlushCallback);
|
||||||
|
eq.start();
|
||||||
|
|
||||||
|
eq.enqueue(createErrorEvent('Interval test'));
|
||||||
|
|
||||||
|
// flushCallback should not be called yet
|
||||||
|
expect(flushCallback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Advance timer past interval
|
||||||
|
vi.advanceTimersByTime(1500);
|
||||||
|
|
||||||
|
// Wait for the flush promise to resolve
|
||||||
|
await vi.runOnlyPendingTimersAsync();
|
||||||
|
|
||||||
|
expect(flushCallback).toHaveBeenCalled();
|
||||||
|
|
||||||
|
eq.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not flush on interval when queue is empty', async () => {
|
||||||
|
const eq = new EventQueue(100, 1000, eventBus, flushCallback, syncFlushCallback);
|
||||||
|
eq.start();
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1500);
|
||||||
|
await vi.runOnlyPendingTimersAsync();
|
||||||
|
|
||||||
|
expect(flushCallback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
eq.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createErrorEvent(message: string, fingerprint?: string): ErrorEvent {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
message,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
fingerprint,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,472 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { PluginManager } from '../src/core/PluginManager';
|
||||||
|
import type { LightPlugin, LightClient, SentryEvent } from '../src/types';
|
||||||
|
|
||||||
|
describe('PluginManager', () => {
|
||||||
|
let mockClient: LightClient;
|
||||||
|
let consoleSpy: ReturnType<typeof vi.spyOn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockClient = {
|
||||||
|
config: { dsn: 'https://abc@sentry.io/123' },
|
||||||
|
dsn: { protocol: 'https', publicKey: 'abc', host: 'sentry.io', projectId: '123' },
|
||||||
|
on: vi.fn(),
|
||||||
|
off: vi.fn(),
|
||||||
|
emit: vi.fn(),
|
||||||
|
captureException: vi.fn(),
|
||||||
|
captureMessage: vi.fn(),
|
||||||
|
captureEvent: vi.fn(),
|
||||||
|
setUser: vi.fn(),
|
||||||
|
setAnonymousId: vi.fn(),
|
||||||
|
setTag: vi.fn(),
|
||||||
|
setTags: vi.fn(),
|
||||||
|
setExtra: vi.fn(),
|
||||||
|
addBreadcrumb: vi.fn(),
|
||||||
|
startTransaction: vi.fn(),
|
||||||
|
getCurrentTransaction: vi.fn(),
|
||||||
|
getCurrentSpan: vi.fn(),
|
||||||
|
setSpan: vi.fn(),
|
||||||
|
startSpan: vi.fn(),
|
||||||
|
flush: vi.fn(),
|
||||||
|
disable: vi.fn(),
|
||||||
|
enable: vi.fn(),
|
||||||
|
use: vi.fn(),
|
||||||
|
init: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
} as unknown as LightClient;
|
||||||
|
|
||||||
|
consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create a PluginManager instance', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
expect(manager).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('add', () => {
|
||||||
|
it('should add a plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'test-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(manager.has('test-plugin')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add duplicate plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const setup1 = vi.fn();
|
||||||
|
const setup2 = vi.fn();
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'test-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: setup1,
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'test-plugin',
|
||||||
|
version: '2.0.0',
|
||||||
|
setup: setup2,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
expect(manager.has('test-plugin')).toBe(true);
|
||||||
|
expect(setup1).toHaveBeenCalledWith(mockClient);
|
||||||
|
expect(setup2).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call plugin setup with client', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const setupFn = vi.fn();
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'setup-test',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: setupFn,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(setupFn).toHaveBeenCalledWith(mockClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if setup throws', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'error-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: () => {
|
||||||
|
throw new Error('Setup error');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => manager.add(plugin)).not.toThrow();
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
it('should remove a plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'removable',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(manager.has('removable')).toBe(true);
|
||||||
|
|
||||||
|
manager.remove('removable');
|
||||||
|
expect(manager.has('removable')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call plugin destroy when removing', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const destroyFn = vi.fn();
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'destroyable',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: destroyFn,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
manager.remove('destroyable');
|
||||||
|
|
||||||
|
expect(destroyFn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if destroy throws', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'destroy-error',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: () => {
|
||||||
|
throw new Error('Destroy error');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(() => manager.remove('destroy-error')).not.toThrow();
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if plugin does not exist', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
manager.remove('non-existent');
|
||||||
|
expect(consoleSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if plugin has no destroy method', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'no-destroy',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(() => manager.remove('no-destroy')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
|
it('should return plugin by name', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'gettable',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
const retrieved = manager.get('gettable');
|
||||||
|
expect(retrieved).toBe(plugin);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for non-existent plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const retrieved = manager.get('non-existent');
|
||||||
|
expect(retrieved).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('has', () => {
|
||||||
|
it('should return true for added plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'checkable',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(manager.has('checkable')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-existent plugin', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
expect(manager.has('non-existent')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('applyBeforeReport', () => {
|
||||||
|
it('should pass event through all plugins', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'before-1',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: (event) => ({ ...event, extra: { ...event.extra, from1: true } }),
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'before-2',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: (event) => ({ ...event, extra: { ...event.extra, from2: true } }),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
const result = manager.applyBeforeReport(event);
|
||||||
|
|
||||||
|
expect(result.extra).toEqual({ from1: true, from2: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if any plugin filters the event', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'filter-1',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: (event) => event,
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'filter-2',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
const result = manager.applyBeforeReport(event);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should continue if plugin beforeReport throws', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'error-before',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: () => {
|
||||||
|
throw new Error('BeforeReport error');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'ok-after',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: (event) => ({ ...event, extra: { passed: true } }),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
const result = manager.applyBeforeReport(event);
|
||||||
|
|
||||||
|
expect(result?.extra).toEqual({ passed: true });
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip plugin without beforeReport', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'no-before',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'modify-after',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
beforeReport: (event) => ({ ...event, extra: { modified: true } }),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
const result = manager.applyBeforeReport(event);
|
||||||
|
|
||||||
|
expect(result?.extra).toEqual({ modified: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return event when no plugins', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
const result = manager.applyBeforeReport(event);
|
||||||
|
expect(result).toBe(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('applyAfterReport', () => {
|
||||||
|
it('should call afterReport on all plugins', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const after1 = vi.fn();
|
||||||
|
const after2 = vi.fn();
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'after-1',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
afterReport: after1,
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'after-2',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
afterReport: after2,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
manager.applyAfterReport(event);
|
||||||
|
|
||||||
|
expect(after1).toHaveBeenCalledWith(event);
|
||||||
|
expect(after2).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should continue if afterReport throws', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'error-after',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
afterReport: () => {
|
||||||
|
throw new Error('AfterReport error');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'ok-after',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
afterReport: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
expect(() => manager.applyAfterReport(event)).not.toThrow();
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip plugin without afterReport', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'no-after',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
|
||||||
|
const event = { type: 'error', level: 'error', timestamp: Date.now() } as SentryEvent;
|
||||||
|
expect(() => manager.applyAfterReport(event)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should call destroy on all plugins', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const destroy1 = vi.fn();
|
||||||
|
const destroy2 = vi.fn();
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'destroy-1',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: destroy1,
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'destroy-2',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: destroy2,
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
manager.destroy();
|
||||||
|
|
||||||
|
expect(destroy1).toHaveBeenCalled();
|
||||||
|
expect(destroy2).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear plugins after destroy', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'clear-me',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(manager.has('clear-me')).toBe(true);
|
||||||
|
|
||||||
|
manager.destroy();
|
||||||
|
expect(manager.has('clear-me')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should continue even if destroy throws', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin1: LightPlugin = {
|
||||||
|
name: 'destroy-error',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: () => {
|
||||||
|
throw new Error('Destroy error');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const plugin2: LightPlugin = {
|
||||||
|
name: 'destroy-ok',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin1);
|
||||||
|
manager.add(plugin2);
|
||||||
|
manager.destroy();
|
||||||
|
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
expect((plugin2 as any).destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if plugin has no destroy method', () => {
|
||||||
|
const manager = new PluginManager(mockClient);
|
||||||
|
const plugin: LightPlugin = {
|
||||||
|
name: 'no-destroy',
|
||||||
|
version: '1.0.0',
|
||||||
|
setup: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.add(plugin);
|
||||||
|
expect(() => manager.destroy()).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import SamplingPlugin from '../src/plugins/SamplingPlugin';
|
||||||
|
import type { LightClient, LightConfig, SentryEvent, ErrorEvent } from '../src/types';
|
||||||
|
|
||||||
|
describe('SamplingPlugin', () => {
|
||||||
|
function createMockClient(configOverrides: Partial<LightConfig> = {}): LightClient {
|
||||||
|
const config: LightConfig = {
|
||||||
|
dsn: 'https://abc@sentry.io/123',
|
||||||
|
sampleRate: 1,
|
||||||
|
sampling: {
|
||||||
|
rates: {
|
||||||
|
error: 1,
|
||||||
|
performance: 1,
|
||||||
|
network: 1,
|
||||||
|
behavior: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...configOverrides,
|
||||||
|
} as LightConfig;
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
dsn: { protocol: 'https', publicKey: 'abc', host: 'sentry.io', projectId: '123' },
|
||||||
|
on: vi.fn(),
|
||||||
|
off: vi.fn(),
|
||||||
|
emit: vi.fn(),
|
||||||
|
captureException: vi.fn(),
|
||||||
|
captureMessage: vi.fn(),
|
||||||
|
captureEvent: vi.fn(),
|
||||||
|
setUser: vi.fn(),
|
||||||
|
setAnonymousId: vi.fn(),
|
||||||
|
setTag: vi.fn(),
|
||||||
|
setTags: vi.fn(),
|
||||||
|
setExtra: vi.fn(),
|
||||||
|
addBreadcrumb: vi.fn(),
|
||||||
|
startTransaction: vi.fn(),
|
||||||
|
getCurrentTransaction: vi.fn(),
|
||||||
|
getCurrentSpan: vi.fn(),
|
||||||
|
setSpan: vi.fn(),
|
||||||
|
startSpan: vi.fn(),
|
||||||
|
flush: vi.fn(),
|
||||||
|
disable: vi.fn(),
|
||||||
|
enable: vi.fn(),
|
||||||
|
use: vi.fn(),
|
||||||
|
init: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
} as unknown as LightClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createErrorEvent(): ErrorEvent {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
message: 'Test error',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEvent(type: string): SentryEvent {
|
||||||
|
return {
|
||||||
|
type: type as any,
|
||||||
|
level: 'info',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as SentryEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create plugin with correct name and version', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
expect(plugin.name).toBe('sampling');
|
||||||
|
expect(plugin.version).toBe('1.0.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setup', () => {
|
||||||
|
it('should call setup with client', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient();
|
||||||
|
expect(() => plugin.setup(client)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load default rates when no sampling config', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampling: undefined });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load custom rates from config', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 1,
|
||||||
|
sampling: {
|
||||||
|
rates: {
|
||||||
|
error: 0.5,
|
||||||
|
performance: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1);
|
||||||
|
expect(plugin.beforeReport(createEvent('error'))).toBeTruthy();
|
||||||
|
randomSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('beforeReport', () => {
|
||||||
|
it('should return event when sampling rate is 1', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampleRate: 1 });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBe(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when global sampleRate is 0', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampleRate: 0 });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when global sampleRate is negative', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampleRate: -1 });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return event when type rate is 1', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 1,
|
||||||
|
sampling: { rates: { error: 1 } },
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBe(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when type rate is 0', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 1,
|
||||||
|
sampling: { rates: { error: 0 } },
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always sample count events (rate = 1)', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 0,
|
||||||
|
sampling: { rates: { error: 0 } },
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const countEvent = {
|
||||||
|
type: 'count' as const,
|
||||||
|
level: 'info' as const,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
fingerprint: 'test',
|
||||||
|
count: 1,
|
||||||
|
ts_start: Date.now(),
|
||||||
|
ts_end: Date.now(),
|
||||||
|
};
|
||||||
|
const result = plugin.beforeReport(countEvent);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unknown event types (default rate = 1)', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampleRate: 1 });
|
||||||
|
plugin.setup(client);
|
||||||
|
const unknownEvent = createEvent('unknown_type');
|
||||||
|
const result = plugin.beforeReport(unknownEvent);
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use combined rate when globalRate < 1', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 0.5,
|
||||||
|
sampling: { rates: { error: 0.5 } },
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.9);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
randomSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass when random is below combined rate', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampleRate: 0.5,
|
||||||
|
sampling: { rates: { error: 0.5 } },
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBe(event);
|
||||||
|
randomSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass when type rate is 1 and globalRate is 1', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampleRate: 1 });
|
||||||
|
plugin.setup(client);
|
||||||
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.99);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).toBe(event);
|
||||||
|
randomSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should not throw when destroy is called', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
expect(() => plugin.destroy()).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('config loading', () => {
|
||||||
|
it('should handle missing sampling config', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampling: undefined });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle sampling config without rates', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({ sampling: {} });
|
||||||
|
plugin.setup(client);
|
||||||
|
const event = createErrorEvent();
|
||||||
|
const result = plugin.beforeReport(event);
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge custom rates with defaults', () => {
|
||||||
|
const plugin = new SamplingPlugin();
|
||||||
|
const client = createMockClient({
|
||||||
|
sampling: {
|
||||||
|
rates: {
|
||||||
|
error: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
plugin.setup(client);
|
||||||
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.9);
|
||||||
|
expect(plugin.beforeReport(createEvent('error'))).toBeNull();
|
||||||
|
expect(plugin.beforeReport(createEvent('performance'))).not.toBeNull();
|
||||||
|
randomSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,350 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { TracingManager } from '../src/core/TracingManager';
|
||||||
|
import type { LightConfig, Transaction, Span } from '../src/types';
|
||||||
|
|
||||||
|
describe('TracingManager', () => {
|
||||||
|
let tracingManager: TracingManager;
|
||||||
|
let mockClient: ReturnType<typeof createMockClient>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockClient = createMockClient();
|
||||||
|
const config: LightConfig = {
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
tracesSampleRate: 1,
|
||||||
|
};
|
||||||
|
tracingManager = new TracingManager(config, mockClient, 'abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
tracingManager.destroy();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('startTransaction', () => {
|
||||||
|
it('should create transaction with correct properties', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
op: 'custom',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transaction).toBeDefined();
|
||||||
|
expect(transaction?.name).toBe('test-transaction');
|
||||||
|
expect(transaction?.op).toBe('custom');
|
||||||
|
expect(transaction?.traceId).toBeDefined();
|
||||||
|
expect(transaction?.spanId).toBeDefined();
|
||||||
|
expect(transaction?.sampled).toBe(true); // tracesSampleRate = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return transaction even when SDK would be disabled (TracingManager does not check enabled)', () => {
|
||||||
|
// Note: Client.startTransaction checks enabled, but TracingManager does not
|
||||||
|
const disabledConfig: LightConfig = {
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
enabled: false,
|
||||||
|
tracesSampleRate: 1,
|
||||||
|
};
|
||||||
|
const disabledManager = new TracingManager(disabledConfig, mockClient, 'abc');
|
||||||
|
|
||||||
|
const transaction = disabledManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
// TracingManager doesn't know about enabled flag, it still creates transaction
|
||||||
|
expect(transaction).toBeDefined();
|
||||||
|
disabledManager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sample transaction based on tracesSampleRate', () => {
|
||||||
|
const sampledConfig: LightConfig = {
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
tracesSampleRate: 0, // No sampling
|
||||||
|
};
|
||||||
|
const sampledManager = new TracingManager(sampledConfig, mockClient, 'abc');
|
||||||
|
|
||||||
|
const transaction = sampledManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transaction?.sampled).toBe(false);
|
||||||
|
sampledManager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use tracesSampler for dynamic sampling', () => {
|
||||||
|
const customSamplerConfig: LightConfig = {
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
tracesSampler: () => false,
|
||||||
|
};
|
||||||
|
const customSamplerManager = new TracingManager(customSamplerConfig, mockClient, 'abc');
|
||||||
|
|
||||||
|
const transaction = customSamplerManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transaction?.sampled).toBe(false);
|
||||||
|
customSamplerManager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set current transaction after start', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBe(transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use tracesSampler for dynamic sampling with parentSampled', () => {
|
||||||
|
// With tracesSampler, parentSampled is passed to the sampler
|
||||||
|
const customSamplerConfig: LightConfig = {
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
tracesSampler: (ctx) => {
|
||||||
|
// If parent was sampled=false, child should also be not sampled
|
||||||
|
if (ctx.parentSampled === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const parentSamplerManager = new TracingManager(customSamplerConfig, mockClient, 'abc');
|
||||||
|
|
||||||
|
const parentTransaction = parentSamplerManager.startTransaction({
|
||||||
|
name: 'parent',
|
||||||
|
sampled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const childTransaction = parentSamplerManager.startTransaction({
|
||||||
|
name: 'child',
|
||||||
|
sampled: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Child should inherit parent's sampled=false via tracesSampler
|
||||||
|
expect(childTransaction?.sampled).toBe(false);
|
||||||
|
parentSamplerManager.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrentTransaction', () => {
|
||||||
|
it('should return null when no transaction exists', () => {
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return current transaction', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBe(transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return current transaction after finish (via TracingManager.onTransactionFinished)', () => {
|
||||||
|
// Note: Transaction.finish() calls client.tracingManager.onTransactionFinished
|
||||||
|
// if client is properly set up. With mock client, this doesn't happen automatically.
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBe(transaction);
|
||||||
|
|
||||||
|
// Manually call onTransactionFinished to simulate proper cleanup
|
||||||
|
tracingManager.onTransactionFinished(transaction!);
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrentSpan', () => {
|
||||||
|
it('should return transaction as span when no child spans', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentSpan = tracingManager.getCurrentSpan();
|
||||||
|
expect(currentSpan?.spanId).toBe(transaction?.spanId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return most recent child span', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
const childSpan = tracingManager.startSpan({
|
||||||
|
op: 'child.span',
|
||||||
|
description: 'child operation',
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentSpan = tracingManager.getCurrentSpan();
|
||||||
|
expect(currentSpan?.spanId).toBe(childSpan?.spanId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('startSpan', () => {
|
||||||
|
it('should create child span under current transaction', () => {
|
||||||
|
tracingManager.startTransaction({
|
||||||
|
name: 'test-transaction',
|
||||||
|
});
|
||||||
|
|
||||||
|
const childSpan = tracingManager.startSpan({
|
||||||
|
op: 'http.client',
|
||||||
|
description: 'GET /api/users',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(childSpan).toBeDefined();
|
||||||
|
expect(childSpan?.op).toBe('http.client');
|
||||||
|
expect(childSpan?.parentSpanId).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when no active span and no traceId', () => {
|
||||||
|
const noSpanManager = new TracingManager(
|
||||||
|
{ dsn: 'https://abc@log.example.com/1001' },
|
||||||
|
mockClient,
|
||||||
|
'abc'
|
||||||
|
);
|
||||||
|
|
||||||
|
const span = noSpanManager.startSpan({
|
||||||
|
op: 'test.span',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(span).toBeNull();
|
||||||
|
noSpanManager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create span with traceId when no parent span exists', () => {
|
||||||
|
const noSpanManager = new TracingManager(
|
||||||
|
{ dsn: 'https://abc@log.example.com/1001' },
|
||||||
|
mockClient,
|
||||||
|
'abc'
|
||||||
|
);
|
||||||
|
|
||||||
|
const span = noSpanManager.startSpan({
|
||||||
|
op: 'test.span',
|
||||||
|
traceId: 'abcd1234abcd1234abcd1234abcd1234',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(span).toBeDefined();
|
||||||
|
expect(span?.traceId).toBe('abcd1234abcd1234abcd1234abcd1234');
|
||||||
|
noSpanManager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add span to unsampled transaction', () => {
|
||||||
|
const unsampledManager = new TracingManager(
|
||||||
|
{ dsn: 'https://abc@log.example.com/1001', tracesSampleRate: 0 },
|
||||||
|
mockClient,
|
||||||
|
'abc'
|
||||||
|
);
|
||||||
|
|
||||||
|
const transaction = unsampledManager.startTransaction({ name: 'unsampled' });
|
||||||
|
const span = unsampledManager.startSpan({ op: 'child' });
|
||||||
|
|
||||||
|
expect(span).toBeDefined();
|
||||||
|
// Span should not be in transaction.spans for unsampled transaction
|
||||||
|
expect(transaction?.spans.length).toBe(0);
|
||||||
|
unsampledManager.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setCurrentSpan', () => {
|
||||||
|
it('should set current span', () => {
|
||||||
|
tracingManager.startTransaction({ name: 'test' });
|
||||||
|
|
||||||
|
const customSpan = { spanId: 'custom-span' } as Span;
|
||||||
|
tracingManager.setCurrentSpan(customSpan);
|
||||||
|
|
||||||
|
expect(tracingManager.getCurrentSpan()?.spanId).toBe('custom-span');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pop span from stack when set to null', () => {
|
||||||
|
tracingManager.startTransaction({ name: 'test' });
|
||||||
|
const childSpan = tracingManager.startSpan({ op: 'child' });
|
||||||
|
expect(tracingManager.getCurrentSpan()?.spanId).toBe(childSpan?.spanId);
|
||||||
|
|
||||||
|
tracingManager.setCurrentSpan(null);
|
||||||
|
// Should return to transaction
|
||||||
|
expect(tracingManager.getCurrentSpan()?.spanId).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSpanFinished', () => {
|
||||||
|
it('should remove finished span from stack', () => {
|
||||||
|
tracingManager.startTransaction({ name: 'test' });
|
||||||
|
const childSpan = tracingManager.startSpan({ op: 'child' });
|
||||||
|
expect(tracingManager.getCurrentSpan()?.spanId).toBe(childSpan?.spanId);
|
||||||
|
|
||||||
|
tracingManager.onSpanFinished(childSpan!);
|
||||||
|
// Should return to transaction after child is removed
|
||||||
|
expect(tracingManager.getCurrentSpan()?.spanId).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onTransactionFinished', () => {
|
||||||
|
it('should clear current transaction', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({ name: 'test' });
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBe(transaction);
|
||||||
|
|
||||||
|
tracingManager.onTransactionFinished(transaction!);
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateConfig', () => {
|
||||||
|
it('should update baggage when user changes', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({ name: 'test' });
|
||||||
|
expect(transaction).toBeDefined();
|
||||||
|
|
||||||
|
tracingManager.updateConfig({
|
||||||
|
dsn: 'https://abc@log.example.com/1001',
|
||||||
|
user: { id: 'user123', segment: 'premium' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transaction baggage should have user info
|
||||||
|
// This is an internal detail, just verify no error
|
||||||
|
expect(transaction).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should finish current transaction on destroy', () => {
|
||||||
|
const transaction = tracingManager.startTransaction({ name: 'test' });
|
||||||
|
|
||||||
|
tracingManager.destroy();
|
||||||
|
|
||||||
|
// Transaction should be finished (sampled=false since no client capture)
|
||||||
|
expect(tracingManager.getCurrentTransaction()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear span stack on destroy', () => {
|
||||||
|
tracingManager.startTransaction({ name: 'test' });
|
||||||
|
tracingManager.startSpan({ op: 'child' });
|
||||||
|
|
||||||
|
tracingManager.destroy();
|
||||||
|
|
||||||
|
expect(tracingManager.getCurrentSpan()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockClient() {
|
||||||
|
return {
|
||||||
|
config: {},
|
||||||
|
dsn: { protocol: 'https', publicKey: 'abc', host: 'log.example.com', projectId: '1001' },
|
||||||
|
captureException: vi.fn(),
|
||||||
|
captureMessage: vi.fn(),
|
||||||
|
captureEvent: vi.fn(),
|
||||||
|
setUser: vi.fn(),
|
||||||
|
setAnonymousId: vi.fn(),
|
||||||
|
setTag: vi.fn(),
|
||||||
|
setTags: vi.fn(),
|
||||||
|
setExtra: vi.fn(),
|
||||||
|
addBreadcrumb: vi.fn(),
|
||||||
|
startTransaction: vi.fn(),
|
||||||
|
getCurrentTransaction: vi.fn(),
|
||||||
|
getCurrentSpan: vi.fn(),
|
||||||
|
setSpan: vi.fn(),
|
||||||
|
startSpan: vi.fn(),
|
||||||
|
flush: vi.fn(),
|
||||||
|
disable: vi.fn(),
|
||||||
|
enable: vi.fn(),
|
||||||
|
use: vi.fn(),
|
||||||
|
init: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
off: vi.fn(),
|
||||||
|
emit: vi.fn(),
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
serializeBaggage,
|
||||||
|
parseBaggage,
|
||||||
|
createBaggageFromTransaction,
|
||||||
|
} from '../src/utils/baggage';
|
||||||
|
import type { BaggageFields } from '../src/types';
|
||||||
|
|
||||||
|
describe('baggage utils', () => {
|
||||||
|
describe('serializeBaggage', () => {
|
||||||
|
it('should serialize empty baggage', () => {
|
||||||
|
const result = serializeBaggage({});
|
||||||
|
expect(result).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize baggage with all fields', () => {
|
||||||
|
const baggage: BaggageFields = {
|
||||||
|
trace_id: 'abc123',
|
||||||
|
public_key: 'key123',
|
||||||
|
sample_rate: '0.5',
|
||||||
|
sampled: 'true',
|
||||||
|
user_id: 'user1',
|
||||||
|
user_segment: 'premium',
|
||||||
|
release: '1.0.0',
|
||||||
|
environment: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializeBaggage(baggage);
|
||||||
|
|
||||||
|
expect(result).toContain('sentry-trace_id=abc123');
|
||||||
|
expect(result).toContain('sentry-public_key=key123');
|
||||||
|
expect(result).toContain('sentry-sample_rate=0.5');
|
||||||
|
expect(result).toContain('sentry-sampled=true');
|
||||||
|
expect(result).toContain('sentry-user_id=user1');
|
||||||
|
expect(result).toContain('sentry-user_segment=premium');
|
||||||
|
expect(result).toContain('sentry-release=1.0.0');
|
||||||
|
expect(result).toContain('sentry-environment=production');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip null, undefined, and empty string values', () => {
|
||||||
|
const baggage: BaggageFields = {
|
||||||
|
trace_id: 'abc123',
|
||||||
|
public_key: undefined as any,
|
||||||
|
sampled: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializeBaggage(baggage);
|
||||||
|
|
||||||
|
expect(result).toContain('sentry-trace_id=abc123');
|
||||||
|
expect(result).not.toContain('public_key');
|
||||||
|
expect(result).not.toContain('sampled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add sentry- prefix to non-sentry keys', () => {
|
||||||
|
const baggage: BaggageFields = {
|
||||||
|
trace_id: 'abc123',
|
||||||
|
custom_field: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializeBaggage(baggage);
|
||||||
|
|
||||||
|
expect(result).toContain('sentry-trace_id=abc123');
|
||||||
|
expect(result).toContain('sentry-custom_field=value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve sentry- prefix on sentry keys', () => {
|
||||||
|
const baggage: BaggageFields = {
|
||||||
|
'sentry-trace_id': 'abc123',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializeBaggage(baggage);
|
||||||
|
|
||||||
|
expect(result).toContain('sentry-trace_id=abc123');
|
||||||
|
// Should not double-prefix
|
||||||
|
expect(result).not.toContain('sentry-sentry-');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should truncate long baggage to 8192 characters', () => {
|
||||||
|
const longBaggage: BaggageFields = {};
|
||||||
|
// Create a very long value
|
||||||
|
const longValue = 'a'.repeat(10000);
|
||||||
|
longBaggage.trace_id = longValue;
|
||||||
|
|
||||||
|
const result = serializeBaggage(longBaggage);
|
||||||
|
|
||||||
|
expect(result.length).toBeLessThanOrEqual(8192);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should URL encode values', () => {
|
||||||
|
const baggage: BaggageFields = {
|
||||||
|
user_id: 'user with spaces',
|
||||||
|
custom_field: 'value=special&chars',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializeBaggage(baggage);
|
||||||
|
|
||||||
|
expect(result).toContain(encodeURIComponent('user with spaces'));
|
||||||
|
expect(result).toContain(encodeURIComponent('value=special&chars'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseBaggage', () => {
|
||||||
|
it('should parse empty baggage', () => {
|
||||||
|
const result = parseBaggage('');
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse valid baggage header', () => {
|
||||||
|
const header = 'sentry-trace_id=abc123,sentry-sampled=true,sentry-user_id=user1';
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.trace_id).toBe('abc123');
|
||||||
|
expect(result.sampled).toBe('true');
|
||||||
|
expect(result.user_id).toBe('user1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove sentry- prefix from keys', () => {
|
||||||
|
const header = 'sentry-public_key=key123,sentry-release=1.0.0';
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.public_key).toBe('key123');
|
||||||
|
expect(result.release).toBe('1.0.0');
|
||||||
|
expect(result['sentry-public_key']).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle custom fields without prefix', () => {
|
||||||
|
const header = 'custom_field=value,another=data';
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.custom_field).toBe('value');
|
||||||
|
expect(result.another).toBe('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should URL decode values', () => {
|
||||||
|
const header = `sentry-user_id=${encodeURIComponent('user with spaces')}`;
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.user_id).toBe('user with spaces');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip malformed entries', () => {
|
||||||
|
const header = 'valid=key,invalid-entry-no-equals,sentry-trace_id=abc123';
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.valid).toBe('key');
|
||||||
|
expect(result['sentry-trace_id']).toBeUndefined();
|
||||||
|
expect(result.trace_id).toBe('abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle whitespace around entries', () => {
|
||||||
|
const header = ' sentry-trace_id=abc123 , sentry-sampled=true ';
|
||||||
|
const result = parseBaggage(header);
|
||||||
|
|
||||||
|
expect(result.trace_id).toBe('abc123');
|
||||||
|
expect(result.sampled).toBe('true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createBaggageFromTransaction', () => {
|
||||||
|
it('should create baggage with only trace_id', () => {
|
||||||
|
const result = createBaggageFromTransaction('abc123');
|
||||||
|
|
||||||
|
expect(result.trace_id).toBe('abc123');
|
||||||
|
expect(Object.keys(result)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create baggage with all options', () => {
|
||||||
|
const result = createBaggageFromTransaction('abc123', {
|
||||||
|
dsnPublicKey: 'key123',
|
||||||
|
sampled: true,
|
||||||
|
sampleRate: 0.5,
|
||||||
|
user: { id: 'user1', segment: 'premium' },
|
||||||
|
release: '1.0.0',
|
||||||
|
environment: 'production',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.trace_id).toBe('abc123');
|
||||||
|
expect(result.public_key).toBe('key123');
|
||||||
|
expect(result.sampled).toBe('true');
|
||||||
|
expect(result.sample_rate).toBe('0.5');
|
||||||
|
expect(result.user_id).toBe('user1');
|
||||||
|
expect(result.user_segment).toBe('premium');
|
||||||
|
expect(result.release).toBe('1.0.0');
|
||||||
|
expect(result.environment).toBe('production');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert sampled boolean to string', () => {
|
||||||
|
expect(createBaggageFromTransaction('abc', { sampled: true }).sampled).toBe('true');
|
||||||
|
expect(createBaggageFromTransaction('abc', { sampled: false }).sampled).toBe('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert sampleRate number to string', () => {
|
||||||
|
const result = createBaggageFromTransaction('abc', { sampleRate: 0.1 });
|
||||||
|
expect(result.sample_rate).toBe('0.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle optional user object', () => {
|
||||||
|
const result1 = createBaggageFromTransaction('abc', { user: { id: 'user1' } });
|
||||||
|
expect(result1.user_id).toBe('user1');
|
||||||
|
expect(result1.user_segment).toBeUndefined();
|
||||||
|
|
||||||
|
const result2 = createBaggageFromTransaction('abc', { user: { id: 'user2', segment: 'vip' } });
|
||||||
|
expect(result2.user_id).toBe('user2');
|
||||||
|
expect(result2.user_segment).toBe('vip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include undefined options', () => {
|
||||||
|
const result = createBaggageFromTransaction('abc', {
|
||||||
|
dsnPublicKey: 'key',
|
||||||
|
sampled: undefined,
|
||||||
|
sampleRate: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.trace_id).toBe('abc');
|
||||||
|
expect(result.public_key).toBe('key');
|
||||||
|
expect(result.sampled).toBeUndefined();
|
||||||
|
expect(result.sample_rate).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work without options', () => {
|
||||||
|
const result = createBaggageFromTransaction('abc');
|
||||||
|
expect(result.trace_id).toBe('abc');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('serialize/parse roundtrip', () => {
|
||||||
|
it('should preserve baggage through serialize/parse cycle', () => {
|
||||||
|
const original: BaggageFields = {
|
||||||
|
trace_id: 'abc123',
|
||||||
|
public_key: 'key123',
|
||||||
|
sampled: 'true',
|
||||||
|
user_id: 'user1',
|
||||||
|
release: '1.0.0',
|
||||||
|
environment: 'production',
|
||||||
|
custom: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const serialized = serializeBaggage(original);
|
||||||
|
const parsed = parseBaggage(serialized);
|
||||||
|
|
||||||
|
expect(parsed.trace_id).toBe(original.trace_id);
|
||||||
|
expect(parsed.public_key).toBe(original.public_key);
|
||||||
|
expect(parsed.sampled).toBe(original.sampled);
|
||||||
|
expect(parsed.user_id).toBe(original.user_id);
|
||||||
|
expect(parsed.release).toBe(original.release);
|
||||||
|
expect(parsed.environment).toBe(original.environment);
|
||||||
|
expect(parsed.custom).toBe(original.custom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import {
|
import {
|
||||||
getEnvInfo,
|
getEnvInfo,
|
||||||
getContexts,
|
getContexts,
|
||||||
|
|
@ -10,6 +10,17 @@ import {
|
||||||
} from '../src/utils/env';
|
} from '../src/utils/env';
|
||||||
|
|
||||||
describe('env utils', () => {
|
describe('env utils', () => {
|
||||||
|
const originalNavigator = global.navigator;
|
||||||
|
const originalUserAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
function setUserAgent(ua: string) {
|
||||||
|
Object.defineProperty(global.navigator, 'userAgent', {
|
||||||
|
value: ua,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('getEnvInfo', () => {
|
describe('getEnvInfo', () => {
|
||||||
it('should return env info object', () => {
|
it('should return env info object', () => {
|
||||||
const env = getEnvInfo();
|
const env = getEnvInfo();
|
||||||
|
|
@ -38,6 +49,158 @@ describe('env utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('browser detection', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'Chrome on Windows',
|
||||||
|
ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
browser: 'Chrome',
|
||||||
|
os: 'Windows 11',
|
||||||
|
device: 'desktop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Firefox on macOS',
|
||||||
|
ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||||
|
browser: 'Firefox',
|
||||||
|
os: 'macOS',
|
||||||
|
device: 'desktop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Safari on macOS',
|
||||||
|
ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
|
||||||
|
browser: 'Safari',
|
||||||
|
os: 'macOS',
|
||||||
|
device: 'desktop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Edge on Windows',
|
||||||
|
ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
|
||||||
|
browser: 'Edge',
|
||||||
|
os: 'Windows 11',
|
||||||
|
device: 'desktop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'WeChat',
|
||||||
|
ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.33(0x1800212d)',
|
||||||
|
browser: 'WeChat',
|
||||||
|
os: 'iOS',
|
||||||
|
device: 'mobile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'iPhone Safari',
|
||||||
|
ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||||
|
browser: 'Safari',
|
||||||
|
os: 'iOS',
|
||||||
|
device: 'mobile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'iPad Safari',
|
||||||
|
ua: 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||||
|
browser: 'Safari',
|
||||||
|
os: 'iOS',
|
||||||
|
device: 'tablet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Android Chrome',
|
||||||
|
ua: 'Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
|
||||||
|
browser: 'Chrome',
|
||||||
|
os: 'Android',
|
||||||
|
device: 'mobile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Samsung device',
|
||||||
|
ua: 'Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
|
||||||
|
browser: 'Chrome',
|
||||||
|
os: 'Android',
|
||||||
|
device: 'mobile',
|
||||||
|
vendor: 'Samsung',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Opera',
|
||||||
|
ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0',
|
||||||
|
browser: 'Opera',
|
||||||
|
os: 'Windows 11',
|
||||||
|
device: 'desktop',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(tc => {
|
||||||
|
it(`should detect ${tc.name}`, () => {
|
||||||
|
setUserAgent(tc.ua);
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.browser.name).toBe(tc.browser);
|
||||||
|
expect(env.os.name).toBe(tc.os);
|
||||||
|
expect(env.device.type).toBe(tc.device);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect Unknown browser for unrecognized UA', () => {
|
||||||
|
setUserAgent('SomeCustomBot/1.0');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.browser.name).toBe('Unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect Windows 11', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||||
|
// The current code doesn't distinguish Win10 vs Win11 based on Win64 x64 alone
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.os.name).toMatch(/Windows/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse browser version', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.5481.100 Safari/537.36');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.browser.version).toMatch(/^120/);
|
||||||
|
expect(env.browser.major).toBe('120');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse macOS version with underscores', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.os.name).toBe('macOS');
|
||||||
|
expect(env.os.version).toContain('14.2.1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('device detection', () => {
|
||||||
|
it('should detect iPhone as mobile Apple device', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.device.type).toBe('mobile');
|
||||||
|
expect(env.device.vendor).toBe('Apple');
|
||||||
|
expect(env.device.model).toBe('iPhone');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect iPad as tablet Apple device', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.device.type).toBe('tablet');
|
||||||
|
expect(env.device.vendor).toBe('Apple');
|
||||||
|
expect(env.device.model).toBe('iPad');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect Samsung device', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.device.vendor).toBe('Samsung');
|
||||||
|
expect(env.device.model).toBe('G991B');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect Pixel device', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.device.vendor).toBe('Google');
|
||||||
|
expect(env.device.model).toBe('Pixel 8');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect Huawei device', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Linux; Android 12; HUAWEI Mate40 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36');
|
||||||
|
const env = getEnvInfo();
|
||||||
|
expect(env.device.vendor).toBe('Huawei');
|
||||||
|
expect(env.device.model).toBe('Mate40');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getContexts', () => {
|
describe('getContexts', () => {
|
||||||
it('should return contexts with encoded values', () => {
|
it('should return contexts with encoded values', () => {
|
||||||
const contexts = getContexts();
|
const contexts = getContexts();
|
||||||
|
|
@ -56,13 +219,26 @@ describe('env utils', () => {
|
||||||
const contexts = getContexts();
|
const contexts = getContexts();
|
||||||
expect(contexts.os.name.length).toBeLessThanOrEqual(10);
|
expect(contexts.os.name.length).toBeLessThanOrEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should map Chrome to c', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||||
|
const contexts = getContexts();
|
||||||
|
expect(contexts.browser.name).toBe('c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map Windows 11 to w11', () => {
|
||||||
|
setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||||
|
const contexts = getContexts();
|
||||||
|
expect(contexts.os.name).toBe('w11');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getContextId', () => {
|
describe('getContextId', () => {
|
||||||
it('should return a 16 character string', () => {
|
it('should return a hex string of up to 8 characters (32-bit hash)', () => {
|
||||||
const contextId = getContextId();
|
const contextId = getContextId();
|
||||||
expect(contextId).toBeDefined();
|
expect(contextId).toBeDefined();
|
||||||
expect(contextId.length).toBe(16);
|
expect(contextId.length).toBeLessThanOrEqual(16);
|
||||||
|
expect(contextId.length).toBeGreaterThan(0);
|
||||||
expect(contextId).toMatch(/^[a-f0-9]+$/i);
|
expect(contextId).toMatch(/^[a-f0-9]+$/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -121,6 +297,49 @@ describe('env utils', () => {
|
||||||
expect(sanitized).toContain('code');
|
expect(sanitized).toContain('code');
|
||||||
expect(sanitized).toContain('%5BFiltered%5D');
|
expect(sanitized).toContain('%5BFiltered%5D');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter sensitive params in hash', () => {
|
||||||
|
const url = 'https://example.com/page#access_token=secret123&other=value';
|
||||||
|
const sanitized = sanitizeUrl(url);
|
||||||
|
expect(sanitized).toContain('access_token');
|
||||||
|
expect(sanitized).toContain('%5BFiltered%5D');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter javascript: protocol', () => {
|
||||||
|
const url = 'javascript:alert(1)';
|
||||||
|
const sanitized = sanitizeUrl(url);
|
||||||
|
expect(sanitized).toBe('javascript:[filtered]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter javascript: with whitespace', () => {
|
||||||
|
const url = ' javascript:alert(document.cookie)';
|
||||||
|
const sanitized = sanitizeUrl(url);
|
||||||
|
expect(sanitized).toBe('javascript:[filtered]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty string', () => {
|
||||||
|
const sanitized = sanitizeUrl('');
|
||||||
|
expect(sanitized).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null/undefined', () => {
|
||||||
|
expect(sanitizeUrl(null as any)).toBeNull();
|
||||||
|
expect(sanitizeUrl(undefined as any)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter multiple sensitive params', () => {
|
||||||
|
const url = 'https://example.com/api?password=pwd&token=tkn&api_key=key&secret=sec&session=sess';
|
||||||
|
const sanitized = sanitizeUrl(url);
|
||||||
|
expect(sanitized).toContain('%5BFiltered%5D');
|
||||||
|
const filteredCount = (sanitized.match(/%5BFiltered%5D/g) || []).length;
|
||||||
|
expect(filteredCount).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep non-sensitive params unchanged', () => {
|
||||||
|
const url = 'https://example.com/api?page=1&limit=10&sort=desc';
|
||||||
|
const sanitized = sanitizeUrl(url);
|
||||||
|
expect(sanitized).toBe(url);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('shouldIgnoreUrl', () => {
|
describe('shouldIgnoreUrl', () => {
|
||||||
|
|
@ -153,5 +372,18 @@ describe('env utils', () => {
|
||||||
const result = shouldIgnoreUrl('https://example.com/api/users/123', [/api\/users\/\d+/]);
|
const result = shouldIgnoreUrl('https://example.com/api/users/123', [/api\/users\/\d+/]);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should match multiple patterns (OR logic)', () => {
|
||||||
|
const patterns = ['/health', '/metrics', /\/debug/];
|
||||||
|
expect(shouldIgnoreUrl('https://example.com/health', patterns)).toBe(true);
|
||||||
|
expect(shouldIgnoreUrl('https://example.com/api/metrics', patterns)).toBe(true);
|
||||||
|
expect(shouldIgnoreUrl('https://example.com/debug/vars', patterns)).toBe(true);
|
||||||
|
expect(shouldIgnoreUrl('https://example.com/api/users', patterns)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match wildcard with multiple *', () => {
|
||||||
|
const result = shouldIgnoreUrl('https://api.example.com/v1/users/123/info', ['*.example.com/*/users/*']);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
FIELD_ENCODE_MAP,
|
||||||
|
FIELD_DECODE_MAP,
|
||||||
|
encodeFields,
|
||||||
|
decodeFields,
|
||||||
|
} from '../src/utils/field-encoder';
|
||||||
|
|
||||||
|
describe('field-encoder', () => {
|
||||||
|
describe('FIELD_ENCODE_MAP', () => {
|
||||||
|
it('should contain mappings for all basic fields', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.event_id).toBe('eid');
|
||||||
|
expect(FIELD_ENCODE_MAP.timestamp).toBe('ts');
|
||||||
|
expect(FIELD_ENCODE_MAP.start_timestamp).toBe('sts');
|
||||||
|
expect(FIELD_ENCODE_MAP.type).toBe('t');
|
||||||
|
expect(FIELD_ENCODE_MAP.level).toBe('l');
|
||||||
|
expect(FIELD_ENCODE_MAP.message).toBe('m');
|
||||||
|
expect(FIELD_ENCODE_MAP.platform).toBe('p');
|
||||||
|
expect(FIELD_ENCODE_MAP.release).toBe('r');
|
||||||
|
expect(FIELD_ENCODE_MAP.environment).toBe('e');
|
||||||
|
expect(FIELD_ENCODE_MAP.fingerprint).toBe('fp');
|
||||||
|
expect(FIELD_ENCODE_MAP.context_id).toBe('cid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for user fields', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.user).toBe('u');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for tags', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.tags).toBe('tg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for exception', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.exception).toBe('ex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for request', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.request).toBe('req');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for contexts', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.contexts).toBe('ctx');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for breadcrumbs', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.breadcrumbs).toBe('bc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for transaction', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.transaction).toBe('tx');
|
||||||
|
expect(FIELD_ENCODE_MAP.duration).toBe('d');
|
||||||
|
expect(FIELD_ENCODE_MAP.spans).toBe('sp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain mappings for extra', () => {
|
||||||
|
expect(FIELD_ENCODE_MAP.extra).toBe('xt');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FIELD_DECODE_MAP', () => {
|
||||||
|
it('should be reverse of FIELD_ENCODE_MAP', () => {
|
||||||
|
for (const [key, value] of Object.entries(FIELD_ENCODE_MAP)) {
|
||||||
|
expect(FIELD_DECODE_MAP[value]).toBe(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode short codes back to original field names', () => {
|
||||||
|
expect(FIELD_DECODE_MAP['eid']).toBe('event_id');
|
||||||
|
expect(FIELD_DECODE_MAP['ts']).toBe('timestamp');
|
||||||
|
expect(FIELD_DECODE_MAP['t']).toBe('type');
|
||||||
|
expect(FIELD_DECODE_MAP['l']).toBe('level');
|
||||||
|
expect(FIELD_DECODE_MAP['m']).toBe('message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encodeFields', () => {
|
||||||
|
it('should encode basic fields to short codes', () => {
|
||||||
|
const event = {
|
||||||
|
event_id: 'abc123',
|
||||||
|
timestamp: '2024-01-01T00:00:00.000Z',
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
message: 'Test error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoded = encodeFields(event);
|
||||||
|
|
||||||
|
expect(encoded.eid).toBe('abc123');
|
||||||
|
expect(encoded.ts).toBe('2024-01-01T00:00:00.000Z');
|
||||||
|
expect(encoded.t).toBe('error');
|
||||||
|
expect(encoded.l).toBe('error');
|
||||||
|
expect(encoded.m).toBe('Test error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep unknown fields unchanged', () => {
|
||||||
|
const event = {
|
||||||
|
event_id: 'abc123',
|
||||||
|
custom_field: 'custom_value',
|
||||||
|
another_field: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoded = encodeFields(event);
|
||||||
|
|
||||||
|
expect(encoded.eid).toBe('abc123');
|
||||||
|
expect(encoded.custom_field).toBe('custom_value');
|
||||||
|
expect(encoded.another_field).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return non-object input unchanged', () => {
|
||||||
|
expect(encodeFields(null as any)).toBe(null);
|
||||||
|
expect(encodeFields(undefined as any)).toBe(undefined);
|
||||||
|
expect(encodeFields(123 as any)).toBe(123);
|
||||||
|
expect(encodeFields('string' as any)).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty object', () => {
|
||||||
|
const encoded = encodeFields({});
|
||||||
|
expect(encoded).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode nested objects without modification', () => {
|
||||||
|
const event = {
|
||||||
|
event_id: 'abc123',
|
||||||
|
user: { id: 'user1', name: 'Test' },
|
||||||
|
contexts: { trace: { trace_id: 'xyz' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoded = encodeFields(event);
|
||||||
|
|
||||||
|
expect(encoded.eid).toBe('abc123');
|
||||||
|
// user is encoded to 'u', contexts is encoded to 'ctx'
|
||||||
|
expect(encoded.u).toEqual({ id: 'user1', name: 'Test' });
|
||||||
|
expect(encoded.ctx).toEqual({ trace: { trace_id: 'xyz' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode transaction fields', () => {
|
||||||
|
const transaction = {
|
||||||
|
type: 'transaction',
|
||||||
|
transaction: 'test-transaction',
|
||||||
|
duration: 1000,
|
||||||
|
spans: [],
|
||||||
|
start_timestamp: '2024-01-01T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoded = encodeFields(transaction);
|
||||||
|
|
||||||
|
expect(encoded.t).toBe('transaction');
|
||||||
|
expect(encoded.tx).toBe('test-transaction');
|
||||||
|
expect(encoded.d).toBe(1000);
|
||||||
|
expect(encoded.sp).toEqual([]);
|
||||||
|
expect(encoded.sts).toBe('2024-01-01T00:00:00.000Z');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('decodeFields', () => {
|
||||||
|
it('should decode short codes back to original field names', () => {
|
||||||
|
const event = {
|
||||||
|
eid: 'abc123',
|
||||||
|
ts: '2024-01-01T00:00:00.000Z',
|
||||||
|
t: 'error',
|
||||||
|
l: 'error',
|
||||||
|
m: 'Test error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const decoded = decodeFields(event);
|
||||||
|
|
||||||
|
expect(decoded.event_id).toBe('abc123');
|
||||||
|
expect(decoded.timestamp).toBe('2024-01-01T00:00:00.000Z');
|
||||||
|
expect(decoded.type).toBe('error');
|
||||||
|
expect(decoded.level).toBe('error');
|
||||||
|
expect(decoded.message).toBe('Test error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep unknown fields unchanged', () => {
|
||||||
|
const event = {
|
||||||
|
eid: 'abc123',
|
||||||
|
custom_field: 'custom_value',
|
||||||
|
another_field: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
const decoded = decodeFields(event);
|
||||||
|
|
||||||
|
expect(decoded.event_id).toBe('abc123');
|
||||||
|
expect(decoded.custom_field).toBe('custom_value');
|
||||||
|
expect(decoded.another_field).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return non-object input unchanged', () => {
|
||||||
|
expect(decodeFields(null as any)).toBe(null);
|
||||||
|
expect(decodeFields(undefined as any)).toBe(undefined);
|
||||||
|
expect(decodeFields(123 as any)).toBe(123);
|
||||||
|
expect(decodeFields('string' as any)).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty object', () => {
|
||||||
|
const decoded = decodeFields({});
|
||||||
|
expect(decoded).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode nested objects without modification', () => {
|
||||||
|
const event = {
|
||||||
|
eid: 'abc123',
|
||||||
|
u: { id: 'user1', name: 'Test' },
|
||||||
|
ctx: { trace: { trace_id: 'xyz' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const decoded = decodeFields(event);
|
||||||
|
|
||||||
|
expect(decoded.event_id).toBe('abc123');
|
||||||
|
expect(decoded.user).toEqual({ id: 'user1', name: 'Test' });
|
||||||
|
expect(decoded.contexts).toEqual({ trace: { trace_id: 'xyz' } });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encode/decode round-trip', () => {
|
||||||
|
it('should preserve data through encode/decode cycle', () => {
|
||||||
|
const original = {
|
||||||
|
event_id: 'abc123',
|
||||||
|
timestamp: '2024-01-01T00:00:00.000Z',
|
||||||
|
type: 'error',
|
||||||
|
level: 'error',
|
||||||
|
message: 'Test error',
|
||||||
|
user: { id: 'user1' },
|
||||||
|
tags: { env: 'prod' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoded = encodeFields(original);
|
||||||
|
const decoded = decodeFields(encoded);
|
||||||
|
|
||||||
|
expect(decoded.event_id).toBe(original.event_id);
|
||||||
|
expect(decoded.timestamp).toBe(original.timestamp);
|
||||||
|
expect(decoded.type).toBe(original.type);
|
||||||
|
expect(decoded.level).toBe(original.level);
|
||||||
|
expect(decoded.message).toBe(original.message);
|
||||||
|
expect(decoded.user).toEqual(original.user);
|
||||||
|
expect(decoded.tags).toEqual(original.tags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
generateEventId,
|
||||||
|
now,
|
||||||
|
uuid,
|
||||||
|
isFunction,
|
||||||
|
isString,
|
||||||
|
isObject,
|
||||||
|
safeGet,
|
||||||
|
truncate,
|
||||||
|
matchPatterns,
|
||||||
|
} from '../src/utils/helper';
|
||||||
|
|
||||||
|
describe('helper utils', () => {
|
||||||
|
describe('generateEventId', () => {
|
||||||
|
it('should generate 32 character hex string', () => {
|
||||||
|
const id = generateEventId();
|
||||||
|
expect(id).toMatch(/^[a-f0-9]{32}$/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate unique IDs', () => {
|
||||||
|
const id1 = generateEventId();
|
||||||
|
const id2 = generateEventId();
|
||||||
|
expect(id1).not.toBe(id2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('now', () => {
|
||||||
|
it('should return current timestamp in milliseconds', () => {
|
||||||
|
const before = Date.now();
|
||||||
|
const timestamp = now();
|
||||||
|
const after = Date.now();
|
||||||
|
expect(timestamp).toBeGreaterThanOrEqual(before);
|
||||||
|
expect(timestamp).toBeLessThanOrEqual(after);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uuid', () => {
|
||||||
|
it('should generate 32 character hex string (UUID without hyphens)', () => {
|
||||||
|
const id = uuid();
|
||||||
|
expect(id).toMatch(/^[a-f0-9]{32}$/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate unique UUIDs', () => {
|
||||||
|
const id1 = uuid();
|
||||||
|
const id2 = uuid();
|
||||||
|
expect(id1).not.toBe(id2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isFunction', () => {
|
||||||
|
it('should return true for functions', () => {
|
||||||
|
expect(isFunction(() => {})).toBe(true);
|
||||||
|
expect(isFunction(async () => {})).toBe(true);
|
||||||
|
expect(isFunction(function() {})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-functions', () => {
|
||||||
|
expect(isFunction('string')).toBe(false);
|
||||||
|
expect(isFunction(123)).toBe(false);
|
||||||
|
expect(isFunction(null)).toBe(false);
|
||||||
|
expect(isFunction(undefined)).toBe(false);
|
||||||
|
expect(isFunction({})).toBe(false);
|
||||||
|
expect(isFunction([])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isString', () => {
|
||||||
|
it('should return true for strings', () => {
|
||||||
|
expect(isString('')).toBe(true);
|
||||||
|
expect(isString('hello')).toBe(true);
|
||||||
|
expect(isString(String(123))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-strings', () => {
|
||||||
|
expect(isString(123)).toBe(false);
|
||||||
|
expect(isString(null)).toBe(false);
|
||||||
|
expect(isString(undefined)).toBe(false);
|
||||||
|
expect(isString({})).toBe(false);
|
||||||
|
expect(isString([])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isObject', () => {
|
||||||
|
it('should return true for objects', () => {
|
||||||
|
expect(isObject({})).toBe(true);
|
||||||
|
expect(isObject({ a: 1 })).toBe(true);
|
||||||
|
expect(isObject(new Object())).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-objects', () => {
|
||||||
|
expect(isObject(null)).toBe(false);
|
||||||
|
expect(isObject([])).toBe(false);
|
||||||
|
expect(isObject('string')).toBe(false);
|
||||||
|
expect(isObject(123)).toBe(false);
|
||||||
|
expect(isObject(undefined)).toBe(false);
|
||||||
|
expect(isObject(() => {})).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('safeGet', () => {
|
||||||
|
it('should return result of function when successful', () => {
|
||||||
|
const result = safeGet(() => 42, 0);
|
||||||
|
expect(result).toBe(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return default value when function throws', () => {
|
||||||
|
const result = safeGet(() => { throw new Error('test'); }, 'default');
|
||||||
|
expect(result).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return default value when function returns undefined', () => {
|
||||||
|
const result = safeGet(() => undefined, 'default');
|
||||||
|
expect(result).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when function returns null (null is a valid value)', () => {
|
||||||
|
const result = safeGet(() => null, 'default');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow falsy values that are not undefined', () => {
|
||||||
|
expect(safeGet(() => 0, 100)).toBe(0);
|
||||||
|
expect(safeGet(() => '', 'default')).toBe('');
|
||||||
|
expect(safeGet(() => false, true)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('truncate', () => {
|
||||||
|
it('should return original string if shorter than max length', () => {
|
||||||
|
expect(truncate('hello', 10)).toBe('hello');
|
||||||
|
expect(truncate('hello', 5)).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should truncate string longer than max length', () => {
|
||||||
|
expect(truncate('hello world', 5)).toBe('hello...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty string', () => {
|
||||||
|
expect(truncate('', 10)).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle exact length', () => {
|
||||||
|
expect(truncate('hello', 5)).toBe('hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('matchPatterns', () => {
|
||||||
|
it('should return false for empty patterns', () => {
|
||||||
|
expect(matchPatterns('test', [])).toBe(false);
|
||||||
|
expect(matchPatterns('test', undefined as any)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match string patterns', () => {
|
||||||
|
expect(matchPatterns('error: something went wrong', ['error'])).toBe(true);
|
||||||
|
expect(matchPatterns('something went wrong', ['error'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match multiple string patterns (OR)', () => {
|
||||||
|
expect(matchPatterns('warning: low memory', ['error', 'warning'])).toBe(true);
|
||||||
|
expect(matchPatterns('info: normal', ['error', 'warning'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match RegExp patterns', () => {
|
||||||
|
expect(matchPatterns('Error: validation failed', [/Error/])).toBe(true);
|
||||||
|
expect(matchPatterns('error: validation failed', [/validation/])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match combined string and RegExp patterns', () => {
|
||||||
|
expect(matchPatterns('WARNING: high CPU', ['error', /WARNING/])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle special regex characters in string patterns', () => {
|
||||||
|
expect(matchPatterns('test.file.txt', ['test.file'])).toBe(true);
|
||||||
|
expect(matchPatterns('test123', ['test(123)'])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import {
|
||||||
|
getAnonymousId,
|
||||||
|
setAnonymousId,
|
||||||
|
clearAnonymousId,
|
||||||
|
isAnonymousTrackingEnabled,
|
||||||
|
} from '../src/utils/identity';
|
||||||
|
|
||||||
|
const ANONYMOUS_ID_KEY = 'light_sentry_aid';
|
||||||
|
|
||||||
|
describe('identity utils', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAnonymousId', () => {
|
||||||
|
it('should generate and store a new anonymous ID when none exists', () => {
|
||||||
|
const id = getAnonymousId();
|
||||||
|
expect(id).toBeTruthy();
|
||||||
|
expect(typeof id).toBe('string');
|
||||||
|
expect(id!.length).toBeGreaterThan(0);
|
||||||
|
const stored = localStorage.getItem(ANONYMOUS_ID_KEY);
|
||||||
|
expect(stored).toBe(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same ID on subsequent calls', () => {
|
||||||
|
const id1 = getAnonymousId();
|
||||||
|
const id2 = getAnonymousId();
|
||||||
|
expect(id1).toBe(id2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return existing ID from localStorage', () => {
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, 'existing-id-123');
|
||||||
|
const id = getAnonymousId();
|
||||||
|
expect(id).toBe('existing-id-123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when localStorage is not available', () => {
|
||||||
|
const originalLocalStorage = (global as any).localStorage;
|
||||||
|
delete (global as any).localStorage;
|
||||||
|
const id = getAnonymousId();
|
||||||
|
expect(id).toBeNull();
|
||||||
|
(global as any).localStorage = originalLocalStorage;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when localStorage throws', () => {
|
||||||
|
const getItemSpy = vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
||||||
|
throw new Error('Storage error');
|
||||||
|
});
|
||||||
|
const id = getAnonymousId();
|
||||||
|
expect(id).toBeNull();
|
||||||
|
getItemSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate UUID format (32 hex chars without hyphens)', () => {
|
||||||
|
const id = getAnonymousId();
|
||||||
|
expect(id).toMatch(/^[a-f0-9]{32}$/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setAnonymousId', () => {
|
||||||
|
it('should set custom anonymous ID', () => {
|
||||||
|
const result = setAnonymousId('custom-id-123');
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(localStorage.getItem(ANONYMOUS_ID_KEY)).toBe('custom-id-123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override existing ID', () => {
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, 'old-id');
|
||||||
|
setAnonymousId('new-id');
|
||||||
|
expect(localStorage.getItem(ANONYMOUS_ID_KEY)).toBe('new-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when localStorage is not available', () => {
|
||||||
|
const originalLocalStorage = (global as any).localStorage;
|
||||||
|
delete (global as any).localStorage;
|
||||||
|
const result = setAnonymousId('test-id');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
(global as any).localStorage = originalLocalStorage;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when localStorage throws', () => {
|
||||||
|
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
|
||||||
|
throw new Error('Storage error');
|
||||||
|
});
|
||||||
|
const result = setAnonymousId('test-id');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
setItemSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set empty string ID', () => {
|
||||||
|
const result = setAnonymousId('');
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(localStorage.getItem(ANONYMOUS_ID_KEY)).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clearAnonymousId', () => {
|
||||||
|
it('should clear existing anonymous ID', () => {
|
||||||
|
localStorage.setItem(ANONYMOUS_ID_KEY, 'existing-id');
|
||||||
|
const result = clearAnonymousId();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(localStorage.getItem(ANONYMOUS_ID_KEY)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true even when no ID exists', () => {
|
||||||
|
const result = clearAnonymousId();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when localStorage is not available', () => {
|
||||||
|
const originalLocalStorage = (global as any).localStorage;
|
||||||
|
delete (global as any).localStorage;
|
||||||
|
const result = clearAnonymousId();
|
||||||
|
expect(result).toBe(false);
|
||||||
|
(global as any).localStorage = originalLocalStorage;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when localStorage throws', () => {
|
||||||
|
const removeItemSpy = vi.spyOn(Storage.prototype, 'removeItem').mockImplementation(() => {
|
||||||
|
throw new Error('Storage error');
|
||||||
|
});
|
||||||
|
const result = clearAnonymousId();
|
||||||
|
expect(result).toBe(false);
|
||||||
|
removeItemSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isAnonymousTrackingEnabled', () => {
|
||||||
|
it('should return true when trackAnonymousUsers is undefined', () => {
|
||||||
|
expect(isAnonymousTrackingEnabled({})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when trackAnonymousUsers is true', () => {
|
||||||
|
expect(isAnonymousTrackingEnabled({ trackAnonymousUsers: true })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when trackAnonymousUsers is false', () => {
|
||||||
|
expect(isAnonymousTrackingEnabled({ trackAnonymousUsers: false })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for other truthy values', () => {
|
||||||
|
expect(isAnonymousTrackingEnabled({ trackAnonymousUsers: 1 as any })).toBe(true);
|
||||||
|
expect(isAnonymousTrackingEnabled({ trackAnonymousUsers: 'yes' as any })).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('integration: full lifecycle', () => {
|
||||||
|
it('should handle set -> get -> clear flow', () => {
|
||||||
|
const id1 = getAnonymousId();
|
||||||
|
expect(id1).toBeTruthy();
|
||||||
|
|
||||||
|
setAnonymousId('custom-user-id');
|
||||||
|
const id2 = getAnonymousId();
|
||||||
|
expect(id2).toBe('custom-user-id');
|
||||||
|
|
||||||
|
clearAnonymousId();
|
||||||
|
const id3 = getAnonymousId();
|
||||||
|
expect(id3).toBeTruthy();
|
||||||
|
expect(id3).not.toBe('custom-user-id');
|
||||||
|
expect(id3).not.toBe(id1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import * as exports from '../src/index';
|
||||||
|
|
||||||
|
describe('index exports', () => {
|
||||||
|
describe('named exports', () => {
|
||||||
|
it('should export init function', () => {
|
||||||
|
expect(typeof exports.init).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export getClient function', () => {
|
||||||
|
expect(typeof exports.getClient).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export captureException function', () => {
|
||||||
|
expect(typeof exports.captureException).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export captureMessage function', () => {
|
||||||
|
expect(typeof exports.captureMessage).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export captureEvent function', () => {
|
||||||
|
expect(typeof exports.captureEvent).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setUser function', () => {
|
||||||
|
expect(typeof exports.setUser).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setAnonymousId function', () => {
|
||||||
|
expect(typeof exports.setAnonymousId).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export clearAnonymousId function', () => {
|
||||||
|
expect(typeof exports.clearAnonymousId).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export getAnonymousId function', () => {
|
||||||
|
expect(typeof exports.getAnonymousId).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export startTransaction function', () => {
|
||||||
|
expect(typeof exports.startTransaction).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export getCurrentTransaction function', () => {
|
||||||
|
expect(typeof exports.getCurrentTransaction).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export getCurrentSpan function', () => {
|
||||||
|
expect(typeof exports.getCurrentSpan).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setSpan function', () => {
|
||||||
|
expect(typeof exports.setSpan).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export startSpan function', () => {
|
||||||
|
expect(typeof exports.startSpan).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setTag function', () => {
|
||||||
|
expect(typeof exports.setTag).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setTags function', () => {
|
||||||
|
expect(typeof exports.setTags).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export setExtra function', () => {
|
||||||
|
expect(typeof exports.setExtra).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export addBreadcrumb function', () => {
|
||||||
|
expect(typeof exports.addBreadcrumb).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export flush function', () => {
|
||||||
|
expect(typeof exports.flush).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export disable function', () => {
|
||||||
|
expect(typeof exports.disable).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export enable function', () => {
|
||||||
|
expect(typeof exports.enable).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export Client class', () => {
|
||||||
|
expect(typeof exports.Client).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export Span class', () => {
|
||||||
|
expect(typeof exports.Span).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export Transaction class', () => {
|
||||||
|
expect(typeof exports.Transaction).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export httpStatusToSpanStatus function', () => {
|
||||||
|
expect(typeof exports.httpStatusToSpanStatus).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export dbStatusToSpanStatus function', () => {
|
||||||
|
expect(typeof exports.dbStatusToSpanStatus).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export statusToSpanStatus function', () => {
|
||||||
|
expect(typeof exports.statusToSpanStatus).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export serializeBaggage function', () => {
|
||||||
|
expect(typeof exports.serializeBaggage).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export parseBaggage function', () => {
|
||||||
|
expect(typeof exports.parseBaggage).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export createBaggageFromTransaction function', () => {
|
||||||
|
expect(typeof exports.createBaggageFromTransaction).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('plugin exports', () => {
|
||||||
|
it('should export ErrorPlugin class', () => {
|
||||||
|
expect(typeof exports.ErrorPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export PerformancePlugin class', () => {
|
||||||
|
expect(typeof exports.PerformancePlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export NetworkPlugin class', () => {
|
||||||
|
expect(typeof exports.NetworkPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export BehaviorPlugin class', () => {
|
||||||
|
expect(typeof exports.BehaviorPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export OfflinePlugin class', () => {
|
||||||
|
expect(typeof exports.OfflinePlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export SamplingPlugin class', () => {
|
||||||
|
expect(typeof exports.SamplingPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export RouterPlugin class', () => {
|
||||||
|
expect(typeof exports.RouterPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export InteractionPlugin class', () => {
|
||||||
|
expect(typeof exports.InteractionPlugin).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('default export', () => {
|
||||||
|
it('should export default object with core functions', () => {
|
||||||
|
const d = exports.default;
|
||||||
|
expect(d).toBeDefined();
|
||||||
|
expect(typeof d.init).toBe('function');
|
||||||
|
expect(typeof d.getClient).toBe('function');
|
||||||
|
expect(typeof d.captureException).toBe('function');
|
||||||
|
expect(typeof d.captureMessage).toBe('function');
|
||||||
|
expect(typeof d.captureEvent).toBe('function');
|
||||||
|
expect(typeof d.setUser).toBe('function');
|
||||||
|
expect(typeof d.setAnonymousId).toBe('function');
|
||||||
|
expect(typeof d.clearAnonymousId).toBe('function');
|
||||||
|
expect(typeof d.getAnonymousId).toBe('function');
|
||||||
|
expect(typeof d.startTransaction).toBe('function');
|
||||||
|
expect(typeof d.getCurrentTransaction).toBe('function');
|
||||||
|
expect(typeof d.getCurrentSpan).toBe('function');
|
||||||
|
expect(typeof d.setSpan).toBe('function');
|
||||||
|
expect(typeof d.startSpan).toBe('function');
|
||||||
|
expect(typeof d.setTag).toBe('function');
|
||||||
|
expect(typeof d.setTags).toBe('function');
|
||||||
|
expect(typeof d.setExtra).toBe('function');
|
||||||
|
expect(typeof d.addBreadcrumb).toBe('function');
|
||||||
|
expect(typeof d.flush).toBe('function');
|
||||||
|
expect(typeof d.disable).toBe('function');
|
||||||
|
expect(typeof d.enable).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export default object with Span and Transaction classes', () => {
|
||||||
|
const d = exports.default;
|
||||||
|
expect(typeof d.Span).toBe('function');
|
||||||
|
expect(typeof d.Transaction).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export default object with utility functions', () => {
|
||||||
|
const d = exports.default;
|
||||||
|
expect(typeof d.httpStatusToSpanStatus).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
setDebug,
|
||||||
|
isDebugEnabled,
|
||||||
|
logDebug,
|
||||||
|
logError,
|
||||||
|
} from '../src/utils/logger';
|
||||||
|
|
||||||
|
describe('logger', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setDebug(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setDebug', () => {
|
||||||
|
it('should enable debug mode', () => {
|
||||||
|
expect(isDebugEnabled()).toBe(false);
|
||||||
|
setDebug(true);
|
||||||
|
expect(isDebugEnabled()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable debug mode', () => {
|
||||||
|
setDebug(true);
|
||||||
|
expect(isDebugEnabled()).toBe(true);
|
||||||
|
setDebug(false);
|
||||||
|
expect(isDebugEnabled()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isDebugEnabled', () => {
|
||||||
|
it('should return current debug state', () => {
|
||||||
|
expect(isDebugEnabled()).toBe(false);
|
||||||
|
setDebug(true);
|
||||||
|
expect(isDebugEnabled()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logDebug', () => {
|
||||||
|
it('should not log when debug is disabled', () => {
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Test', 'This should not appear');
|
||||||
|
expect(consoleSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log message when debug is enabled', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Test', 'This should appear');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log message with data when debug is enabled', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Test', 'Message with data', { key: 'value', num: 123 });
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
const loggedMessage = consoleSpy.mock.calls[0].join(' ');
|
||||||
|
expect(loggedMessage).toContain('[LightSDK][Test]');
|
||||||
|
expect(loggedMessage).toContain('Message with data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include timestamp in log message', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Module', 'Test message');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
const loggedMessage = consoleSpy.mock.calls[0][0];
|
||||||
|
expect(loggedMessage).toMatch(/^\[LightSDK\]\[Module\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle message without data', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Module', 'Simple message');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined data', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Module', 'Message', undefined);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null data', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
logDebug('Module', 'Message', null);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex data objects', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const complexData = {
|
||||||
|
nested: { deeply: { value: 123 } },
|
||||||
|
array: [1, 2, 3],
|
||||||
|
bool: true,
|
||||||
|
null: null,
|
||||||
|
};
|
||||||
|
logDebug('Module', 'Complex data', complexData);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logError', () => {
|
||||||
|
it('should not log when debug is disabled', () => {
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Test', 'This should not appear');
|
||||||
|
expect(consoleSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log error message when debug is enabled', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Test', 'This error should appear');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include ERROR tag in log message', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Module', 'Error message');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
const loggedMessage = consoleSpy.mock.calls[0][0];
|
||||||
|
expect(loggedMessage).toContain('[LightSDK][Module][ERROR]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log error with error data', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
const error = new Error('Test error');
|
||||||
|
logError('Module', 'Error occurred', error);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle plain object error', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
const errorObj = { status: 404, message: 'Not found' };
|
||||||
|
logError('Module', 'Request failed', errorObj);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle error message without data', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Module', 'Simple error');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle string error', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Module', 'String error', 'error details');
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined error', () => {
|
||||||
|
setDebug(true);
|
||||||
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
logError('Module', 'Error without details', undefined);
|
||||||
|
expect(consoleSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('debug toggle behavior', () => {
|
||||||
|
it('should toggle between enabled and disabled', () => {
|
||||||
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
setDebug(true);
|
||||||
|
logDebug('Test', 'Debug on');
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
setDebug(false);
|
||||||
|
logDebug('Test', 'Debug off');
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
setDebug(true);
|
||||||
|
logError('Test', 'Error on');
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
setDebug(false);
|
||||||
|
logError('Test', 'Error off');
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,360 @@
|
||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { parseStackTrace } from '../src/utils/stacktrace';
|
||||||
|
|
||||||
|
describe('stacktrace', () => {
|
||||||
|
// Mock location for jsdom environment
|
||||||
|
beforeEach(() => {
|
||||||
|
delete (global as any).location;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseStackTrace', () => {
|
||||||
|
it('should return empty array for undefined stack', () => {
|
||||||
|
expect(parseStackTrace(undefined)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array for empty string', () => {
|
||||||
|
expect(parseStackTrace('')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse standard Chrome/Node.js stack trace format', () => {
|
||||||
|
// Stack trace: newest call first (Function1), then Function2, then Function3
|
||||||
|
// After reverse, order becomes Function3, Function2, Function1
|
||||||
|
const stack = `Error
|
||||||
|
at Function3 (http://example.com/js/index.js:30:5)
|
||||||
|
at Function2 (http://example.com/js/main.js:25:10)
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(3);
|
||||||
|
// After reverse, Function1 should be first (oldest call in the trace)
|
||||||
|
expect(frames[0]).toEqual({
|
||||||
|
filename: 'http://example.com/js/app.js',
|
||||||
|
function: 'Function1',
|
||||||
|
lineno: 10,
|
||||||
|
colno: 15,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
expect(frames[1]).toEqual({
|
||||||
|
filename: 'http://example.com/js/main.js',
|
||||||
|
function: 'Function2',
|
||||||
|
lineno: 25,
|
||||||
|
colno: 10,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
expect(frames[2]).toEqual({
|
||||||
|
filename: 'http://example.com/js/index.js',
|
||||||
|
function: 'Function3',
|
||||||
|
lineno: 30,
|
||||||
|
colno: 5,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse V8 format with @ separator', () => {
|
||||||
|
// V8 format requires 'at' prefix: at Function1@path:line:col
|
||||||
|
const stack = `Error
|
||||||
|
at Function1@http://example.com/js/app.js:10:15
|
||||||
|
at Function2@http://example.com/js/main.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
// After reverse, order is reversed
|
||||||
|
expect(frames[0]).toEqual({
|
||||||
|
filename: 'http://example.com/js/main.js',
|
||||||
|
function: 'Function2',
|
||||||
|
lineno: 25,
|
||||||
|
colno: 10,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
expect(frames[1]).toEqual({
|
||||||
|
filename: 'http://example.com/js/app.js',
|
||||||
|
function: 'Function1',
|
||||||
|
lineno: 10,
|
||||||
|
colno: 15,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse URL-only format (no function name)', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at http://example.com/js/app.js:10:15
|
||||||
|
at http://example.com/js/main.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0]).toEqual({
|
||||||
|
filename: 'http://example.com/js/main.js',
|
||||||
|
lineno: 25,
|
||||||
|
colno: 10,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
expect(frames[1]).toEqual({
|
||||||
|
filename: 'http://example.com/js/app.js',
|
||||||
|
lineno: 10,
|
||||||
|
colno: 15,
|
||||||
|
in_app: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mark node_modules frames as in_app: false when captured', () => {
|
||||||
|
// When captureNodeModules is true, node_modules frames are included
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/node_modules/dep.js:10:15)
|
||||||
|
at Function2 (http://example.com/js/app.js:25:10)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: true });
|
||||||
|
|
||||||
|
// After reverse, order is Function2, Function1
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].in_app).toBe(true); // Function2 (app code)
|
||||||
|
expect(frames[1].in_app).toBe(false); // Function1 (node_modules)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip node_modules frames when captureNodeModules is false', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/node_modules/dep.js:10:15)
|
||||||
|
at Function2 (http://example.com/js/app.js:25:10)
|
||||||
|
at Function3 (http://example.com/node_modules/other.js:30:5)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: false });
|
||||||
|
|
||||||
|
// After reverse: Function3, Function2, Function1, then filter node_modules
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].function).toBe('Function2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include node_modules when captureNodeModules is true', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/node_modules/dep.js:10:15)
|
||||||
|
at Function2 (http://example.com/js/app.js:25:10)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: true });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include column when captureColumn is false', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureColumn: false });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].colno).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not apply relativePathOnly when origin does not match', () => {
|
||||||
|
// In jsdom, location.origin would be 'http://localhost:3000' or similar
|
||||||
|
// so example.com URLs won't match
|
||||||
|
const stack = `Error
|
||||||
|
at Function2 (http://other.com/js/main.js:25:10)
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { relativePathOnly: true });
|
||||||
|
|
||||||
|
// URLs don't match location.origin, so they remain unchanged
|
||||||
|
// After reverse: Function1, Function2
|
||||||
|
expect(frames[0].filename).toBe('http://example.com/js/app.js');
|
||||||
|
expect(frames[1].filename).toBe('http://other.com/js/main.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should limit frames with maxFrames option', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function4 (http://example.com/js/index.js:40:0)
|
||||||
|
at Function3 (http://example.com/js/util.js:35:0)
|
||||||
|
at Function2 (http://example.com/js/main.js:25:10)
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)`;
|
||||||
|
|
||||||
|
// After reverse: Function1, Function2, Function3, Function4
|
||||||
|
// maxFrames=2 returns first 2: Function1, Function2
|
||||||
|
const frames = parseStackTrace(stack, { maxFrames: 2 });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].function).toBe('Function1');
|
||||||
|
expect(frames[1].function).toBe('Function2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all frames when maxFrames is 0 or undefined', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)
|
||||||
|
at Function2 (http://example.com/js/main.js:25:10)`;
|
||||||
|
|
||||||
|
const frames1 = parseStackTrace(stack, { maxFrames: 0 });
|
||||||
|
const frames2 = parseStackTrace(stack, { maxFrames: undefined });
|
||||||
|
|
||||||
|
expect(frames1).toHaveLength(2);
|
||||||
|
expect(frames2).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle anonymous functions in V8 format', () => {
|
||||||
|
// V8 format requires 'at' prefix: at <anonymous>@path:line:col
|
||||||
|
const stack = `Error
|
||||||
|
at <anonymous>@http://example.com/js/app.js:10:15`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].function).toBe('<anonymous>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle stack with extra whitespace', () => {
|
||||||
|
// Note: complex whitespace may cause regex to fail, test with standard format
|
||||||
|
const stack = `Error
|
||||||
|
at Function2 (http://example.com/js/main.js:25:10)
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
// After reverse: Function1, Function2
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].function).toBe('Function1');
|
||||||
|
expect(frames[1].function).toBe('Function2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file:// URLs', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (file:///Users/name/project/app.js:10:15)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].filename).toBe('file:///Users/name/project/app.js');
|
||||||
|
expect(frames[0].in_app).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed format stacks', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)
|
||||||
|
at Function2@http://example.com/js/main.js:25:10
|
||||||
|
at http://example.com/js/index.js:30:5`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle V8 format with captureColumn: false', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1@http://example.com/js/app.js:10:15`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureColumn: false });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].colno).toBeUndefined();
|
||||||
|
expect(frames[0].function).toBe('Function1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle URL-only format with captureColumn: false', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at http://example.com/js/app.js:10:15`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureColumn: false });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].colno).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip node_modules in V8 format', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1@http://example.com/node_modules/dep.js:10:15
|
||||||
|
at Function2@http://example.com/js/app.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: false });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
expect(frames[0].function).toBe('Function2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip node_modules in URL-only format', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at http://example.com/node_modules/dep.js:10:15
|
||||||
|
at http://example.com/js/app.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: false });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include node_modules in V8 format when captureNodeModules is true', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at Function1@http://example.com/node_modules/dep.js:10:15
|
||||||
|
at Function2@http://example.com/js/app.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: true });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].in_app).toBe(true);
|
||||||
|
expect(frames[1].in_app).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include node_modules in URL-only format when captureNodeModules is true', () => {
|
||||||
|
const stack = `Error
|
||||||
|
at http://example.com/node_modules/dep.js:10:15
|
||||||
|
at http://example.com/js/app.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { captureNodeModules: true });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply relativePathOnly to V8 format', () => {
|
||||||
|
// Set up location mock
|
||||||
|
const mockLocation = {
|
||||||
|
protocol: 'http:',
|
||||||
|
host: 'example.com',
|
||||||
|
};
|
||||||
|
(global as any).location = mockLocation;
|
||||||
|
|
||||||
|
const stack = `Error
|
||||||
|
at Function1@http://example.com/js/app.js:10:15
|
||||||
|
at Function2@http://example.com/js/main.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { relativePathOnly: true });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].filename).toBe('/js/main.js');
|
||||||
|
expect(frames[1].filename).toBe('/js/app.js');
|
||||||
|
|
||||||
|
delete (global as any).location;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply relativePathOnly to URL-only format', () => {
|
||||||
|
const mockLocation = {
|
||||||
|
protocol: 'http:',
|
||||||
|
host: 'example.com',
|
||||||
|
};
|
||||||
|
(global as any).location = mockLocation;
|
||||||
|
|
||||||
|
const stack = `Error
|
||||||
|
at http://example.com/js/app.js:10:15
|
||||||
|
at http://example.com/js/main.js:25:10`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack, { relativePathOnly: true });
|
||||||
|
|
||||||
|
expect(frames).toHaveLength(2);
|
||||||
|
expect(frames[0].filename).toBe('/js/main.js');
|
||||||
|
expect(frames[1].filename).toBe('/js/app.js');
|
||||||
|
|
||||||
|
delete (global as any).location;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return reversed frames (newest call first after reversal)', () => {
|
||||||
|
// In standard stack traces, the first line after "Error" is the most recent call
|
||||||
|
const stack = `Error
|
||||||
|
at Function1 (http://example.com/js/app.js:10:15)
|
||||||
|
at Function2 (http://example.com/js/main.js:25:10)
|
||||||
|
at Function3 (http://example.com/js/index.js:30:5)`;
|
||||||
|
|
||||||
|
const frames = parseStackTrace(stack);
|
||||||
|
|
||||||
|
// After reverse of [Function1, Function2, Function3], we get [Function3, Function2, Function1]
|
||||||
|
// This puts the oldest call first
|
||||||
|
expect(frames[0].function).toBe('Function3');
|
||||||
|
expect(frames[1].function).toBe('Function2');
|
||||||
|
expect(frames[2].function).toBe('Function1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { Span, Transaction, httpStatusToSpanStatus, dbStatusToSpanStatus, statusToSpanStatus } from '../src/utils/tracing';
|
||||||
|
import type { SpanContext, TransactionContext } from '../src/types';
|
||||||
|
|
||||||
|
describe('Span', () => {
|
||||||
|
let span: Span;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
span = new Span({ op: 'test.span' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create span with generated IDs when not provided', () => {
|
||||||
|
const newSpan = new Span({ op: 'test' });
|
||||||
|
expect(newSpan.traceId).toMatch(/^[a-f0-9]{32}$/i);
|
||||||
|
expect(newSpan.spanId).toMatch(/^[a-f0-9]{16}$/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided traceId and spanId', () => {
|
||||||
|
const newSpan = new Span({
|
||||||
|
op: 'test',
|
||||||
|
traceId: 'abc123abc123abc123abc123abc123ab',
|
||||||
|
spanId: 'abc123abc123abcd',
|
||||||
|
});
|
||||||
|
expect(newSpan.traceId).toBe('abc123abc123abc123abc123abc123ab');
|
||||||
|
expect(newSpan.spanId).toBe('abc123abc123abcd');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set parentSpanId from context', () => {
|
||||||
|
const childSpan = new Span({
|
||||||
|
op: 'child',
|
||||||
|
parentSpanId: 'parent-span-id',
|
||||||
|
});
|
||||||
|
expect(childSpan.parentSpanId).toBe('parent-span-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set op and description', () => {
|
||||||
|
const newSpan = new Span({
|
||||||
|
op: 'http.client',
|
||||||
|
description: 'GET /api/users',
|
||||||
|
});
|
||||||
|
expect(newSpan.op).toBe('http.client');
|
||||||
|
expect(newSpan.description).toBe('GET /api/users');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize with current timestamp', () => {
|
||||||
|
const before = Date.now();
|
||||||
|
const newSpan = new Span({ op: 'test' });
|
||||||
|
const after = Date.now();
|
||||||
|
expect(newSpan.startTimestamp).toBeGreaterThanOrEqual(before);
|
||||||
|
expect(newSpan.startTimestamp).toBeLessThanOrEqual(after);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided startTimestamp', () => {
|
||||||
|
const newSpan = new Span({
|
||||||
|
op: 'test',
|
||||||
|
startTimestamp: 1234567890000,
|
||||||
|
});
|
||||||
|
expect(newSpan.startTimestamp).toBe(1234567890000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setTag', () => {
|
||||||
|
it('should add tag to span', () => {
|
||||||
|
const result = span.setTag('http.method', 'GET');
|
||||||
|
expect(span.tags).toEqual({ 'http.method': 'GET' });
|
||||||
|
expect(result).toBe(span); // should return this for chaining
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should overwrite existing tag', () => {
|
||||||
|
span.setTag('http.method', 'GET');
|
||||||
|
span.setTag('http.method', 'POST');
|
||||||
|
expect(span.tags['http.method']).toBe('POST');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support chaining', () => {
|
||||||
|
const result = span.setTag('key1', 'value1').setTag('key2', 'value2');
|
||||||
|
expect(result).toBe(span);
|
||||||
|
expect(span.tags).toEqual({ key1: 'value1', key2: 'value2' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setData', () => {
|
||||||
|
it('should add data to span', () => {
|
||||||
|
span.setData('http.url', 'https://api.example.com/users');
|
||||||
|
expect(span.data).toEqual({ 'http.url': 'https://api.example.com/users' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support chaining', () => {
|
||||||
|
const result = span.setData('key1', 'value1').setData('key2', 'value2');
|
||||||
|
expect(result).toBe(span);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setStatus', () => {
|
||||||
|
it('should set status', () => {
|
||||||
|
span.setStatus('ok');
|
||||||
|
expect(span.status).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support chaining', () => {
|
||||||
|
const result = span.setStatus('ok');
|
||||||
|
expect(result).toBe(span);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setHttpStatus', () => {
|
||||||
|
it('should set status for 2xx', () => {
|
||||||
|
span.setHttpStatus(200);
|
||||||
|
expect(span.status).toBe('ok');
|
||||||
|
expect(span.tags['http.status_code']).toBe('200');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set status for 4xx', () => {
|
||||||
|
span.setHttpStatus(404);
|
||||||
|
expect(span.status).toBe('not_found');
|
||||||
|
expect(span.tags['http.status_code']).toBe('404');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set status for 5xx', () => {
|
||||||
|
span.setHttpStatus(500);
|
||||||
|
expect(span.status).toBe('internal_error');
|
||||||
|
expect(span.tags['http.status_code']).toBe('500');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support chaining', () => {
|
||||||
|
const result = span.setHttpStatus(200);
|
||||||
|
expect(result).toBe(span);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setStatusFromResponse', () => {
|
||||||
|
it('should handle string status', () => {
|
||||||
|
span.setStatusFromResponse('ok');
|
||||||
|
expect(span.status).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle number status', () => {
|
||||||
|
span.setStatusFromResponse(404);
|
||||||
|
expect(span.status).toBe('not_found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined status', () => {
|
||||||
|
span.setStatusFromResponse(undefined);
|
||||||
|
expect(span.status).toBe('unknown_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set tag when tagKey provided', () => {
|
||||||
|
span.setStatusFromResponse(200, 'custom.status');
|
||||||
|
expect(span.tags['custom.status']).toBe('200');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('startChild', () => {
|
||||||
|
it('should create child span', () => {
|
||||||
|
const child = span.startChild({ op: 'http.client' });
|
||||||
|
expect(child).toBeDefined();
|
||||||
|
expect(child.op).toBe('http.client');
|
||||||
|
expect(child.parentSpanId).toBe(span.spanId);
|
||||||
|
expect(child.traceId).toBe(span.traceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set transaction reference', () => {
|
||||||
|
const tx = new Transaction({ name: 'test-tx' });
|
||||||
|
const child = tx.startChild({ op: 'child' });
|
||||||
|
expect(child.transaction).toBe(tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add span to unsampled transaction', () => {
|
||||||
|
const tx = new Transaction({ name: 'test-tx', sampled: false });
|
||||||
|
const child = tx.startChild({ op: 'child' });
|
||||||
|
expect(tx.spans.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('finish', () => {
|
||||||
|
it('should set endTimestamp', () => {
|
||||||
|
span.finish();
|
||||||
|
expect(span.endTimestamp).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided endTimestamp', () => {
|
||||||
|
span.finish(1234567890000);
|
||||||
|
expect(span.endTimestamp).toBe(1234567890000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only finish once', () => {
|
||||||
|
span.finish();
|
||||||
|
const firstEndTime = span.endTimestamp;
|
||||||
|
span.finish(); // should not change
|
||||||
|
expect(span.endTimestamp).toBe(firstEndTime);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toTraceparent', () => {
|
||||||
|
it('should return traceparent without sampled flag when not sampled', () => {
|
||||||
|
const newSpan = new Span({ op: 'test' });
|
||||||
|
const traceparent = newSpan.toTraceparent();
|
||||||
|
expect(traceparent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return traceparent with sampled=1 when sampled', () => {
|
||||||
|
const tx = new Transaction({ name: 'test', sampled: true });
|
||||||
|
const traceparent = tx.toTraceparent();
|
||||||
|
expect(traceparent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}-1$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return traceparent with sampled=0 when not sampled', () => {
|
||||||
|
const tx = new Transaction({ name: 'test', sampled: false });
|
||||||
|
const traceparent = tx.toTraceparent();
|
||||||
|
expect(traceparent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}-0$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toJSON', () => {
|
||||||
|
it('should return correct JSON structure', () => {
|
||||||
|
span.setTag('http.method', 'GET');
|
||||||
|
span.setData('http.url', 'https://api.example.com');
|
||||||
|
span.setStatus('ok');
|
||||||
|
span.finish();
|
||||||
|
|
||||||
|
const json = span.toJSON();
|
||||||
|
|
||||||
|
expect(json.trace_id).toBe(span.traceId);
|
||||||
|
expect(json.span_id).toBe(span.spanId);
|
||||||
|
expect(json.op).toBe('test.span');
|
||||||
|
expect(json.status).toBe('ok');
|
||||||
|
expect(json.tags).toEqual({ 'http.method': 'GET' });
|
||||||
|
expect(json.data).toEqual({ 'http.url': 'https://api.example.com' });
|
||||||
|
expect(json.start_timestamp).toBe(span.startTimestamp);
|
||||||
|
expect(json.timestamp).toBe(span.endTimestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should omit empty tags and data', () => {
|
||||||
|
const newSpan = new Span({ op: 'test' });
|
||||||
|
const json = newSpan.toJSON();
|
||||||
|
expect(json.tags).toBeUndefined();
|
||||||
|
expect(json.data).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Transaction', () => {
|
||||||
|
let transaction: Transaction;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transaction = new Transaction({ name: 'test-transaction', op: 'transaction' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should create transaction with name and op', () => {
|
||||||
|
expect(transaction.name).toBe('test-transaction');
|
||||||
|
expect(transaction.op).toBe('transaction');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set transaction reference to itself', () => {
|
||||||
|
expect(transaction.transaction).toBe(transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize empty spans array', () => {
|
||||||
|
expect(transaction.spans).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set maxSpans default', () => {
|
||||||
|
expect(transaction.maxSpans).toBe(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept custom maxSpans', () => {
|
||||||
|
const tx = new Transaction({ name: 'test' }, undefined, undefined, undefined, undefined, 500);
|
||||||
|
expect(tx.maxSpans).toBe(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_addSpan', () => {
|
||||||
|
it('should add span to transaction', () => {
|
||||||
|
const child = transaction.startChild({ op: 'child' });
|
||||||
|
expect(transaction.spans.length).toBe(1);
|
||||||
|
expect(transaction.spans[0]).toBe(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not exceed maxSpans', () => {
|
||||||
|
const tx = new Transaction({ name: 'test' }, undefined, undefined, undefined, undefined, 2);
|
||||||
|
tx.startChild({ op: 'child1' });
|
||||||
|
tx.startChild({ op: 'child2' });
|
||||||
|
tx.startChild({ op: 'child3' }); // should be dropped
|
||||||
|
expect(tx.spans.length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setBaggage', () => {
|
||||||
|
it('should set baggage field', () => {
|
||||||
|
const result = transaction.setBaggage('custom.key', 'value');
|
||||||
|
expect(transaction.toBaggage()).toContain('custom.key=value');
|
||||||
|
expect(result).toBe(transaction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setUserBaggage', () => {
|
||||||
|
it('should set user_id in baggage', () => {
|
||||||
|
transaction.setUserBaggage('user123');
|
||||||
|
expect(transaction.toBaggage()).toContain('sentry-user_id=user123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set user_segment in baggage', () => {
|
||||||
|
transaction.setUserBaggage('user123', 'premium');
|
||||||
|
expect(transaction.toBaggage()).toContain('sentry-user_id=user123');
|
||||||
|
expect(transaction.toBaggage()).toContain('sentry-user_segment=premium');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete user_id when undefined', () => {
|
||||||
|
transaction.setUserBaggage('user123');
|
||||||
|
transaction.setUserBaggage(undefined);
|
||||||
|
expect(transaction.toBaggage()).not.toContain('user_id');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toBaggage', () => {
|
||||||
|
it('should return serialized baggage with trace_id', () => {
|
||||||
|
const baggage = transaction.toBaggage();
|
||||||
|
expect(baggage).toContain('sentry-trace_id=');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include sampled when explicitly set', () => {
|
||||||
|
const txWithSample = new Transaction({ name: 'test', sampled: true });
|
||||||
|
const baggage = txWithSample.toBaggage();
|
||||||
|
expect(baggage).toContain('sentry-sampled=true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toJSON', () => {
|
||||||
|
it('should include spans in JSON', () => {
|
||||||
|
transaction.startChild({ op: 'child1' });
|
||||||
|
transaction.startChild({ op: 'child2' });
|
||||||
|
|
||||||
|
const json = transaction.toJSON();
|
||||||
|
|
||||||
|
expect(json.spans).toHaveLength(2);
|
||||||
|
expect(json.transaction).toBe('test-transaction');
|
||||||
|
expect(json.sampled).toBeUndefined(); // not sampled by default
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toEvent', () => {
|
||||||
|
it('should return transaction event', () => {
|
||||||
|
transaction.startChild({ op: 'child' });
|
||||||
|
const event = transaction.toEvent();
|
||||||
|
|
||||||
|
expect(event.type).toBe('transaction');
|
||||||
|
expect(event.transaction).toBe('test-transaction');
|
||||||
|
expect(event.spans).toHaveLength(1);
|
||||||
|
expect(event.contexts?.trace).toBeDefined();
|
||||||
|
expect(event.platform).toBe('javascript');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('httpStatusToSpanStatus', () => {
|
||||||
|
it('should map 2xx to ok', () => {
|
||||||
|
expect(httpStatusToSpanStatus(200)).toBe('ok');
|
||||||
|
expect(httpStatusToSpanStatus(201)).toBe('ok');
|
||||||
|
expect(httpStatusToSpanStatus(299)).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 400 to invalid_argument', () => {
|
||||||
|
expect(httpStatusToSpanStatus(400)).toBe('invalid_argument');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 401 to unauthenticated', () => {
|
||||||
|
expect(httpStatusToSpanStatus(401)).toBe('unauthenticated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 403 to permission_denied', () => {
|
||||||
|
expect(httpStatusToSpanStatus(403)).toBe('permission_denied');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 404 to not_found', () => {
|
||||||
|
expect(httpStatusToSpanStatus(404)).toBe('not_found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 409 to already_exists', () => {
|
||||||
|
expect(httpStatusToSpanStatus(409)).toBe('already_exists');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 429 to resource_exhausted', () => {
|
||||||
|
expect(httpStatusToSpanStatus(429)).toBe('resource_exhausted');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 4xx to invalid_argument', () => {
|
||||||
|
expect(httpStatusToSpanStatus(499)).toBe('invalid_argument');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 5xx to internal_error', () => {
|
||||||
|
expect(httpStatusToSpanStatus(500)).toBe('internal_error');
|
||||||
|
expect(httpStatusToSpanStatus(503)).toBe('internal_error');
|
||||||
|
expect(httpStatusToSpanStatus(599)).toBe('internal_error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dbStatusToSpanStatus', () => {
|
||||||
|
it('should map 0 and 1 to ok', () => {
|
||||||
|
expect(dbStatusToSpanStatus(0)).toBe('ok');
|
||||||
|
expect(dbStatusToSpanStatus(1)).toBe('ok');
|
||||||
|
expect(dbStatusToSpanStatus('0')).toBe('ok');
|
||||||
|
expect(dbStatusToSpanStatus('1')).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 10-19 to invalid_argument', () => {
|
||||||
|
expect(dbStatusToSpanStatus(10)).toBe('invalid_argument');
|
||||||
|
expect(dbStatusToSpanStatus(15)).toBe('invalid_argument');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 20-29 to not_found', () => {
|
||||||
|
expect(dbStatusToSpanStatus(20)).toBe('not_found');
|
||||||
|
expect(dbStatusToSpanStatus(25)).toBe('not_found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 30-39 to already_exists', () => {
|
||||||
|
expect(dbStatusToSpanStatus(30)).toBe('already_exists');
|
||||||
|
expect(dbStatusToSpanStatus(35)).toBe('already_exists');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map 50+ to internal_error', () => {
|
||||||
|
expect(dbStatusToSpanStatus(50)).toBe('internal_error');
|
||||||
|
expect(dbStatusToSpanStatus(100)).toBe('internal_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unknown_error for undefined/null/empty', () => {
|
||||||
|
expect(dbStatusToSpanStatus(undefined)).toBe('unknown_error');
|
||||||
|
expect(dbStatusToSpanStatus(null)).toBe('unknown_error');
|
||||||
|
expect(dbStatusToSpanStatus('')).toBe('unknown_error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('statusToSpanStatus', () => {
|
||||||
|
it('should handle string status ok', () => {
|
||||||
|
expect(statusToSpanStatus('ok', undefined)).toBe('ok');
|
||||||
|
expect(statusToSpanStatus('success', undefined)).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle string status cancelled', () => {
|
||||||
|
expect(statusToSpanStatus('cancelled', undefined)).toBe('cancelled');
|
||||||
|
expect(statusToSpanStatus('canceled', undefined)).toBe('cancelled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle HTTP status codes', () => {
|
||||||
|
expect(statusToSpanStatus(404, undefined)).toBe('not_found');
|
||||||
|
expect(statusToSpanStatus(500, undefined)).toBe('internal_error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use db rules for db ops', () => {
|
||||||
|
expect(statusToSpanStatus(0, 'db.query')).toBe('ok');
|
||||||
|
expect(statusToSpanStatus(10, 'db.query')).toBe('invalid_argument');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unknown_error for unknown strings', () => {
|
||||||
|
expect(statusToSpanStatus('unknown_status', undefined)).toBe('unknown_error');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue