@feiyangdefeng/sdk 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +9 -7
  2. package/dist/collector/auto-tracker.d.ts +4 -0
  3. package/dist/collector/auto-tracker.d.ts.map +1 -1
  4. package/dist/collector/click-tracker.d.ts +10 -1
  5. package/dist/collector/click-tracker.d.ts.map +1 -1
  6. package/dist/collector/error-tracker.d.ts.map +1 -1
  7. package/dist/collector/exposure-tracker.d.ts.map +1 -1
  8. package/dist/collector/input-tracker.d.ts +10 -1
  9. package/dist/collector/input-tracker.d.ts.map +1 -1
  10. package/dist/collector/performance-tracker.d.ts.map +1 -1
  11. package/dist/collector/view-tracker.d.ts.map +1 -1
  12. package/dist/core/config.d.ts.map +1 -1
  13. package/dist/core/panel-manager.d.ts +27 -0
  14. package/dist/core/panel-manager.d.ts.map +1 -0
  15. package/dist/core/request.d.ts +3 -0
  16. package/dist/core/request.d.ts.map +1 -1
  17. package/dist/core/utils.d.ts +5 -0
  18. package/dist/core/utils.d.ts.map +1 -1
  19. package/dist/core/vue-utils.d.ts +58 -0
  20. package/dist/core/vue-utils.d.ts.map +1 -0
  21. package/dist/extension/naive-ui-tracker.d.ts +20 -0
  22. package/dist/extension/naive-ui-tracker.d.ts.map +1 -0
  23. package/dist/extension/performance-monitor.d.ts +93 -0
  24. package/dist/extension/performance-monitor.d.ts.map +1 -0
  25. package/dist/extension/user-action-sequence.d.ts +151 -0
  26. package/dist/extension/user-action-sequence.d.ts.map +1 -0
  27. package/dist/extension/vue-integration.d.ts +28 -0
  28. package/dist/extension/vue-integration.d.ts.map +1 -0
  29. package/dist/extension/vue-plugin.d.ts +66 -0
  30. package/dist/extension/vue-plugin.d.ts.map +1 -0
  31. package/dist/index.d.ts +12 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.es.js +3293 -246
  34. package/dist/index.es.js.map +1 -1
  35. package/dist/index.iife.js +2 -2
  36. package/dist/index.iife.js.map +1 -1
  37. package/dist/index.umd.js +2 -2
  38. package/dist/index.umd.js.map +1 -1
  39. package/dist/panels/offline-storage-viewer.html.d.ts +6 -0
  40. package/dist/panels/offline-storage-viewer.html.d.ts.map +1 -0
  41. package/dist/panels/performance-panel.html.d.ts +6 -0
  42. package/dist/panels/performance-panel.html.d.ts.map +1 -0
  43. package/dist/processor/deduplicator.d.ts.map +1 -1
  44. package/dist/tracker.d.ts +24 -0
  45. package/dist/tracker.d.ts.map +1 -1
  46. package/dist/transport/batch-sender.d.ts.map +1 -1
  47. package/dist/transport/retry-handler.d.ts.map +1 -1
  48. package/package.json +2 -5
package/dist/index.es.js CHANGED
@@ -16,7 +16,12 @@ const defaultConfig = {
16
16
  maxRetries: 3,
17
17
  userId: "",
18
18
  deviceId: "",
19
- customProperties: {}
19
+ customProperties: {},
20
+ enableVueTracking: false,
21
+ enableNaiveUITracking: false,
22
+ componentInfoDepth: 3,
23
+ extractComponentProps: true,
24
+ extractComponentValue: true
20
25
  };
21
26
  class ConfigManager {
22
27
  constructor(config) {
@@ -133,8 +138,16 @@ function getDeviceId() {
133
138
  if (typeof window === "undefined") {
134
139
  return generateId();
135
140
  }
141
+ if (typeof localStorage === "undefined" || !localStorage) {
142
+ return generateId();
143
+ }
136
144
  const key = "__tracker_device_id__";
137
- let deviceId = localStorage.getItem(key);
145
+ let deviceId = null;
146
+ try {
147
+ deviceId = localStorage.getItem(key);
148
+ } catch (e) {
149
+ return generateId();
150
+ }
138
151
  if (!deviceId) {
139
152
  deviceId = generateId();
140
153
  try {
@@ -148,8 +161,16 @@ function getSessionId() {
148
161
  if (typeof window === "undefined") {
149
162
  return generateId();
150
163
  }
164
+ if (typeof sessionStorage === "undefined" || !sessionStorage) {
165
+ return generateId();
166
+ }
151
167
  const key = "__tracker_session_id__";
152
- let sessionId = sessionStorage.getItem(key);
168
+ let sessionId = null;
169
+ try {
170
+ sessionId = sessionStorage.getItem(key);
171
+ } catch (e) {
172
+ return generateId();
173
+ }
153
174
  if (!sessionId) {
154
175
  sessionId = generateId();
155
176
  try {
@@ -165,6 +186,12 @@ function getCurrentUrl() {
165
186
  }
166
187
  return window.location.href;
167
188
  }
189
+ function isTrackerInternalPanel() {
190
+ if (typeof window === "undefined") {
191
+ return false;
192
+ }
193
+ return !!window.__trackerInternalPanel;
194
+ }
168
195
  function getPageTitle() {
169
196
  if (typeof window === "undefined") {
170
197
  return "";
@@ -213,22 +240,33 @@ function throttle(func, wait) {
213
240
  };
214
241
  }
215
242
  function deepClone(obj) {
243
+ return deepCloneWithRefs(obj, /* @__PURE__ */ new WeakSet());
244
+ }
245
+ function deepCloneWithRefs(obj, visited) {
216
246
  if (obj === null || typeof obj !== "object") {
217
247
  return obj;
218
248
  }
249
+ if (visited.has(obj)) {
250
+ return { __circular: true };
251
+ }
219
252
  if (obj instanceof Date) {
220
253
  return new Date(obj.getTime());
221
254
  }
222
255
  if (obj instanceof Array) {
223
- return obj.map((item) => deepClone(item));
256
+ visited.add(obj);
257
+ const cloned = obj.map((item) => deepCloneWithRefs(item, visited));
258
+ visited.delete(obj);
259
+ return cloned;
224
260
  }
225
261
  if (typeof obj === "object") {
262
+ visited.add(obj);
226
263
  const clonedObj = {};
227
264
  for (const key in obj) {
228
265
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
229
- clonedObj[key] = deepClone(obj[key]);
266
+ clonedObj[key] = deepCloneWithRefs(obj[key], visited);
230
267
  }
231
268
  }
269
+ visited.delete(obj);
232
270
  return clonedObj;
233
271
  }
234
272
  return obj;
@@ -312,6 +350,15 @@ class ViewTracker {
312
350
  * 追踪页面浏览
313
351
  */
314
352
  trackPageView() {
353
+ if (isTrackerInternalPanel()) {
354
+ return {
355
+ type: "pageview",
356
+ name: "pageview",
357
+ timestamp: Date.now(),
358
+ url: getCurrentUrl(),
359
+ title: getPageTitle()
360
+ };
361
+ }
315
362
  if (this.isTracking) {
316
363
  return {
317
364
  type: "pageview",
@@ -504,6 +551,539 @@ class ViewTracker {
504
551
  }
505
552
  }
506
553
  }
554
+ function getVueComponentInstance(element) {
555
+ if (!element || typeof window === "undefined") {
556
+ return null;
557
+ }
558
+ const vueInstance = element;
559
+ if (vueInstance.__vueParentComponent) {
560
+ return vueInstance.__vueParentComponent;
561
+ }
562
+ if (vueInstance.__vue__) {
563
+ return vueInstance.__vue__;
564
+ }
565
+ let current = element.parentElement;
566
+ let depth = 0;
567
+ const maxDepth = 5;
568
+ while (current && depth < maxDepth) {
569
+ const parentVueInstance = current;
570
+ if (parentVueInstance.__vueParentComponent) {
571
+ return parentVueInstance.__vueParentComponent;
572
+ }
573
+ if (parentVueInstance.__vue__) {
574
+ return parentVueInstance.__vue__;
575
+ }
576
+ current = current.parentElement;
577
+ depth++;
578
+ }
579
+ return null;
580
+ }
581
+ function getComponentName(instance) {
582
+ var _a, _b, _c, _d, _e, _f, _g;
583
+ if (!instance) {
584
+ return void 0;
585
+ }
586
+ if ((_a = instance.$options) == null ? void 0 : _a.name) {
587
+ return instance.$options.name;
588
+ }
589
+ if (instance.__name) {
590
+ return instance.__name;
591
+ }
592
+ if ((_b = instance.type) == null ? void 0 : _b.name) {
593
+ return instance.type.name;
594
+ }
595
+ if ((_c = instance.type) == null ? void 0 : _c.__name) {
596
+ return instance.type.__name;
597
+ }
598
+ if ((_e = (_d = instance.$) == null ? void 0 : _d.type) == null ? void 0 : _e.name) {
599
+ return instance.$.type.name;
600
+ }
601
+ if ((_g = (_f = instance.$) == null ? void 0 : _f.type) == null ? void 0 : _g.__name) {
602
+ return instance.$.type.__name;
603
+ }
604
+ return void 0;
605
+ }
606
+ function getComponentProps(instance) {
607
+ if (!instance) {
608
+ return void 0;
609
+ }
610
+ if (instance.$props) {
611
+ return instance.$props;
612
+ }
613
+ if (instance.props) {
614
+ return instance.props;
615
+ }
616
+ return void 0;
617
+ }
618
+ function getComponentAttrs(instance) {
619
+ if (!instance) {
620
+ return void 0;
621
+ }
622
+ if (instance.$attrs) {
623
+ return instance.$attrs;
624
+ }
625
+ if (instance.attrs) {
626
+ return instance.attrs;
627
+ }
628
+ return void 0;
629
+ }
630
+ function getComponentValue(instance) {
631
+ if (!instance) {
632
+ return void 0;
633
+ }
634
+ const props = getComponentProps(instance);
635
+ if (props) {
636
+ if ("value" in props) {
637
+ return props.value;
638
+ }
639
+ if ("modelValue" in props) {
640
+ return props.modelValue;
641
+ }
642
+ }
643
+ if (instance.setupState) {
644
+ if ("value" in instance.setupState) {
645
+ return instance.setupState.value;
646
+ }
647
+ if ("modelValue" in instance.setupState) {
648
+ return instance.setupState.modelValue;
649
+ }
650
+ }
651
+ if (instance.ctx) {
652
+ if ("value" in instance.ctx) {
653
+ return instance.ctx.value;
654
+ }
655
+ if ("modelValue" in instance.ctx) {
656
+ return instance.ctx.modelValue;
657
+ }
658
+ }
659
+ return void 0;
660
+ }
661
+ const SENSITIVE_KEYS = ["password", "token", "secret", "key", "auth", "credential", "pwd"];
662
+ function filterSensitiveData(data) {
663
+ if (!data) {
664
+ return void 0;
665
+ }
666
+ const filtered = {};
667
+ for (const [key, value] of Object.entries(data)) {
668
+ const lowerKey = key.toLowerCase();
669
+ const isSensitive = SENSITIVE_KEYS.some((sensitiveKey) => {
670
+ const lowerSensitiveKey = sensitiveKey.toLowerCase();
671
+ if (lowerKey === lowerSensitiveKey) return true;
672
+ const index = lowerKey.indexOf(lowerSensitiveKey);
673
+ if (index < 0) return false;
674
+ const prevChar = index > 0 ? key[index - 1] : null;
675
+ const nextChar = index + lowerSensitiveKey.length < key.length ? key[index + lowerSensitiveKey.length] : null;
676
+ if (index === 0 && nextChar !== null) {
677
+ if (nextChar === "-" || nextChar === "_" || /[A-Z]/.test(nextChar)) {
678
+ return true;
679
+ }
680
+ }
681
+ if (index === lowerKey.length - lowerSensitiveKey.length && prevChar !== null) {
682
+ if (prevChar === "-" || prevChar === "_" || /[A-Z]/.test(prevChar)) {
683
+ return true;
684
+ }
685
+ const originalSensitivePart = key.substring(index, index + lowerSensitiveKey.length);
686
+ if (/[A-Z]/.test(originalSensitivePart[0])) {
687
+ return true;
688
+ }
689
+ }
690
+ if (index > 0 && index < lowerKey.length - lowerSensitiveKey.length) {
691
+ if (prevChar !== null && nextChar !== null) {
692
+ if ((prevChar === "-" || prevChar === "_" || /[A-Z]/.test(prevChar)) && (nextChar === "-" || nextChar === "_" || /[A-Z]/.test(nextChar))) {
693
+ return true;
694
+ }
695
+ const originalSensitivePart = key.substring(index, index + lowerSensitiveKey.length);
696
+ if (/[A-Z]/.test(originalSensitivePart[0])) {
697
+ return true;
698
+ }
699
+ }
700
+ }
701
+ return false;
702
+ });
703
+ if (isSensitive) {
704
+ if (value === null) {
705
+ filtered[key] = "[null]";
706
+ } else if (value === void 0) {
707
+ continue;
708
+ } else {
709
+ filtered[key] = `[${typeof value}]`;
710
+ }
711
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
712
+ const nestedFiltered = filterSensitiveData(value);
713
+ filtered[key] = nestedFiltered !== void 0 ? nestedFiltered : {};
714
+ } else {
715
+ filtered[key] = value;
716
+ }
717
+ }
718
+ return filtered;
719
+ }
720
+ function identifyNaiveUIComponent(element) {
721
+ var _a, _b;
722
+ if (!element) {
723
+ return void 0;
724
+ }
725
+ const className = element.className;
726
+ if (typeof className === "string") {
727
+ const match = className.match(/\bn-([a-z-]+)\b/);
728
+ if (match) {
729
+ return `n-${match[1]}`;
730
+ }
731
+ }
732
+ const tagName = (_a = element.tagName) == null ? void 0 : _a.toLowerCase();
733
+ if (tagName && tagName.startsWith("n-")) {
734
+ return tagName;
735
+ }
736
+ let current = element.parentElement;
737
+ let depth = 0;
738
+ const maxDepth = 3;
739
+ while (current && depth < maxDepth) {
740
+ const parentClassName = current.className;
741
+ if (typeof parentClassName === "string") {
742
+ const match = parentClassName.match(/\bn-([a-z-]+)\b/);
743
+ if (match) {
744
+ return `n-${match[1]}`;
745
+ }
746
+ }
747
+ const parentTagName = (_b = current.tagName) == null ? void 0 : _b.toLowerCase();
748
+ if (parentTagName && parentTagName.startsWith("n-")) {
749
+ return parentTagName;
750
+ }
751
+ current = current.parentElement;
752
+ depth++;
753
+ }
754
+ return void 0;
755
+ }
756
+ function extractVueComponentInfo(element, options = {}) {
757
+ var _a;
758
+ if (!element || typeof window === "undefined") {
759
+ return void 0;
760
+ }
761
+ const {
762
+ extractProps = true,
763
+ extractAttrs = true,
764
+ extractValue = true,
765
+ extractLabel = true
766
+ } = options;
767
+ const naiveUIComponentName = identifyNaiveUIComponent(element);
768
+ const vueInstance = getVueComponentInstance(element);
769
+ const componentName = naiveUIComponentName || getComponentName(vueInstance);
770
+ if (!componentName && !vueInstance) {
771
+ return void 0;
772
+ }
773
+ const componentInfo = {};
774
+ if (componentName) {
775
+ componentInfo.componentName = componentName;
776
+ }
777
+ if (extractProps && vueInstance) {
778
+ const props = getComponentProps(vueInstance);
779
+ if (props) {
780
+ componentInfo.props = filterSensitiveData(props);
781
+ if ("type" in props) {
782
+ componentInfo.componentType = String(props.type);
783
+ }
784
+ if ("disabled" in props) {
785
+ componentInfo.disabled = Boolean(props.disabled);
786
+ }
787
+ if ("label" in props && extractLabel) {
788
+ componentInfo.label = String(props.label);
789
+ }
790
+ }
791
+ }
792
+ if (extractAttrs && vueInstance) {
793
+ const attrs = getComponentAttrs(vueInstance);
794
+ if (attrs) {
795
+ componentInfo.attrs = filterSensitiveData(attrs);
796
+ if (!componentInfo.componentType && "type" in attrs) {
797
+ componentInfo.componentType = String(attrs.type);
798
+ }
799
+ if (componentInfo.disabled === void 0 && "disabled" in attrs) {
800
+ componentInfo.disabled = Boolean(attrs.disabled);
801
+ }
802
+ }
803
+ }
804
+ if (extractValue && vueInstance) {
805
+ const value = getComponentValue(vueInstance);
806
+ if (value !== void 0 && value !== null) {
807
+ const props = getComponentProps(vueInstance);
808
+ const isSensitive = props && ("type" in props && String(props.type).toLowerCase() === "password" || "password" in props || componentName === "n-input" && "type" in props && String(props.type).toLowerCase() === "password");
809
+ if (isSensitive) {
810
+ const valueLength = typeof value === "string" ? value.length : void 0;
811
+ componentInfo.value = valueLength !== void 0 ? `[password:${valueLength} chars]` : `[${typeof value}]`;
812
+ } else {
813
+ if (typeof value === "object") {
814
+ if (Array.isArray(value)) {
815
+ componentInfo.value = `[Array(${value.length})]`;
816
+ if (componentName === "n-select" || componentName === "n-cascader") {
817
+ componentInfo.options = {
818
+ count: value.length,
819
+ multiple: props && "multiple" in props ? Boolean(props.multiple) : false
820
+ };
821
+ }
822
+ } else {
823
+ componentInfo.value = `[Object]`;
824
+ }
825
+ } else {
826
+ componentInfo.value = value;
827
+ }
828
+ }
829
+ }
830
+ }
831
+ if (extractLabel && !componentInfo.label) {
832
+ const text = (_a = element.textContent) == null ? void 0 : _a.trim();
833
+ if (text && text.length > 0 && text.length < 100) {
834
+ componentInfo.label = text.substring(0, 100);
835
+ }
836
+ }
837
+ if ((componentName === "n-select" || componentName === "n-cascader") && vueInstance) {
838
+ const props = getComponentProps(vueInstance);
839
+ if (props && "options" in props && Array.isArray(props.options)) {
840
+ componentInfo.options = {
841
+ count: props.options.length,
842
+ multiple: props && "multiple" in props ? Boolean(props.multiple) : false
843
+ };
844
+ }
845
+ }
846
+ return Object.keys(componentInfo).length > 0 ? componentInfo : void 0;
847
+ }
848
+ function isVueComponent(element) {
849
+ if (!element) {
850
+ return false;
851
+ }
852
+ const vueInstance = getVueComponentInstance(element);
853
+ return vueInstance !== null;
854
+ }
855
+ function isNaiveUIComponent(element) {
856
+ if (!element) {
857
+ return false;
858
+ }
859
+ const componentName = identifyNaiveUIComponent(element);
860
+ return componentName !== void 0 && componentName.startsWith("n-");
861
+ }
862
+ const NAIVE_UI_COMPONENT_TYPES = {
863
+ "n-button": ["default", "primary", "success", "info", "warning", "error", "tertiary", "quaternary"],
864
+ "n-input": ["text", "password", "textarea", "number", "email", "tel", "url"],
865
+ "n-select": ["default", "multiple", "tags", "filterable"],
866
+ "n-checkbox": ["default", "indeterminate"],
867
+ "n-radio": ["default", "button"],
868
+ "n-switch": ["default"],
869
+ "n-date-picker": ["date", "datetime", "daterange", "datetimerange"],
870
+ "n-time-picker": ["time", "timerange"],
871
+ "n-cascader": ["default", "multiple", "filterable"],
872
+ "n-transfer": ["default"],
873
+ "n-tree-select": ["default", "multiple", "filterable"],
874
+ "n-form": ["default", "inline"],
875
+ "n-form-item": ["default"],
876
+ "n-table": ["default", "bordered", "striped"],
877
+ "n-pagination": ["default"],
878
+ "n-menu": ["default", "horizontal", "vertical"],
879
+ "n-tabs": ["default", "card", "bar"],
880
+ "n-drawer": ["default"],
881
+ "n-modal": ["default"],
882
+ "n-message": ["default", "success", "info", "warning", "error"],
883
+ "n-notification": ["default", "success", "info", "warning", "error"],
884
+ "n-popover": ["default"],
885
+ "n-tooltip": ["default"],
886
+ "n-dropdown": ["default"],
887
+ "n-breadcrumb": ["default"],
888
+ "n-steps": ["default", "vertical"],
889
+ "n-timeline": ["default"],
890
+ "n-card": ["default", "bordered", "hoverable"],
891
+ "n-collapse": ["default", "accordion"],
892
+ "n-divider": ["default", "horizontal", "vertical"],
893
+ "n-space": ["default", "vertical"],
894
+ "n-grid": ["default"],
895
+ "n-layout": ["default"],
896
+ "n-back-top": ["default"],
897
+ "n-anchor": ["default"],
898
+ "n-affix": ["default"],
899
+ "n-watermark": ["default"],
900
+ "n-spin": ["default"],
901
+ "n-skeleton": ["default"],
902
+ "n-progress": ["default", "line", "circle", "dashboard"],
903
+ "n-rate": ["default"],
904
+ "n-slider": ["default", "range"],
905
+ "n-color-picker": ["default"],
906
+ "n-image": ["default"],
907
+ "n-avatar": ["default", "circle", "square"],
908
+ "n-badge": ["default"],
909
+ "n-tag": ["default", "success", "info", "warning", "error"],
910
+ "n-empty": ["default"],
911
+ "n-result": ["default", "success", "info", "warning", "error", "404", "500"],
912
+ "n-descriptions": ["default", "bordered"],
913
+ "n-statistic": ["default"],
914
+ "n-list": ["default", "bordered"],
915
+ "n-list-item": ["default"],
916
+ "n-carousel": ["default", "dot", "arrow"],
917
+ "n-calendar": ["default"]
918
+ };
919
+ function extractNaiveUIComponentInfo(element, options = {}) {
920
+ if (!element) {
921
+ return void 0;
922
+ }
923
+ if (!isNaiveUIComponent(element)) {
924
+ return void 0;
925
+ }
926
+ const componentInfo = extractVueComponentInfo(element, {
927
+ extractProps: options.extractProps,
928
+ extractAttrs: options.extractAttrs,
929
+ extractValue: options.extractValue,
930
+ extractLabel: options.extractLabel
931
+ });
932
+ if (!componentInfo) {
933
+ return void 0;
934
+ }
935
+ const componentName = componentInfo.componentName;
936
+ if (!componentName || !componentName.startsWith("n-")) {
937
+ return componentInfo;
938
+ }
939
+ switch (componentName) {
940
+ case "n-button":
941
+ enhanceButtonInfo(element, componentInfo);
942
+ break;
943
+ case "n-input":
944
+ enhanceInputInfo(element, componentInfo);
945
+ break;
946
+ case "n-select":
947
+ case "n-cascader":
948
+ case "n-tree-select":
949
+ enhanceSelectInfo(element, componentInfo);
950
+ break;
951
+ case "n-checkbox":
952
+ case "n-radio":
953
+ enhanceCheckboxRadioInfo(element, componentInfo);
954
+ break;
955
+ case "n-switch":
956
+ enhanceSwitchInfo(element, componentInfo);
957
+ break;
958
+ case "n-form":
959
+ case "n-form-item":
960
+ enhanceFormInfo(element, componentInfo);
961
+ break;
962
+ }
963
+ return componentInfo;
964
+ }
965
+ function enhanceButtonInfo(element, componentInfo) {
966
+ var _a;
967
+ if (!componentInfo.label) {
968
+ const text = (_a = element.textContent) == null ? void 0 : _a.trim();
969
+ if (text) {
970
+ componentInfo.label = text.substring(0, 50);
971
+ }
972
+ }
973
+ const iconElement = element.querySelector('.n-button__icon, [class*="icon"]');
974
+ if (iconElement) {
975
+ componentInfo.hasIcon = true;
976
+ }
977
+ if (element.classList.contains("n-button--disabled") || element.hasAttribute("disabled")) {
978
+ componentInfo.disabled = true;
979
+ }
980
+ if (element.classList.contains("n-button--loading")) {
981
+ componentInfo.loading = true;
982
+ }
983
+ }
984
+ function enhanceInputInfo(element, componentInfo) {
985
+ var _a;
986
+ const inputElement = element.querySelector("input, textarea");
987
+ if (inputElement) {
988
+ if (!((_a = componentInfo.attrs) == null ? void 0 : _a.placeholder) && inputElement.placeholder) {
989
+ if (!componentInfo.attrs) {
990
+ componentInfo.attrs = {};
991
+ }
992
+ componentInfo.attrs.placeholder = inputElement.placeholder;
993
+ }
994
+ if (inputElement.tagName.toLowerCase() === "input") {
995
+ const inputType = inputElement.type || "text";
996
+ if (!componentInfo.componentType) {
997
+ componentInfo.componentType = inputType;
998
+ }
999
+ if (inputType === "password") {
1000
+ componentInfo.isSensitive = true;
1001
+ if (componentInfo.value && typeof componentInfo.value === "string") {
1002
+ if (!componentInfo.value.startsWith("[password:")) {
1003
+ componentInfo.value = `[password:${componentInfo.value.length} chars]`;
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ const value = inputElement.value;
1009
+ if (value) {
1010
+ const isPassword = inputElement.type === "password" || componentInfo.props && "type" in componentInfo.props && String(componentInfo.props.type).toLowerCase() === "password";
1011
+ if (isPassword) {
1012
+ componentInfo.value = `[password:${value.length} chars]`;
1013
+ componentInfo.isSensitive = true;
1014
+ } else {
1015
+ componentInfo.value = value.length > 100 ? value.substring(0, 100) + "..." : value;
1016
+ }
1017
+ } else if (!componentInfo.value) ;
1018
+ }
1019
+ }
1020
+ function enhanceSelectInfo(element, componentInfo) {
1021
+ var _a;
1022
+ const selectedElement = element.querySelector(".n-base-selection, .n-base-selection-label");
1023
+ if (selectedElement) {
1024
+ const selectedText = (_a = selectedElement.textContent) == null ? void 0 : _a.trim();
1025
+ if (selectedText && !componentInfo.label) {
1026
+ componentInfo.label = selectedText.substring(0, 100);
1027
+ }
1028
+ }
1029
+ if (element.classList.contains("n-base-selection--multiple")) {
1030
+ if (!componentInfo.options) {
1031
+ componentInfo.options = {};
1032
+ }
1033
+ componentInfo.options.multiple = true;
1034
+ }
1035
+ if (element.querySelector(".n-base-selection-input")) {
1036
+ componentInfo.filterable = true;
1037
+ }
1038
+ }
1039
+ function enhanceCheckboxRadioInfo(element, componentInfo) {
1040
+ var _a;
1041
+ const inputElement = element.querySelector('input[type="checkbox"], input[type="radio"]');
1042
+ if (inputElement) {
1043
+ componentInfo.checked = inputElement.checked;
1044
+ const labelElement = element.querySelector("label, .n-checkbox__label, .n-radio__label");
1045
+ if (labelElement) {
1046
+ const labelText = (_a = labelElement.textContent) == null ? void 0 : _a.trim();
1047
+ if (labelText && !componentInfo.label) {
1048
+ componentInfo.label = labelText.substring(0, 100);
1049
+ }
1050
+ }
1051
+ if (inputElement.value) {
1052
+ componentInfo.value = inputElement.value;
1053
+ }
1054
+ }
1055
+ }
1056
+ function enhanceSwitchInfo(element, componentInfo) {
1057
+ const inputElement = element.querySelector('input[type="checkbox"]');
1058
+ if (inputElement) {
1059
+ componentInfo.checked = inputElement.checked;
1060
+ componentInfo.value = inputElement.checked;
1061
+ }
1062
+ }
1063
+ function enhanceFormInfo(element, componentInfo) {
1064
+ var _a;
1065
+ const labelElement = element.querySelector(".n-form-item-label, label");
1066
+ if (labelElement) {
1067
+ const labelText = (_a = labelElement.textContent) == null ? void 0 : _a.trim();
1068
+ if (labelText && !componentInfo.label) {
1069
+ componentInfo.label = labelText.substring(0, 100);
1070
+ }
1071
+ }
1072
+ const requiredElement = element.querySelector('.n-form-item-label__asterisk, [class*="required"]');
1073
+ if (requiredElement) {
1074
+ componentInfo.required = true;
1075
+ }
1076
+ }
1077
+ function isNaiveUIComponentType(element, componentType) {
1078
+ if (!element) {
1079
+ return false;
1080
+ }
1081
+ const componentName = identifyNaiveUIComponent(element);
1082
+ return componentName === componentType;
1083
+ }
1084
+ function getNaiveUIComponentTypes(componentName) {
1085
+ return NAIVE_UI_COMPONENT_TYPES[componentName];
1086
+ }
507
1087
  class ClickTracker {
508
1088
  constructor() {
509
1089
  __publicField(this, "clickHandler", null);
@@ -515,18 +1095,28 @@ class ClickTracker {
515
1095
  __publicField(this, "TOUCH_CLICK_THRESHOLD", 300);
516
1096
  // ms
517
1097
  __publicField(this, "TOUCH_DISTANCE_THRESHOLD", 10);
1098
+ // px
1099
+ __publicField(this, "enableVueTracking", false);
1100
+ __publicField(this, "enableNaiveUITracking", false);
1101
+ __publicField(this, "extractComponentProps", true);
1102
+ __publicField(this, "extractComponentValue", true);
518
1103
  }
519
- // px
520
1104
  /**
521
1105
  * 初始化
522
1106
  */
523
- init(debounceTime) {
1107
+ init(debounceTime, options) {
524
1108
  if (typeof window === "undefined") {
525
1109
  return;
526
1110
  }
527
1111
  if (debounceTime !== void 0) {
528
1112
  this.debounceTime = debounceTime;
529
1113
  }
1114
+ if (options) {
1115
+ this.enableVueTracking = options.enableVueTracking ?? false;
1116
+ this.enableNaiveUITracking = options.enableNaiveUITracking ?? false;
1117
+ this.extractComponentProps = options.extractComponentProps ?? true;
1118
+ this.extractComponentValue = options.extractComponentValue ?? true;
1119
+ }
530
1120
  this.clickHandler = debounce((...args) => {
531
1121
  const event = args[0];
532
1122
  this.handleClick(event);
@@ -590,6 +1180,9 @@ class ClickTracker {
590
1180
  * 处理触摸交互(模拟点击行为)
591
1181
  */
592
1182
  processTouchInteraction(touch, element) {
1183
+ if (isTrackerInternalPanel()) {
1184
+ return;
1185
+ }
593
1186
  if (!element) {
594
1187
  return;
595
1188
  }
@@ -617,6 +1210,30 @@ class ClickTracker {
617
1210
  interactionType: "touch"
618
1211
  // 标记为触摸交互
619
1212
  };
1213
+ if (this.enableNaiveUITracking || this.enableVueTracking) {
1214
+ try {
1215
+ if (this.enableNaiveUITracking) {
1216
+ const naiveUIInfo = extractNaiveUIComponentInfo(element, {
1217
+ extractProps: this.extractComponentProps,
1218
+ extractValue: this.extractComponentValue
1219
+ });
1220
+ if (naiveUIInfo) {
1221
+ touchEvent.componentInfo = naiveUIInfo;
1222
+ }
1223
+ }
1224
+ if (!touchEvent.componentInfo && this.enableVueTracking) {
1225
+ const vueInfo = extractVueComponentInfo(element, {
1226
+ extractProps: this.extractComponentProps,
1227
+ extractValue: this.extractComponentValue
1228
+ });
1229
+ if (vueInfo) {
1230
+ touchEvent.componentInfo = vueInfo;
1231
+ }
1232
+ }
1233
+ } catch (componentError) {
1234
+ console.warn("ClickTracker: Error extracting component info:", componentError);
1235
+ }
1236
+ }
620
1237
  if (typeof window !== "undefined" && window.__trackerEventBus) {
621
1238
  window.__trackerEventBus.emit("click", touchEvent);
622
1239
  }
@@ -634,6 +1251,9 @@ class ClickTracker {
634
1251
  * 处理点击事件
635
1252
  */
636
1253
  handleClick(event) {
1254
+ if (isTrackerInternalPanel()) {
1255
+ return;
1256
+ }
637
1257
  try {
638
1258
  const target = event.target;
639
1259
  if (!target) {
@@ -662,6 +1282,30 @@ class ClickTracker {
662
1282
  interactionType: "mouse"
663
1283
  // 显式标识为鼠标交互
664
1284
  };
1285
+ if (this.enableNaiveUITracking || this.enableVueTracking) {
1286
+ try {
1287
+ if (this.enableNaiveUITracking) {
1288
+ const naiveUIInfo = extractNaiveUIComponentInfo(target, {
1289
+ extractProps: this.extractComponentProps,
1290
+ extractValue: this.extractComponentValue
1291
+ });
1292
+ if (naiveUIInfo) {
1293
+ clickEvent.componentInfo = naiveUIInfo;
1294
+ }
1295
+ }
1296
+ if (!clickEvent.componentInfo && this.enableVueTracking) {
1297
+ const vueInfo = extractVueComponentInfo(target, {
1298
+ extractProps: this.extractComponentProps,
1299
+ extractValue: this.extractComponentValue
1300
+ });
1301
+ if (vueInfo) {
1302
+ clickEvent.componentInfo = vueInfo;
1303
+ }
1304
+ }
1305
+ } catch (componentError) {
1306
+ console.warn("ClickTracker: Error extracting component info:", componentError);
1307
+ }
1308
+ }
665
1309
  if (typeof window !== "undefined" && window.__trackerEventBus) {
666
1310
  window.__trackerEventBus.emit("click", clickEvent);
667
1311
  }
@@ -821,6 +1465,9 @@ class ExposureTracker {
821
1465
  * 处理IntersectionObserver回调
822
1466
  */
823
1467
  handleIntersection(entry) {
1468
+ if (isTrackerInternalPanel()) {
1469
+ return;
1470
+ }
824
1471
  const { target, intersectionRatio, isIntersecting } = entry;
825
1472
  if (isIntersecting && intersectionRatio >= (this.options.threshold || 0.5)) {
826
1473
  if (!this.exposedElements.has(target)) {
@@ -955,11 +1602,15 @@ class InputTracker {
955
1602
  // 默认跳过空输入
956
1603
  // 敏感字段关键词列表
957
1604
  __publicField(this, "sensitiveKeywords", ["password", "pwd", "secret", "token", "key", "auth", "credential"]);
1605
+ __publicField(this, "enableVueTracking", false);
1606
+ __publicField(this, "enableNaiveUITracking", false);
1607
+ __publicField(this, "extractComponentProps", true);
1608
+ __publicField(this, "extractComponentValue", true);
958
1609
  }
959
1610
  /**
960
1611
  * 初始化
961
1612
  */
962
- init(debounceTime, skipEmptyInput) {
1613
+ init(debounceTime, skipEmptyInput, options) {
963
1614
  if (typeof window === "undefined") {
964
1615
  return;
965
1616
  }
@@ -970,6 +1621,12 @@ class InputTracker {
970
1621
  if (skipEmptyInput !== void 0) {
971
1622
  this.skipEmptyInput = skipEmptyInput;
972
1623
  }
1624
+ if (options) {
1625
+ this.enableVueTracking = options.enableVueTracking ?? false;
1626
+ this.enableNaiveUITracking = options.enableNaiveUITracking ?? false;
1627
+ this.extractComponentProps = options.extractComponentProps ?? true;
1628
+ this.extractComponentValue = options.extractComponentValue ?? true;
1629
+ }
973
1630
  this.inputHandler = debounce((...args) => {
974
1631
  const event = args[0];
975
1632
  this.handleInput(event);
@@ -986,6 +1643,9 @@ class InputTracker {
986
1643
  */
987
1644
  handleInput(event) {
988
1645
  var _a;
1646
+ if (isTrackerInternalPanel()) {
1647
+ return;
1648
+ }
989
1649
  const target = event.target;
990
1650
  if (!target) {
991
1651
  return;
@@ -1047,6 +1707,48 @@ class InputTracker {
1047
1707
  valueLength,
1048
1708
  isSensitive
1049
1709
  };
1710
+ if (this.enableNaiveUITracking || this.enableVueTracking) {
1711
+ try {
1712
+ if (this.enableNaiveUITracking) {
1713
+ const naiveUIInfo = extractNaiveUIComponentInfo(target, {
1714
+ extractProps: this.extractComponentProps,
1715
+ extractValue: this.extractComponentValue
1716
+ });
1717
+ if (naiveUIInfo) {
1718
+ inputEvent.componentInfo = naiveUIInfo;
1719
+ if (naiveUIInfo.value !== void 0 && !isSensitive) {
1720
+ if (typeof naiveUIInfo.value === "string") {
1721
+ if (!naiveUIInfo.value.startsWith("[password:") && !naiveUIInfo.value.startsWith("[FILTERED]")) {
1722
+ inputEvent.value = naiveUIInfo.value;
1723
+ }
1724
+ } else if (typeof naiveUIInfo.value === "number" || typeof naiveUIInfo.value === "boolean") {
1725
+ inputEvent.value = String(naiveUIInfo.value);
1726
+ }
1727
+ }
1728
+ }
1729
+ }
1730
+ if (!inputEvent.componentInfo && this.enableVueTracking) {
1731
+ const vueInfo = extractVueComponentInfo(target, {
1732
+ extractProps: this.extractComponentProps,
1733
+ extractValue: this.extractComponentValue
1734
+ });
1735
+ if (vueInfo) {
1736
+ inputEvent.componentInfo = vueInfo;
1737
+ if (vueInfo.value !== void 0 && !isSensitive) {
1738
+ if (typeof vueInfo.value === "string") {
1739
+ if (!vueInfo.value.startsWith("[password:") && !vueInfo.value.startsWith("[FILTERED]")) {
1740
+ inputEvent.value = vueInfo.value;
1741
+ }
1742
+ } else if (typeof vueInfo.value === "number" || typeof vueInfo.value === "boolean") {
1743
+ inputEvent.value = String(vueInfo.value);
1744
+ }
1745
+ }
1746
+ }
1747
+ }
1748
+ } catch (componentError) {
1749
+ console.warn("InputTracker: Error extracting component info:", componentError);
1750
+ }
1751
+ }
1050
1752
  if (typeof window !== "undefined" && window.__trackerEventBus) {
1051
1753
  window.__trackerEventBus.emit("input", inputEvent);
1052
1754
  }
@@ -1065,6 +1767,9 @@ class InputTracker {
1065
1767
  */
1066
1768
  handleChange(event) {
1067
1769
  var _a;
1770
+ if (isTrackerInternalPanel()) {
1771
+ return;
1772
+ }
1068
1773
  const target = event.target;
1069
1774
  if (!target) {
1070
1775
  return;
@@ -1111,17 +1816,59 @@ class InputTracker {
1111
1816
  valueLength,
1112
1817
  isSensitive
1113
1818
  };
1114
- if (typeof window !== "undefined" && window.__trackerEventBus) {
1115
- window.__trackerEventBus.emit("input", inputEvent);
1116
- }
1117
- } catch (error) {
1118
- console.error("InputTracker: Error processing change event:", error);
1119
- }
1120
- };
1121
- if (typeof requestIdleCallback !== "undefined") {
1122
- requestIdleCallback(processChange, { timeout: 100 });
1123
- } else {
1124
- setTimeout(processChange, 0);
1819
+ if (this.enableNaiveUITracking || this.enableVueTracking) {
1820
+ try {
1821
+ if (this.enableNaiveUITracking) {
1822
+ const naiveUIInfo = extractNaiveUIComponentInfo(target, {
1823
+ extractProps: this.extractComponentProps,
1824
+ extractValue: this.extractComponentValue
1825
+ });
1826
+ if (naiveUIInfo) {
1827
+ inputEvent.componentInfo = naiveUIInfo;
1828
+ if (naiveUIInfo.value !== void 0 && !isSensitive) {
1829
+ if (typeof naiveUIInfo.value === "string") {
1830
+ if (!naiveUIInfo.value.startsWith("[password:") && !naiveUIInfo.value.startsWith("[FILTERED]")) {
1831
+ inputEvent.value = naiveUIInfo.value;
1832
+ }
1833
+ } else if (typeof naiveUIInfo.value === "number" || typeof naiveUIInfo.value === "boolean") {
1834
+ inputEvent.value = String(naiveUIInfo.value);
1835
+ }
1836
+ }
1837
+ }
1838
+ }
1839
+ if (!inputEvent.componentInfo && this.enableVueTracking) {
1840
+ const vueInfo = extractVueComponentInfo(target, {
1841
+ extractProps: this.extractComponentProps,
1842
+ extractValue: this.extractComponentValue
1843
+ });
1844
+ if (vueInfo) {
1845
+ inputEvent.componentInfo = vueInfo;
1846
+ if (vueInfo.value !== void 0 && !isSensitive) {
1847
+ if (typeof vueInfo.value === "string") {
1848
+ if (!vueInfo.value.startsWith("[password:") && !vueInfo.value.startsWith("[FILTERED]")) {
1849
+ inputEvent.value = vueInfo.value;
1850
+ }
1851
+ } else if (typeof vueInfo.value === "number" || typeof vueInfo.value === "boolean") {
1852
+ inputEvent.value = String(vueInfo.value);
1853
+ }
1854
+ }
1855
+ }
1856
+ }
1857
+ } catch (componentError) {
1858
+ console.warn("InputTracker: Error extracting component info:", componentError);
1859
+ }
1860
+ }
1861
+ if (typeof window !== "undefined" && window.__trackerEventBus) {
1862
+ window.__trackerEventBus.emit("input", inputEvent);
1863
+ }
1864
+ } catch (error) {
1865
+ console.error("InputTracker: Error processing change event:", error);
1866
+ }
1867
+ };
1868
+ if (typeof requestIdleCallback !== "undefined") {
1869
+ requestIdleCallback(processChange, { timeout: 100 });
1870
+ } else {
1871
+ setTimeout(processChange, 0);
1125
1872
  }
1126
1873
  }
1127
1874
  /**
@@ -1194,16 +1941,36 @@ class AutoTracker {
1194
1941
  if (this.enabled) {
1195
1942
  return;
1196
1943
  }
1197
- const { enableClick = true, enableExposure = true, enableInput = true, clickDebounceTime, inputDebounceTime } = options || {};
1944
+ const {
1945
+ enableClick = true,
1946
+ enableExposure = true,
1947
+ enableInput = true,
1948
+ clickDebounceTime,
1949
+ inputDebounceTime,
1950
+ enableVueTracking = false,
1951
+ enableNaiveUITracking = false,
1952
+ extractComponentProps = true,
1953
+ extractComponentValue = true
1954
+ } = options || {};
1198
1955
  this.viewTracker.init();
1199
1956
  if (enableClick) {
1200
- this.clickTracker.init(clickDebounceTime);
1957
+ this.clickTracker.init(clickDebounceTime, {
1958
+ enableVueTracking,
1959
+ enableNaiveUITracking,
1960
+ extractComponentProps,
1961
+ extractComponentValue
1962
+ });
1201
1963
  }
1202
1964
  if (enableExposure) {
1203
1965
  this.exposureTracker.init();
1204
1966
  }
1205
1967
  if (enableInput) {
1206
- this.inputTracker.init(inputDebounceTime);
1968
+ this.inputTracker.init(inputDebounceTime, void 0, {
1969
+ enableVueTracking,
1970
+ enableNaiveUITracking,
1971
+ extractComponentProps,
1972
+ extractComponentValue
1973
+ });
1207
1974
  }
1208
1975
  this.enabled = true;
1209
1976
  }
@@ -1307,6 +2074,9 @@ class ErrorTracker {
1307
2074
  * 处理Promise错误
1308
2075
  */
1309
2076
  handlePromiseError(event) {
2077
+ if (isTrackerInternalPanel()) {
2078
+ return;
2079
+ }
1310
2080
  try {
1311
2081
  const reason = event.reason;
1312
2082
  let message = "Unhandled promise rejection";
@@ -1339,6 +2109,9 @@ class ErrorTracker {
1339
2109
  */
1340
2110
  handleResourceError(event) {
1341
2111
  var _a;
2112
+ if (isTrackerInternalPanel()) {
2113
+ return;
2114
+ }
1342
2115
  try {
1343
2116
  const target = event.target;
1344
2117
  if (!target) {
@@ -1499,6 +2272,9 @@ class PerformanceTracker {
1499
2272
  * 采集Navigation Timing数据
1500
2273
  */
1501
2274
  collectNavigationTiming() {
2275
+ if (isTrackerInternalPanel()) {
2276
+ return;
2277
+ }
1502
2278
  if (this.navigationTimingCollected) {
1503
2279
  return;
1504
2280
  }
@@ -1904,7 +2680,8 @@ class Deduplicator {
1904
2680
  * 清理过期缓存
1905
2681
  */
1906
2682
  cleanExpiredCache(now) {
1907
- const maxTimeout = Math.max(...Object.values(this.cacheTimeouts), this.defaultCacheTimeout);
2683
+ const timeoutValues = Object.values(this.cacheTimeouts);
2684
+ const maxTimeout = timeoutValues.length > 0 ? Math.max(...timeoutValues, this.defaultCacheTimeout) : this.defaultCacheTimeout;
1908
2685
  const expireTime = maxTimeout * 2;
1909
2686
  for (const [key, time] of this.eventCache.entries()) {
1910
2687
  if (now - time > expireTime) {
@@ -6243,6 +7020,24 @@ async function fetchPolyfill(url, init) {
6243
7020
  reject(new DOMException("Aborted", "AbortError"));
6244
7021
  return;
6245
7022
  }
7023
+ if (typeof signal.addEventListener === "function") {
7024
+ signal.addEventListener("abort", () => {
7025
+ xhr.abort();
7026
+ reject(new DOMException("Aborted", "AbortError"));
7027
+ });
7028
+ } else {
7029
+ const checkAborted = setInterval(() => {
7030
+ if (signal.aborted) {
7031
+ clearInterval(checkAborted);
7032
+ xhr.abort();
7033
+ reject(new DOMException("Aborted", "AbortError"));
7034
+ }
7035
+ }, 100);
7036
+ const cleanup = () => clearInterval(checkAborted);
7037
+ xhr.addEventListener("loadend", cleanup);
7038
+ xhr.addEventListener("error", cleanup);
7039
+ xhr.addEventListener("timeout", cleanup);
7040
+ }
6246
7041
  }
6247
7042
  xhr.onload = () => {
6248
7043
  const headersString = xhr.getAllResponseHeaders();
@@ -6311,15 +7106,18 @@ function getFetch() {
6311
7106
  new (getAbortController())();
6312
7107
  return fetch;
6313
7108
  } catch {
6314
- return fetchPolyfill;
7109
+ if (typeof XMLHttpRequest !== "undefined") {
7110
+ return fetchPolyfill;
7111
+ }
7112
+ throw new Error("AbortController is not available. This should not happen in Node.js 18+ or modern browsers.");
6315
7113
  }
6316
7114
  }
6317
7115
  if (typeof XMLHttpRequest !== "undefined") {
6318
7116
  return fetchPolyfill;
6319
7117
  }
6320
- throw new Error("fetch is not available and XMLHttpRequest fallback is not supported");
7118
+ throw new Error("fetch is not available. Node.js 18+ is required (engines.node >= 18.0.0). For browser environments, please use a modern browser or provide a fetch polyfill.");
6321
7119
  }
6322
- async function browserRequest(url, options = {}) {
7120
+ async function universalRequest(url, options = {}) {
6323
7121
  const { method = "POST", headers = {}, body, timeout = 1e4 } = options;
6324
7122
  const AbortControllerClass = getAbortController();
6325
7123
  const controller = new AbortControllerClass();
@@ -6354,64 +7152,8 @@ async function browserRequest(url, options = {}) {
6354
7152
  throw error;
6355
7153
  }
6356
7154
  }
6357
- async function nodeRequest(url, options = {}) {
6358
- var _a;
6359
- const { method = "POST", headers = {}, body, timeout = 1e4 } = options;
6360
- try {
6361
- let nodeFetchFn;
6362
- try {
6363
- const nodeFetch = await import("node-fetch");
6364
- nodeFetchFn = nodeFetch.default || nodeFetch;
6365
- } catch (e) {
6366
- if (typeof process !== "undefined" && ((_a = process.versions) == null ? void 0 : _a.node)) {
6367
- throw new Error("node-fetch is required in Node.js environment. Please install it: pnpm add node-fetch");
6368
- }
6369
- throw new Error("Network request failed: node-fetch not available");
6370
- }
6371
- const AbortControllerClass = getAbortController();
6372
- const controller = new AbortControllerClass();
6373
- const timeoutId = setTimeout(() => controller.abort(), timeout);
6374
- try {
6375
- const response = await nodeFetchFn(url, {
6376
- method,
6377
- headers: {
6378
- "Content-Type": "application/json",
6379
- ...headers
6380
- },
6381
- body,
6382
- signal: controller.signal
6383
- });
6384
- clearTimeout(timeoutId);
6385
- let data;
6386
- const contentType = response.headers.get("content-type");
6387
- if (contentType && contentType.includes("application/json")) {
6388
- data = await response.json();
6389
- } else {
6390
- data = await response.text();
6391
- }
6392
- return {
6393
- ok: response.ok,
6394
- status: response.status,
6395
- statusText: response.statusText,
6396
- data
6397
- };
6398
- } catch (error) {
6399
- clearTimeout(timeoutId);
6400
- throw error;
6401
- }
6402
- } catch (error) {
6403
- throw error;
6404
- }
6405
- }
6406
7155
  async function request(url, options = {}) {
6407
- var _a;
6408
- if (typeof process !== "undefined" && ((_a = process.versions) == null ? void 0 : _a.node)) {
6409
- return nodeRequest(url, options);
6410
- }
6411
- if (typeof window !== "undefined") {
6412
- return browserRequest(url, options);
6413
- }
6414
- return browserRequest(url, options);
7156
+ return universalRequest(url, options);
6415
7157
  }
6416
7158
  function post(url, data, options = {}) {
6417
7159
  const body = typeof data === "string" ? data : JSON.stringify(data);
@@ -6428,14 +7170,15 @@ class RetryHandler {
6428
7170
  maxDelay = 1e4,
6429
7171
  shouldRetry = () => true
6430
7172
  } = options;
7173
+ const actualMaxRetries = Math.min(maxRetries, 100);
6431
7174
  let lastError;
6432
7175
  let delay = initialDelay;
6433
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
7176
+ for (let attempt = 0; attempt <= actualMaxRetries; attempt++) {
6434
7177
  try {
6435
7178
  return await fn();
6436
7179
  } catch (error) {
6437
7180
  lastError = error;
6438
- if (attempt < maxRetries && shouldRetry(error)) {
7181
+ if (attempt < actualMaxRetries && shouldRetry(error)) {
6439
7182
  await this.sleep(delay);
6440
7183
  delay = Math.min(delay * 2, maxDelay);
6441
7184
  } else {
@@ -7116,8 +7859,8 @@ class BatchSender {
7116
7859
  __publicField(this, "eventCallbacks", /* @__PURE__ */ new Map());
7117
7860
  this.sender = new Sender(serverUrl);
7118
7861
  this.offlineStorage = new OfflineStorage(options.maxOfflineStorageSize ?? 1e3);
7119
- this.batchSize = options.batchSize || 10;
7120
- this.batchWait = options.batchWait || 5e3;
7862
+ this.batchSize = options.batchSize ?? 10;
7863
+ this.batchWait = options.batchWait ?? 5e3;
7121
7864
  this.enableOfflineStorage = options.enableOfflineStorage ?? true;
7122
7865
  this.initPromise = this.init();
7123
7866
  }
@@ -7147,10 +7890,18 @@ class BatchSender {
7147
7890
  if (options == null ? void 0 : options.onBatchComplete) {
7148
7891
  this.eventCallbacks.set(event, options);
7149
7892
  }
7893
+ if (this.batchSize === 0) {
7894
+ this.flush();
7895
+ return;
7896
+ }
7150
7897
  if (this.queue.length >= this.batchSize) {
7151
7898
  this.flush();
7152
7899
  return;
7153
7900
  }
7901
+ if (this.batchWait === 0) {
7902
+ this.flush();
7903
+ return;
7904
+ }
7154
7905
  if (!this.timer) {
7155
7906
  this.timer = setTimeout(() => {
7156
7907
  this.flush();
@@ -7628,192 +8379,1883 @@ class SessionReplay {
7628
8379
  this.clear();
7629
8380
  }
7630
8381
  }
7631
- class Tracker {
7632
- constructor(config) {
7633
- __publicField(this, "config");
7634
- __publicField(this, "autoTracker", null);
7635
- __publicField(this, "errorTracker", null);
7636
- __publicField(this, "performanceTracker", null);
7637
- __publicField(this, "customTracker");
7638
- __publicField(this, "dataProcessor");
7639
- __publicField(this, "batchSender");
7640
- __publicField(this, "pluginManager");
7641
- __publicField(this, "behaviorAnalysis");
7642
- __publicField(this, "sessionReplay", null);
7643
- __publicField(this, "userInfo", null);
7644
- __publicField(this, "deviceId");
7645
- __publicField(this, "sessionId");
7646
- if (!config.appId) {
7647
- throw new Error("Tracker: appId is required");
7648
- }
7649
- this.config = new ConfigManager(config);
7650
- this.deviceId = config.deviceId || getDeviceId();
7651
- this.sessionId = getSessionId();
7652
- this.customTracker = new CustomTracker();
7653
- this.dataProcessor = new DataProcessor();
7654
- const serverUrl = config.serverUrl || "about:blank";
7655
- this.batchSender = new BatchSender(serverUrl, {
7656
- batchSize: this.config.get("batchSize"),
7657
- batchWait: this.config.get("batchWait"),
7658
- enableOfflineStorage: this.config.get("enableOfflineStorage") ?? true,
7659
- // 未设置serverUrl时强制启用离线存储
7660
- maxOfflineStorageSize: this.config.get("maxOfflineStorageSize")
7661
- });
7662
- if (!config.serverUrl) {
7663
- this.batchSender.setServerUrl("");
7664
- }
7665
- this.pluginManager = new PluginManager();
7666
- this.behaviorAnalysis = new BehaviorAnalysis();
7667
- this.pluginManager.setTrackerInstance({
7668
- send: this.send.bind(this),
7669
- getConfig: () => this.config.getAll(),
7670
- setUser: this.setUser.bind(this),
7671
- getUser: () => this.userInfo
7672
- });
8382
+ class PerformanceMonitor {
8383
+ constructor(options = {}) {
8384
+ __publicField(this, "events", []);
8385
+ __publicField(this, "alerts", []);
8386
+ __publicField(this, "alertRules", []);
8387
+ __publicField(this, "options");
8388
+ __publicField(this, "storageKey");
8389
+ __publicField(this, "maxStoredEvents");
8390
+ __publicField(this, "retentionTime");
8391
+ __publicField(this, "cleanupTimer", null);
8392
+ this.options = {
8393
+ enableAlerts: options.enableAlerts ?? true,
8394
+ defaultAlertRules: options.defaultAlertRules ?? this.getDefaultAlertRules(),
8395
+ onAlert: options.onAlert ?? (() => {
8396
+ }),
8397
+ storageKey: options.storageKey ?? "__tracker_performance_events__",
8398
+ maxStoredEvents: options.maxStoredEvents ?? 1e4,
8399
+ retentionTime: options.retentionTime ?? 7 * 24 * 60 * 60 * 1e3
8400
+ // 7天
8401
+ };
8402
+ this.storageKey = this.options.storageKey;
8403
+ this.maxStoredEvents = this.options.maxStoredEvents;
8404
+ this.retentionTime = this.options.retentionTime;
8405
+ this.alertRules = this.options.defaultAlertRules;
7673
8406
  this.init();
7674
- if (typeof window !== "undefined") {
7675
- window.__trackerEventBus = {
7676
- emit: (event, data) => {
7677
- eventBus.emit(event, data);
7678
- }
7679
- };
7680
- }
7681
8407
  }
7682
8408
  /**
7683
8409
  * 初始化
7684
8410
  */
7685
8411
  init() {
7686
- if (this.config.get("enableAutoTrack")) {
7687
- this.autoTracker = new AutoTracker();
7688
- this.autoTracker.init();
7689
- this.setupAutoTrackListeners();
7690
- }
7691
- if (this.config.get("enableErrorTrack")) {
7692
- this.errorTracker = new ErrorTracker();
7693
- this.errorTracker.init();
7694
- }
7695
- if (this.config.get("enablePerformanceTrack")) {
7696
- this.performanceTracker = new PerformanceTracker();
7697
- this.performanceTracker.init();
7698
- }
7699
- if (this.config.get("enableSessionReplay")) {
7700
- this.sessionReplay = new SessionReplay();
7701
- this.sessionReplay.start();
7702
- }
7703
- if (this.config.get("userId")) {
7704
- this.setUser({ userId: this.config.get("userId") });
7705
- }
8412
+ const handler = (...args) => {
8413
+ const event = args[0];
8414
+ this.handlePerformanceEvent(event);
8415
+ };
8416
+ eventBus.on("performance", handler);
8417
+ this._performanceHandler = handler;
8418
+ this.loadHistoryData();
8419
+ this.startCleanupTimer();
7706
8420
  }
7707
8421
  /**
7708
- * 设置自动埋点监听器
8422
+ * 处理性能事件
7709
8423
  */
7710
- setupAutoTrackListeners() {
7711
- eventBus.on("pageview", (event) => {
7712
- this.send(event);
7713
- });
7714
- eventBus.on("click", (event) => {
7715
- this.send(event);
7716
- });
7717
- eventBus.on("exposure", (event) => {
7718
- this.send(event);
7719
- });
7720
- eventBus.on("input", (event) => {
7721
- this.send(event);
7722
- });
7723
- eventBus.on("error", (event) => {
7724
- this.send(event);
7725
- });
7726
- eventBus.on("performance", (event) => {
7727
- this.send(event);
7728
- });
8424
+ handlePerformanceEvent(event) {
8425
+ this.events.push(event);
8426
+ if (this.events.length > this.maxStoredEvents) {
8427
+ this.events = this.events.slice(-this.maxStoredEvents);
8428
+ }
8429
+ if (this.options.enableAlerts) {
8430
+ this.checkAlertRules(event);
8431
+ }
8432
+ this.saveToStorage();
7729
8433
  }
7730
8434
  /**
7731
- * 发送事件
8435
+ * 检查告警规则
7732
8436
  */
7733
- send(event) {
7734
- try {
7735
- const enrichedEvent = this.enrichEvent(event);
7736
- const processedEvent = this.dataProcessor.process(enrichedEvent, {
7737
- sampleRate: this.config.get("sampleRate"),
7738
- enableDeduplication: true,
7739
- isCritical: event.type === "error"
7740
- });
7741
- if (!processedEvent) {
7742
- return;
8437
+ checkAlertRules(event) {
8438
+ if (event.metricType !== "navigation" && event.metricType !== "longtask") {
8439
+ return;
8440
+ }
8441
+ for (const rule of this.alertRules) {
8442
+ if (!rule.enabled) {
8443
+ continue;
7743
8444
  }
7744
- const beforeSend = this.config.get("beforeSend");
7745
- if (beforeSend) {
7746
- const result = beforeSend(processedEvent);
7747
- if (result === false || result === null) {
7748
- return;
7749
- }
7750
- if (result) {
7751
- Object.assign(processedEvent, result);
7752
- }
8445
+ let value;
8446
+ if (rule.metric === "ttfb" && event.metricType === "navigation") {
8447
+ value = event.metrics.ttfb;
8448
+ } else if (rule.metric === "total" && event.metricType === "navigation") {
8449
+ value = event.metrics.total;
8450
+ } else if (rule.metric === "longtask" && event.metricType === "longtask") {
8451
+ value = event.metrics.duration;
8452
+ } else {
8453
+ continue;
7753
8454
  }
7754
- this.batchSender.add(processedEvent, {
7755
- onBatchComplete: (events, success) => {
7756
- const afterSend = this.config.get("afterSend");
7757
- if (afterSend) {
7758
- events.forEach((event2) => {
7759
- setTimeout(() => {
7760
- afterSend(event2, success);
7761
- }, 0);
7762
- });
7763
- }
7764
- }
7765
- });
7766
- if (event.type === "pageview") {
7767
- this.behaviorAnalysis.analyzePageView(processedEvent);
8455
+ if (value === void 0) {
8456
+ continue;
7768
8457
  }
7769
- } catch (error) {
7770
- const onError = this.config.get("onError");
7771
- if (onError) {
7772
- onError(error);
7773
- } else {
7774
- console.error("Tracker: Error sending event:", error);
8458
+ let triggered = false;
8459
+ switch (rule.operator) {
8460
+ case ">":
8461
+ triggered = value > rule.threshold;
8462
+ break;
8463
+ case ">=":
8464
+ triggered = value >= rule.threshold;
8465
+ break;
8466
+ case "<":
8467
+ triggered = value < rule.threshold;
8468
+ break;
8469
+ case "<=":
8470
+ triggered = value <= rule.threshold;
8471
+ break;
8472
+ }
8473
+ if (triggered) {
8474
+ this.triggerAlert(rule, value, event);
7775
8475
  }
7776
8476
  }
7777
8477
  }
7778
8478
  /**
7779
- * 丰富事件数据
8479
+ * 触发告警
7780
8480
  */
7781
- enrichEvent(event) {
7782
- var _a;
7783
- const userId = event.userId || ((_a = this.userInfo) == null ? void 0 : _a.userId) || this.config.get("userId");
7784
- const customProperties = this.config.get("customProperties") || {};
7785
- const eventProperties = event.properties || {};
7786
- const mergedProperties = {
7787
- ...customProperties,
7788
- ...eventProperties
7789
- };
7790
- const finalProperties = Object.keys(mergedProperties).length > 0 ? mergedProperties : void 0;
7791
- const enriched = {
7792
- ...event,
7793
- deviceId: event.deviceId || this.deviceId,
7794
- sessionId: event.sessionId || this.sessionId
7795
- };
7796
- if (userId && userId !== "") {
7797
- enriched.userId = userId;
8481
+ triggerAlert(rule, value, event) {
8482
+ let level = "warning";
8483
+ if (value > rule.threshold * 2) {
8484
+ level = "critical";
8485
+ } else if (value > rule.threshold * 1.5) {
8486
+ level = "error";
7798
8487
  }
7799
- if (finalProperties) {
7800
- enriched.properties = finalProperties;
8488
+ const alert2 = {
8489
+ id: `alert-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
8490
+ ruleId: rule.id,
8491
+ level,
8492
+ message: rule.message || `${rule.name}: ${value.toFixed(2)}ms 超过阈值 ${rule.threshold}ms`,
8493
+ value,
8494
+ threshold: rule.threshold,
8495
+ timestamp: Date.now(),
8496
+ url: event.url,
8497
+ resolved: false
8498
+ };
8499
+ this.alerts.push(alert2);
8500
+ if (this.alerts.length > 1e3) {
8501
+ this.alerts = this.alerts.slice(-1e3);
7801
8502
  }
7802
- return enriched;
8503
+ this.options.onAlert(alert2);
7803
8504
  }
7804
8505
  /**
7805
- * 追踪自定义事件
8506
+ * 获取默认告警规则
7806
8507
  */
7807
- track(eventName, properties) {
7808
- const event = this.customTracker.track(eventName, properties);
7809
- this.send(event);
8508
+ getDefaultAlertRules() {
8509
+ return [
8510
+ {
8511
+ id: "ttfb-warning",
8512
+ name: "TTFB告警",
8513
+ metric: "ttfb",
8514
+ threshold: 1e3,
8515
+ operator: ">",
8516
+ enabled: true,
8517
+ message: "TTFB超过1000ms,可能影响用户体验"
8518
+ },
8519
+ {
8520
+ id: "total-load-warning",
8521
+ name: "总加载时间告警",
8522
+ metric: "total",
8523
+ threshold: 3e3,
8524
+ operator: ">",
8525
+ enabled: true,
8526
+ message: "页面总加载时间超过3000ms,可能影响用户体验"
8527
+ },
8528
+ {
8529
+ id: "longtask-warning",
8530
+ name: "长任务告警",
8531
+ metric: "longtask",
8532
+ threshold: 50,
8533
+ operator: ">",
8534
+ enabled: true,
8535
+ message: "检测到长任务超过50ms,可能阻塞主线程"
8536
+ }
8537
+ ];
7810
8538
  }
7811
8539
  /**
7812
- * 设置用户信息
8540
+ * 获取性能指标
7813
8541
  */
7814
- setUser(user) {
7815
- this.userInfo = { ...this.userInfo, ...user };
7816
- this.config.set("userId", user.userId || "");
8542
+ getMetrics(startTime, endTime) {
8543
+ const now = Date.now();
8544
+ const start = startTime || now - 36e5;
8545
+ const end = endTime || now;
8546
+ const filteredEvents = this.events.filter(
8547
+ (e) => e.timestamp >= start && e.timestamp <= end
8548
+ );
8549
+ const navigationEvents = filteredEvents.filter(
8550
+ (e) => e.metricType === "navigation"
8551
+ );
8552
+ const resourceEvents = filteredEvents.filter(
8553
+ (e) => e.metricType === "resource"
8554
+ );
8555
+ const longTaskEvents = filteredEvents.filter(
8556
+ (e) => e.metricType === "longtask"
8557
+ );
8558
+ let navigation = {
8559
+ dns: 0,
8560
+ tcp: 0,
8561
+ ssl: 0,
8562
+ ttfb: 0,
8563
+ response: 0,
8564
+ domParse: 0,
8565
+ resourceLoad: 0,
8566
+ total: 0,
8567
+ domContentLoaded: 0,
8568
+ load: 0
8569
+ };
8570
+ if (navigationEvents.length > 0) {
8571
+ const metrics = navigationEvents.map((e) => e.metrics);
8572
+ navigation = {
8573
+ dns: this.average(metrics, "dns"),
8574
+ tcp: this.average(metrics, "tcp"),
8575
+ ssl: this.average(metrics, "ssl"),
8576
+ ttfb: this.average(metrics, "ttfb"),
8577
+ response: this.average(metrics, "response"),
8578
+ domParse: this.average(metrics, "domParse"),
8579
+ resourceLoad: this.average(metrics, "resourceLoad"),
8580
+ total: this.average(metrics, "total"),
8581
+ domContentLoaded: this.average(metrics, "domContentLoaded"),
8582
+ load: this.average(metrics, "load")
8583
+ };
8584
+ }
8585
+ const resources = {
8586
+ total: resourceEvents.length,
8587
+ avgLoadTime: 0,
8588
+ maxLoadTime: 0,
8589
+ totalSize: 0,
8590
+ byType: {}
8591
+ };
8592
+ if (resourceEvents.length > 0) {
8593
+ const loadTimes = resourceEvents.map((e) => e.metrics.total || 0).filter((t) => t > 0);
8594
+ if (loadTimes.length > 0) {
8595
+ resources.avgLoadTime = loadTimes.reduce((a, b) => a + b, 0) / loadTimes.length;
8596
+ resources.maxLoadTime = Math.max(...loadTimes);
8597
+ }
8598
+ resources.totalSize = resourceEvents.reduce(
8599
+ (sum, e) => sum + (e.metrics.size || 0),
8600
+ 0
8601
+ );
8602
+ const typeMap = /* @__PURE__ */ new Map();
8603
+ resourceEvents.forEach((e) => {
8604
+ const metricsWithString = e.metrics;
8605
+ const type = metricsWithString.resourceType || "unknown";
8606
+ if (!typeMap.has(type)) {
8607
+ typeMap.set(type, []);
8608
+ }
8609
+ typeMap.get(type).push(metricsWithString.total || 0);
8610
+ });
8611
+ typeMap.forEach((times, type) => {
8612
+ const typeEvents = resourceEvents.filter((e) => {
8613
+ const metricsWithString = e.metrics;
8614
+ return metricsWithString.resourceType === type;
8615
+ });
8616
+ resources.byType[type] = {
8617
+ count: times.length,
8618
+ avgLoadTime: times.reduce((a, b) => a + b, 0) / times.length,
8619
+ totalSize: typeEvents.reduce((sum, e) => {
8620
+ const metricsWithString = e.metrics;
8621
+ return sum + (metricsWithString.size || 0);
8622
+ }, 0)
8623
+ };
8624
+ });
8625
+ }
8626
+ const longTasks = {
8627
+ total: longTaskEvents.length,
8628
+ avgDuration: 0,
8629
+ maxDuration: 0,
8630
+ recent: longTaskEvents.slice(-10).map((e) => ({
8631
+ duration: e.metrics.duration || 0,
8632
+ startTime: e.metrics.startTime || 0,
8633
+ timestamp: e.timestamp
8634
+ }))
8635
+ };
8636
+ if (longTaskEvents.length > 0) {
8637
+ const durations = longTaskEvents.map((e) => e.metrics.duration || 0).filter((d) => d > 0);
8638
+ if (durations.length > 0) {
8639
+ longTasks.avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
8640
+ longTasks.maxDuration = Math.max(...durations);
8641
+ }
8642
+ }
8643
+ return {
8644
+ navigation,
8645
+ resources,
8646
+ longTasks,
8647
+ timeRange: {
8648
+ start,
8649
+ end
8650
+ }
8651
+ };
8652
+ }
8653
+ /**
8654
+ * 计算平均值
8655
+ */
8656
+ average(metrics, key) {
8657
+ const values = metrics.map((m) => m[key] || 0).filter((v) => v > 0);
8658
+ if (values.length === 0) {
8659
+ return 0;
8660
+ }
8661
+ return values.reduce((a, b) => a + b, 0) / values.length;
8662
+ }
8663
+ /**
8664
+ * 生成性能报告
8665
+ */
8666
+ generateReport(options = {}) {
8667
+ const now = Date.now();
8668
+ const startTime = options.startTime || (options.duration ? now - options.duration : now - 36e5);
8669
+ const endTime = options.endTime || now;
8670
+ const metrics = this.getMetrics(startTime, endTime);
8671
+ const filteredAlerts = this.alerts.filter(
8672
+ (a) => a.timestamp >= startTime && a.timestamp <= endTime
8673
+ );
8674
+ const navigationEvents = this.events.filter(
8675
+ (e) => e.metricType === "navigation" && e.timestamp >= startTime && e.timestamp <= endTime
8676
+ );
8677
+ const summary = {
8678
+ pageLoads: navigationEvents.length,
8679
+ avgTTFB: metrics.navigation.ttfb,
8680
+ avgTotalLoadTime: metrics.navigation.total,
8681
+ totalLongTasks: metrics.longTasks.total,
8682
+ totalResources: metrics.resources.total
8683
+ };
8684
+ const trends = this.generateTrends(startTime, endTime);
8685
+ return {
8686
+ id: `report-${Date.now()}`,
8687
+ generatedAt: now,
8688
+ timeRange: {
8689
+ start: startTime,
8690
+ end: endTime
8691
+ },
8692
+ summary,
8693
+ metrics,
8694
+ alerts: filteredAlerts,
8695
+ trends
8696
+ };
8697
+ }
8698
+ /**
8699
+ * 生成趋势数据
8700
+ */
8701
+ generateTrends(startTime, endTime) {
8702
+ const trends = [];
8703
+ const interval = 36e5;
8704
+ let current = startTime;
8705
+ while (current < endTime) {
8706
+ const next = Math.min(current + interval, endTime);
8707
+ const hourMetrics = this.getMetrics(current, next);
8708
+ trends.push({
8709
+ time: current,
8710
+ metrics: {
8711
+ navigation: hourMetrics.navigation,
8712
+ resources: {
8713
+ total: hourMetrics.resources.total,
8714
+ avgLoadTime: hourMetrics.resources.avgLoadTime,
8715
+ maxLoadTime: hourMetrics.resources.maxLoadTime,
8716
+ totalSize: hourMetrics.resources.totalSize,
8717
+ byType: hourMetrics.resources.byType
8718
+ },
8719
+ longTasks: {
8720
+ total: hourMetrics.longTasks.total,
8721
+ avgDuration: hourMetrics.longTasks.avgDuration,
8722
+ maxDuration: hourMetrics.longTasks.maxDuration,
8723
+ recent: hourMetrics.longTasks.recent
8724
+ }
8725
+ }
8726
+ });
8727
+ current = next;
8728
+ }
8729
+ return trends;
8730
+ }
8731
+ /**
8732
+ * 获取告警列表
8733
+ */
8734
+ getAlerts(resolved) {
8735
+ if (resolved === void 0) {
8736
+ return [...this.alerts];
8737
+ }
8738
+ return this.alerts.filter((a) => a.resolved === resolved);
8739
+ }
8740
+ /**
8741
+ * 添加告警规则
8742
+ */
8743
+ addAlertRule(rule) {
8744
+ this.alertRules.push(rule);
8745
+ }
8746
+ /**
8747
+ * 移除告警规则
8748
+ */
8749
+ removeAlertRule(ruleId) {
8750
+ const index = this.alertRules.findIndex((r) => r.id === ruleId);
8751
+ if (index >= 0) {
8752
+ this.alertRules.splice(index, 1);
8753
+ return true;
8754
+ }
8755
+ return false;
8756
+ }
8757
+ /**
8758
+ * 获取告警规则
8759
+ */
8760
+ getAlertRules() {
8761
+ return [...this.alertRules];
8762
+ }
8763
+ /**
8764
+ * 标记告警为已处理
8765
+ */
8766
+ resolveAlert(alertId) {
8767
+ const alert2 = this.alerts.find((a) => a.id === alertId);
8768
+ if (alert2) {
8769
+ alert2.resolved = true;
8770
+ return true;
8771
+ }
8772
+ return false;
8773
+ }
8774
+ /**
8775
+ * 保存到存储
8776
+ */
8777
+ async saveToStorage() {
8778
+ try {
8779
+ const data = {
8780
+ events: this.events.slice(-1e3),
8781
+ // 只保存最近1000个事件
8782
+ alerts: this.alerts.slice(-100),
8783
+ // 只保存最近100个告警
8784
+ timestamp: Date.now()
8785
+ };
8786
+ await indexedDBStorage.setItem(this.storageKey, JSON.stringify(data));
8787
+ } catch (error) {
8788
+ console.error("PerformanceMonitor: Failed to save to storage:", error);
8789
+ }
8790
+ }
8791
+ /**
8792
+ * 加载历史数据
8793
+ */
8794
+ async loadHistoryData() {
8795
+ try {
8796
+ const dataStr = await indexedDBStorage.getItem(this.storageKey);
8797
+ if (dataStr) {
8798
+ const data = JSON.parse(dataStr);
8799
+ if (data.events) {
8800
+ this.events = data.events;
8801
+ }
8802
+ if (data.alerts) {
8803
+ this.alerts = data.alerts;
8804
+ }
8805
+ }
8806
+ } catch (error) {
8807
+ console.error("PerformanceMonitor: Failed to load history data:", error);
8808
+ }
8809
+ }
8810
+ /**
8811
+ * 启动清理定时器
8812
+ */
8813
+ startCleanupTimer() {
8814
+ if (this.cleanupTimer || typeof window === "undefined") {
8815
+ return;
8816
+ }
8817
+ this.cleanupTimer = window.setInterval(() => {
8818
+ this.cleanup();
8819
+ }, 36e5);
8820
+ }
8821
+ /**
8822
+ * 清理过期数据
8823
+ */
8824
+ cleanup() {
8825
+ const now = Date.now();
8826
+ const cutoffTime = now - this.retentionTime;
8827
+ this.events = this.events.filter((e) => e.timestamp >= cutoffTime);
8828
+ this.alerts = this.alerts.filter((a) => a.timestamp >= cutoffTime);
8829
+ this.saveToStorage();
8830
+ }
8831
+ /**
8832
+ * 销毁
8833
+ */
8834
+ destroy() {
8835
+ const handler = this._performanceHandler;
8836
+ if (handler) {
8837
+ eventBus.off("performance", handler);
8838
+ }
8839
+ if (this.cleanupTimer && typeof window !== "undefined") {
8840
+ clearInterval(this.cleanupTimer);
8841
+ this.cleanupTimer = null;
8842
+ }
8843
+ }
8844
+ }
8845
+ const PERFORMANCE_PANEL_HTML = `
8846
+ <!DOCTYPE html>
8847
+ <html lang="zh-CN">
8848
+ <head>
8849
+ <meta charset="UTF-8">
8850
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8851
+ <title>性能监控面板 - Tracker SDK</title>
8852
+ <style>
8853
+ * {
8854
+ margin: 0;
8855
+ padding: 0;
8856
+ box-sizing: border-box;
8857
+ }
8858
+ body {
8859
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
8860
+ background: #f5f5f5;
8861
+ padding: 20px;
8862
+ }
8863
+ .container {
8864
+ max-width: 1400px;
8865
+ margin: 0 auto;
8866
+ background: white;
8867
+ border-radius: 8px;
8868
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
8869
+ padding: 30px;
8870
+ }
8871
+ h1 {
8872
+ color: #333;
8873
+ margin-bottom: 10px;
8874
+ border-bottom: 3px solid #9C27B0;
8875
+ padding-bottom: 10px;
8876
+ }
8877
+ .subtitle {
8878
+ color: #666;
8879
+ margin-bottom: 30px;
8880
+ }
8881
+ .toolbar {
8882
+ display: flex;
8883
+ gap: 10px;
8884
+ margin-bottom: 20px;
8885
+ flex-wrap: wrap;
8886
+ }
8887
+ button {
8888
+ background: #9C27B0;
8889
+ color: white;
8890
+ border: none;
8891
+ padding: 10px 20px;
8892
+ border-radius: 4px;
8893
+ cursor: pointer;
8894
+ font-size: 14px;
8895
+ transition: background 0.3s;
8896
+ }
8897
+ button:hover {
8898
+ background: #7B1FA2;
8899
+ }
8900
+ button.secondary {
8901
+ background: #2196F3;
8902
+ }
8903
+ button.secondary:hover {
8904
+ background: #1976D2;
8905
+ }
8906
+ button.success {
8907
+ background: #4CAF50;
8908
+ }
8909
+ button.success:hover {
8910
+ background: #45a049;
8911
+ }
8912
+ .stats-grid {
8913
+ display: grid;
8914
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
8915
+ gap: 15px;
8916
+ margin-bottom: 30px;
8917
+ }
8918
+ .stat-card {
8919
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
8920
+ color: white;
8921
+ padding: 20px;
8922
+ border-radius: 8px;
8923
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
8924
+ }
8925
+ .stat-card.warning {
8926
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
8927
+ }
8928
+ .stat-card.success {
8929
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
8930
+ }
8931
+ .stat-card.info {
8932
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
8933
+ }
8934
+ .stat-label {
8935
+ font-size: 12px;
8936
+ opacity: 0.9;
8937
+ margin-bottom: 5px;
8938
+ }
8939
+ .stat-value {
8940
+ font-size: 32px;
8941
+ font-weight: bold;
8942
+ }
8943
+ .stat-unit {
8944
+ font-size: 14px;
8945
+ opacity: 0.8;
8946
+ margin-left: 5px;
8947
+ }
8948
+ .tabs {
8949
+ display: flex;
8950
+ gap: 10px;
8951
+ margin-bottom: 20px;
8952
+ border-bottom: 2px solid #eee;
8953
+ }
8954
+ .tab {
8955
+ padding: 10px 20px;
8956
+ cursor: pointer;
8957
+ border: none;
8958
+ background: none;
8959
+ color: #666;
8960
+ font-size: 14px;
8961
+ border-bottom: 2px solid transparent;
8962
+ margin-bottom: -2px;
8963
+ }
8964
+ .tab.active {
8965
+ color: #9C27B0;
8966
+ border-bottom-color: #9C27B0;
8967
+ font-weight: bold;
8968
+ }
8969
+ .tab-content {
8970
+ display: none;
8971
+ }
8972
+ .tab-content.active {
8973
+ display: block;
8974
+ }
8975
+ .metrics-table {
8976
+ width: 100%;
8977
+ border-collapse: collapse;
8978
+ margin-top: 20px;
8979
+ }
8980
+ .metrics-table th,
8981
+ .metrics-table td {
8982
+ padding: 12px;
8983
+ text-align: left;
8984
+ border-bottom: 1px solid #eee;
8985
+ }
8986
+ .metrics-table th {
8987
+ background: #f9f9f9;
8988
+ font-weight: bold;
8989
+ color: #333;
8990
+ }
8991
+ .metrics-table tr:hover {
8992
+ background: #f9f9f9;
8993
+ }
8994
+ .alert-item {
8995
+ padding: 15px;
8996
+ border-left: 4px solid #f44336;
8997
+ background: #fff3f3;
8998
+ margin-bottom: 10px;
8999
+ border-radius: 4px;
9000
+ }
9001
+ .alert-item.warning {
9002
+ border-left-color: #FF9800;
9003
+ background: #fff8f0;
9004
+ }
9005
+ .alert-item.error {
9006
+ border-left-color: #f44336;
9007
+ background: #fff3f3;
9008
+ }
9009
+ .alert-item.critical {
9010
+ border-left-color: #d32f2f;
9011
+ background: #ffebee;
9012
+ }
9013
+ .alert-header {
9014
+ display: flex;
9015
+ justify-content: space-between;
9016
+ align-items: center;
9017
+ margin-bottom: 5px;
9018
+ }
9019
+ .alert-level {
9020
+ display: inline-block;
9021
+ padding: 4px 8px;
9022
+ border-radius: 4px;
9023
+ font-size: 12px;
9024
+ font-weight: bold;
9025
+ color: white;
9026
+ }
9027
+ .alert-level.warning {
9028
+ background: #FF9800;
9029
+ }
9030
+ .alert-level.error {
9031
+ background: #f44336;
9032
+ }
9033
+ .alert-level.critical {
9034
+ background: #d32f2f;
9035
+ }
9036
+ .alert-time {
9037
+ color: #999;
9038
+ font-size: 12px;
9039
+ }
9040
+ .chart-container {
9041
+ width: 100%;
9042
+ height: 300px;
9043
+ margin: 20px 0;
9044
+ position: relative;
9045
+ }
9046
+ .chart-bar {
9047
+ display: flex;
9048
+ align-items: flex-end;
9049
+ height: 250px;
9050
+ gap: 10px;
9051
+ padding: 20px;
9052
+ background: #f9f9f9;
9053
+ border-radius: 4px;
9054
+ }
9055
+ .chart-bar-item {
9056
+ flex: 1;
9057
+ background: linear-gradient(to top, #9C27B0, #7B1FA2);
9058
+ border-radius: 4px 4px 0 0;
9059
+ position: relative;
9060
+ min-height: 10px;
9061
+ }
9062
+ .chart-bar-label {
9063
+ text-align: center;
9064
+ margin-top: 5px;
9065
+ font-size: 12px;
9066
+ color: #666;
9067
+ }
9068
+ .chart-bar-value {
9069
+ position: absolute;
9070
+ top: -20px;
9071
+ left: 50%;
9072
+ transform: translateX(-50%);
9073
+ font-size: 11px;
9074
+ color: #333;
9075
+ font-weight: bold;
9076
+ }
9077
+ .loading {
9078
+ text-align: center;
9079
+ padding: 40px;
9080
+ color: #666;
9081
+ }
9082
+ .spinner {
9083
+ border: 3px solid #f3f3f3;
9084
+ border-top: 3px solid #9C27B0;
9085
+ border-radius: 50%;
9086
+ width: 40px;
9087
+ height: 40px;
9088
+ animation: spin 1s linear infinite;
9089
+ margin: 0 auto 20px;
9090
+ }
9091
+ @keyframes spin {
9092
+ 0% { transform: rotate(0deg); }
9093
+ 100% { transform: rotate(360deg); }
9094
+ }
9095
+ .empty-state {
9096
+ text-align: center;
9097
+ padding: 60px 20px;
9098
+ color: #999;
9099
+ }
9100
+ </style>
9101
+ </head>
9102
+ <body>
9103
+ <div class="container">
9104
+ <h1>📊 性能监控面板</h1>
9105
+ <p class="subtitle">实时性能指标监控和告警</p>
9106
+
9107
+ <div class="toolbar">
9108
+ <button onclick="refreshData()">🔄 刷新数据</button>
9109
+ <button class="secondary" onclick="generateReport()">📄 生成报告</button>
9110
+ <button class="success" onclick="exportData()">📥 导出数据</button>
9111
+ </div>
9112
+
9113
+ <div class="stats-grid" id="stats-grid">
9114
+ <div class="stat-card">
9115
+ <div class="stat-label">TTFB</div>
9116
+ <div class="stat-value" id="stat-ttfb">-<span class="stat-unit">ms</span></div>
9117
+ </div>
9118
+ <div class="stat-card">
9119
+ <div class="stat-label">总加载时间</div>
9120
+ <div class="stat-value" id="stat-total">-<span class="stat-unit">ms</span></div>
9121
+ </div>
9122
+ <div class="stat-card info">
9123
+ <div class="stat-label">资源总数</div>
9124
+ <div class="stat-value" id="stat-resources">-</div>
9125
+ </div>
9126
+ <div class="stat-card warning">
9127
+ <div class="stat-label">长任务</div>
9128
+ <div class="stat-value" id="stat-longtasks">-</div>
9129
+ </div>
9130
+ <div class="stat-card warning">
9131
+ <div class="stat-label">活跃告警</div>
9132
+ <div class="stat-value" id="stat-alerts">-</div>
9133
+ </div>
9134
+ </div>
9135
+
9136
+ <div class="tabs">
9137
+ <button class="tab active" onclick="switchTab('navigation')" data-tab="navigation">页面加载</button>
9138
+ <button class="tab" onclick="switchTab('resources')" data-tab="resources">资源加载</button>
9139
+ <button class="tab" onclick="switchTab('longtasks')" data-tab="longtasks">长任务</button>
9140
+ <button class="tab" onclick="switchTab('alerts')" data-tab="alerts">告警</button>
9141
+ </div>
9142
+
9143
+ <div id="tab-navigation" class="tab-content active">
9144
+ <h2>页面加载性能</h2>
9145
+ <div class="chart-container">
9146
+ <div class="chart-bar" id="navigation-chart"></div>
9147
+ </div>
9148
+ <table class="metrics-table">
9149
+ <thead>
9150
+ <tr>
9151
+ <th>指标</th>
9152
+ <th>值</th>
9153
+ <th>说明</th>
9154
+ </tr>
9155
+ </thead>
9156
+ <tbody id="navigation-metrics">
9157
+ </tbody>
9158
+ </table>
9159
+ </div>
9160
+
9161
+ <div id="tab-resources" class="tab-content">
9162
+ <h2>资源加载性能</h2>
9163
+ <table class="metrics-table">
9164
+ <thead>
9165
+ <tr>
9166
+ <th>资源类型</th>
9167
+ <th>数量</th>
9168
+ <th>平均加载时间</th>
9169
+ <th>总大小</th>
9170
+ </tr>
9171
+ </thead>
9172
+ <tbody id="resources-metrics">
9173
+ </tbody>
9174
+ </table>
9175
+ </div>
9176
+
9177
+ <div id="tab-longtasks" class="tab-content">
9178
+ <h2>长任务监控</h2>
9179
+ <table class="metrics-table">
9180
+ <thead>
9181
+ <tr>
9182
+ <th>时间</th>
9183
+ <th>持续时间</th>
9184
+ <th>开始时间</th>
9185
+ </tr>
9186
+ </thead>
9187
+ <tbody id="longtasks-metrics">
9188
+ </tbody>
9189
+ </table>
9190
+ </div>
9191
+
9192
+ <div id="tab-alerts" class="tab-content">
9193
+ <h2>性能告警</h2>
9194
+ <div id="alerts-list"></div>
9195
+ </div>
9196
+ </div>
9197
+
9198
+ <script>
9199
+ // 标记这是SDK内部面板,不应被监控
9200
+ window.__trackerInternalPanel = true;
9201
+
9202
+ let metricsData = null;
9203
+ let alertsData = [];
9204
+
9205
+ // 从父窗口获取Tracker实例
9206
+ function getTrackerInstance() {
9207
+ if (window.opener && window.opener.__trackerInstance) {
9208
+ return window.opener.__trackerInstance;
9209
+ }
9210
+ // 尝试从全局变量获取
9211
+ if (window.__trackerInstance) {
9212
+ return window.__trackerInstance;
9213
+ }
9214
+ return null;
9215
+ }
9216
+
9217
+ // 刷新数据
9218
+ async function refreshData() {
9219
+ const tracker = getTrackerInstance();
9220
+ if (!tracker) {
9221
+ alert('无法获取Tracker实例,请确保从Tracker打开此面板');
9222
+ return;
9223
+ }
9224
+
9225
+ try {
9226
+ // 获取性能指标
9227
+ metricsData = tracker.getPerformanceMetrics();
9228
+
9229
+ // 获取告警
9230
+ alertsData = tracker.getPerformanceMonitor().getAlerts(false);
9231
+
9232
+ // 更新UI
9233
+ updateStats();
9234
+ updateNavigationTab();
9235
+ updateResourcesTab();
9236
+ updateLongTasksTab();
9237
+ updateAlertsTab();
9238
+ } catch (error) {
9239
+ console.error('刷新数据失败:', error);
9240
+ alert('刷新数据失败: ' + (error instanceof Error ? error.message : String(error)));
9241
+ }
9242
+ }
9243
+
9244
+ // 更新统计卡片
9245
+ function updateStats() {
9246
+ if (!metricsData) return;
9247
+
9248
+ document.getElementById('stat-ttfb').innerHTML =
9249
+ Math.round(metricsData.navigation.ttfb) + '<span class="stat-unit">ms</span>';
9250
+ document.getElementById('stat-total').innerHTML =
9251
+ Math.round(metricsData.navigation.total) + '<span class="stat-unit">ms</span>';
9252
+ document.getElementById('stat-resources').textContent = metricsData.resources.total;
9253
+ document.getElementById('stat-longtasks').textContent = metricsData.longTasks.total;
9254
+ document.getElementById('stat-alerts').textContent = alertsData.length;
9255
+ }
9256
+
9257
+ // 更新页面加载标签页
9258
+ function updateNavigationTab() {
9259
+ if (!metricsData) return;
9260
+
9261
+ const nav = metricsData.navigation;
9262
+ const metrics = [
9263
+ { name: 'DNS查询', value: nav.dns, desc: '域名解析时间' },
9264
+ { name: 'TCP连接', value: nav.tcp, desc: 'TCP连接建立时间' },
9265
+ { name: 'SSL握手', value: nav.ssl, desc: 'SSL/TLS握手时间' },
9266
+ { name: 'TTFB', value: nav.ttfb, desc: '首字节时间' },
9267
+ { name: '响应时间', value: nav.response, desc: '服务器响应时间' },
9268
+ { name: 'DOM解析', value: nav.domParse, desc: 'DOM解析时间' },
9269
+ { name: '资源加载', value: nav.resourceLoad, desc: '资源加载时间' },
9270
+ { name: '总加载时间', value: nav.total, desc: '页面总加载时间' }
9271
+ ];
9272
+
9273
+ const tbody = document.getElementById('navigation-metrics');
9274
+ tbody.innerHTML = metrics.map(m => \`
9275
+ <tr>
9276
+ <td><strong>\${m.name}</strong></td>
9277
+ <td>\${Math.round(m.value)} ms</td>
9278
+ <td>\${m.desc}</td>
9279
+ </tr>
9280
+ \`).join('');
9281
+
9282
+ // 更新图表
9283
+ updateNavigationChart(metrics);
9284
+ }
9285
+
9286
+ // 更新导航图表
9287
+ function updateNavigationChart(metrics) {
9288
+ const chart = document.getElementById('navigation-chart');
9289
+ const values = metrics.map(m => m.value).filter(v => v > 0);
9290
+ const maxValue = values.length > 0 ? Math.max(...values) : 1;
9291
+
9292
+ chart.innerHTML = metrics.map(m => {
9293
+ const height = maxValue > 0 ? (m.value / maxValue) * 100 : 0;
9294
+ return \`
9295
+ <div class="chart-bar-item" style="height: \${height}%">
9296
+ <div class="chart-bar-value">\${Math.round(m.value)}ms</div>
9297
+ </div>
9298
+ \`;
9299
+ }).join('') + metrics.map(m => \`
9300
+ <div class="chart-bar-label">\${m.name}</div>
9301
+ \`).join('');
9302
+ }
9303
+
9304
+ // 更新资源标签页
9305
+ function updateResourcesTab() {
9306
+ if (!metricsData) return;
9307
+
9308
+ const resources = metricsData.resources;
9309
+ const tbody = document.getElementById('resources-metrics');
9310
+
9311
+ if (Object.keys(resources.byType).length === 0) {
9312
+ tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; color: #999;">暂无资源数据</td></tr>';
9313
+ return;
9314
+ }
9315
+
9316
+ tbody.innerHTML = Object.entries(resources.byType).map(([type, stats]) => \`
9317
+ <tr>
9318
+ <td><strong>\${type}</strong></td>
9319
+ <td>\${stats.count}</td>
9320
+ <td>\${Math.round(stats.avgLoadTime)} ms</td>
9321
+ <td>\${formatSize(stats.totalSize)}</td>
9322
+ </tr>
9323
+ \`).join('');
9324
+
9325
+ // 添加总计行
9326
+ tbody.innerHTML += \`
9327
+ <tr style="background: #f9f9f9; font-weight: bold;">
9328
+ <td>总计</td>
9329
+ <td>\${resources.total}</td>
9330
+ <td>\${Math.round(resources.avgLoadTime)} ms</td>
9331
+ <td>\${formatSize(resources.totalSize)}</td>
9332
+ </tr>
9333
+ \`;
9334
+ }
9335
+
9336
+ // 更新长任务标签页
9337
+ function updateLongTasksTab() {
9338
+ if (!metricsData) return;
9339
+
9340
+ const longTasks = metricsData.longTasks;
9341
+ const tbody = document.getElementById('longtasks-metrics');
9342
+
9343
+ if (longTasks.recent.length === 0) {
9344
+ tbody.innerHTML = '<tr><td colspan="3" style="text-align: center; color: #999;">暂无长任务数据</td></tr>';
9345
+ return;
9346
+ }
9347
+
9348
+ tbody.innerHTML = longTasks.recent.map(task => {
9349
+ const date = new Date(task.timestamp);
9350
+ return \`
9351
+ <tr>
9352
+ <td>\${date.toLocaleString()}</td>
9353
+ <td>\${Math.round(task.duration)} ms</td>
9354
+ <td>\${Math.round(task.startTime)}</td>
9355
+ </tr>
9356
+ \`;
9357
+ }).join('');
9358
+ }
9359
+
9360
+ // 更新告警标签页
9361
+ function updateAlertsTab() {
9362
+ const container = document.getElementById('alerts-list');
9363
+
9364
+ if (alertsData.length === 0) {
9365
+ container.innerHTML = '<div class="empty-state"><p>暂无告警</p></div>';
9366
+ return;
9367
+ }
9368
+
9369
+ container.innerHTML = alertsData.map(alert => {
9370
+ const date = new Date(alert.timestamp);
9371
+ return \`
9372
+ <div class="alert-item \${alert.level}">
9373
+ <div class="alert-header">
9374
+ <div>
9375
+ <span class="alert-level \${alert.level}">\${alert.level.toUpperCase()}</span>
9376
+ <strong style="margin-left: 10px;">\${alert.message}</strong>
9377
+ </div>
9378
+ <span class="alert-time">\${date.toLocaleString()}</span>
9379
+ </div>
9380
+ <div style="margin-top: 5px; color: #666; font-size: 13px;">
9381
+ 值: \${Math.round(alert.value)}ms | 阈值: \${alert.threshold}ms
9382
+ \${alert.url ? ' | URL: ' + alert.url : ''}
9383
+ </div>
9384
+ </div>
9385
+ \`;
9386
+ }).join('');
9387
+ }
9388
+
9389
+ // 切换标签页
9390
+ function switchTab(tabName) {
9391
+ // 更新标签按钮
9392
+ document.querySelectorAll('.tab').forEach(tab => {
9393
+ tab.classList.remove('active');
9394
+ if (tab.getAttribute('data-tab') === tabName) {
9395
+ tab.classList.add('active');
9396
+ }
9397
+ });
9398
+
9399
+ // 更新内容
9400
+ document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
9401
+ const targetTab = document.getElementById('tab-' + tabName);
9402
+ if (targetTab) {
9403
+ targetTab.classList.add('active');
9404
+ }
9405
+ }
9406
+
9407
+ // 生成报告
9408
+ async function generateReport() {
9409
+ const tracker = getTrackerInstance();
9410
+ if (!tracker) {
9411
+ alert('无法获取Tracker实例');
9412
+ return;
9413
+ }
9414
+
9415
+ try {
9416
+ const report = tracker.generatePerformanceReport({
9417
+ duration: 3600000 // 最近1小时
9418
+ });
9419
+
9420
+ // 下载JSON报告
9421
+ const dataStr = JSON.stringify(report, null, 2);
9422
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
9423
+ const url = URL.createObjectURL(dataBlob);
9424
+ const link = document.createElement('a');
9425
+ link.href = url;
9426
+ link.download = \`performance-report-\${new Date().toISOString().split('T')[0]}.json\`;
9427
+ link.click();
9428
+ URL.revokeObjectURL(url);
9429
+ } catch (error) {
9430
+ console.error('生成报告失败:', error);
9431
+ alert('生成报告失败: ' + (error instanceof Error ? error.message : String(error)));
9432
+ }
9433
+ }
9434
+
9435
+ // 导出数据
9436
+ function exportData() {
9437
+ if (!metricsData) {
9438
+ alert('请先刷新数据');
9439
+ return;
9440
+ }
9441
+
9442
+ const data = {
9443
+ metrics: metricsData,
9444
+ alerts: alertsData,
9445
+ exportedAt: new Date().toISOString()
9446
+ };
9447
+
9448
+ const dataStr = JSON.stringify(data, null, 2);
9449
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
9450
+ const url = URL.createObjectURL(dataBlob);
9451
+ const link = document.createElement('a');
9452
+ link.href = url;
9453
+ link.download = \`performance-data-\${new Date().toISOString().split('T')[0]}.json\`;
9454
+ link.click();
9455
+ URL.revokeObjectURL(url);
9456
+ }
9457
+
9458
+ // 格式化文件大小
9459
+ function formatSize(bytes) {
9460
+ if (bytes === 0) return '0 B';
9461
+ const k = 1024;
9462
+ const sizes = ['B', 'KB', 'MB', 'GB'];
9463
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
9464
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
9465
+ }
9466
+
9467
+ // 页面加载时自动刷新
9468
+ window.addEventListener('load', () => {
9469
+ setTimeout(refreshData, 500);
9470
+
9471
+ // 每30秒自动刷新
9472
+ setInterval(refreshData, 30000);
9473
+ });
9474
+ <\/script>
9475
+ </body>
9476
+ </html>
9477
+ `;
9478
+ const OFFLINE_STORAGE_VIEWER_HTML = `
9479
+ <!DOCTYPE html>
9480
+ <html lang="zh-CN">
9481
+ <head>
9482
+ <meta charset="UTF-8">
9483
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9484
+ <title>查看离线存储数据 - Tracker SDK</title>
9485
+ <style>
9486
+ * {
9487
+ margin: 0;
9488
+ padding: 0;
9489
+ box-sizing: border-box;
9490
+ }
9491
+ body {
9492
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
9493
+ background: #f5f5f5;
9494
+ padding: 20px;
9495
+ }
9496
+ .container {
9497
+ max-width: 1400px;
9498
+ margin: 0 auto;
9499
+ background: white;
9500
+ border-radius: 8px;
9501
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
9502
+ padding: 30px;
9503
+ }
9504
+ h1 {
9505
+ color: #333;
9506
+ margin-bottom: 10px;
9507
+ border-bottom: 3px solid #4CAF50;
9508
+ padding-bottom: 10px;
9509
+ }
9510
+ .subtitle {
9511
+ color: #666;
9512
+ margin-bottom: 30px;
9513
+ }
9514
+ .toolbar {
9515
+ display: flex;
9516
+ gap: 10px;
9517
+ margin-bottom: 20px;
9518
+ flex-wrap: wrap;
9519
+ }
9520
+ button {
9521
+ background: #4CAF50;
9522
+ color: white;
9523
+ border: none;
9524
+ padding: 10px 20px;
9525
+ border-radius: 4px;
9526
+ cursor: pointer;
9527
+ font-size: 14px;
9528
+ transition: background 0.3s;
9529
+ }
9530
+ button:hover {
9531
+ background: #45a049;
9532
+ }
9533
+ button.danger {
9534
+ background: #f44336;
9535
+ }
9536
+ button.danger:hover {
9537
+ background: #da190b;
9538
+ }
9539
+ button.secondary {
9540
+ background: #2196F3;
9541
+ }
9542
+ button.secondary:hover {
9543
+ background: #1976D2;
9544
+ }
9545
+ .stats {
9546
+ display: grid;
9547
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
9548
+ gap: 15px;
9549
+ margin-bottom: 20px;
9550
+ }
9551
+ .stat-card {
9552
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9553
+ color: white;
9554
+ padding: 20px;
9555
+ border-radius: 8px;
9556
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
9557
+ }
9558
+ .stat-card.success {
9559
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
9560
+ }
9561
+ .stat-card.warning {
9562
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
9563
+ }
9564
+ .stat-card.info {
9565
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
9566
+ }
9567
+ .stat-label {
9568
+ font-size: 12px;
9569
+ opacity: 0.9;
9570
+ margin-bottom: 5px;
9571
+ }
9572
+ .stat-value {
9573
+ font-size: 32px;
9574
+ font-weight: bold;
9575
+ }
9576
+ .filters {
9577
+ background: #f9f9f9;
9578
+ padding: 15px;
9579
+ border-radius: 4px;
9580
+ margin-bottom: 20px;
9581
+ }
9582
+ .filters label {
9583
+ display: inline-block;
9584
+ margin-right: 15px;
9585
+ font-size: 14px;
9586
+ color: #666;
9587
+ }
9588
+ .filters select, .filters input {
9589
+ padding: 5px 10px;
9590
+ border: 1px solid #ddd;
9591
+ border-radius: 4px;
9592
+ margin-left: 5px;
9593
+ }
9594
+ .events-container {
9595
+ max-height: 600px;
9596
+ overflow-y: auto;
9597
+ border: 1px solid #ddd;
9598
+ border-radius: 4px;
9599
+ }
9600
+ .event-item {
9601
+ border-bottom: 1px solid #eee;
9602
+ padding: 15px;
9603
+ transition: background 0.2s;
9604
+ }
9605
+ .event-item:hover {
9606
+ background: #f9f9f9;
9607
+ }
9608
+ .event-item:last-child {
9609
+ border-bottom: none;
9610
+ }
9611
+ .event-header {
9612
+ display: flex;
9613
+ justify-content: space-between;
9614
+ align-items: center;
9615
+ margin-bottom: 10px;
9616
+ }
9617
+ .event-type {
9618
+ display: inline-block;
9619
+ padding: 4px 8px;
9620
+ border-radius: 4px;
9621
+ font-size: 12px;
9622
+ font-weight: bold;
9623
+ color: white;
9624
+ }
9625
+ .event-type.pageview { background: #4CAF50; }
9626
+ .event-type.click { background: #2196F3; }
9627
+ .event-type.exposure { background: #FF9800; }
9628
+ .event-type.error { background: #f44336; }
9629
+ .event-type.performance { background: #9C27B0; }
9630
+ .event-type.custom { background: #607D8B; }
9631
+ .event-time {
9632
+ color: #999;
9633
+ font-size: 12px;
9634
+ }
9635
+ .event-details {
9636
+ font-size: 13px;
9637
+ color: #666;
9638
+ margin-top: 8px;
9639
+ }
9640
+ .event-details pre {
9641
+ background: #f5f5f5;
9642
+ padding: 10px;
9643
+ border-radius: 4px;
9644
+ overflow-x: auto;
9645
+ font-size: 12px;
9646
+ margin-top: 5px;
9647
+ }
9648
+ .empty-state {
9649
+ text-align: center;
9650
+ padding: 60px 20px;
9651
+ color: #999;
9652
+ }
9653
+ .empty-state svg {
9654
+ width: 100px;
9655
+ height: 100px;
9656
+ margin-bottom: 20px;
9657
+ opacity: 0.3;
9658
+ }
9659
+ .loading {
9660
+ text-align: center;
9661
+ padding: 40px;
9662
+ color: #666;
9663
+ }
9664
+ .spinner {
9665
+ border: 3px solid #f3f3f3;
9666
+ border-top: 3px solid #4CAF50;
9667
+ border-radius: 50%;
9668
+ width: 40px;
9669
+ height: 40px;
9670
+ animation: spin 1s linear infinite;
9671
+ margin: 0 auto 20px;
9672
+ }
9673
+ @keyframes spin {
9674
+ 0% { transform: rotate(0deg); }
9675
+ 100% { transform: rotate(360deg); }
9676
+ }
9677
+ .json-viewer {
9678
+ background: #f5f5f5;
9679
+ padding: 10px;
9680
+ border-radius: 4px;
9681
+ font-family: 'Courier New', monospace;
9682
+ font-size: 12px;
9683
+ max-height: 300px;
9684
+ overflow-y: auto;
9685
+ }
9686
+ </style>
9687
+ </head>
9688
+ <body>
9689
+ <div class="container">
9690
+ <h1>📦 离线存储数据查看器</h1>
9691
+ <p class="subtitle">查看 Tracker SDK 离线存储的所有事件数据</p>
9692
+
9693
+ <div class="toolbar">
9694
+ <button onclick="loadEvents()">🔄 刷新数据</button>
9695
+ <button class="secondary" onclick="exportData()">📥 导出数据</button>
9696
+ <button class="danger" onclick="clearStorage()">🗑️ 清空存储</button>
9697
+ <button onclick="openDevTools()">🔧 打开开发者工具</button>
9698
+ </div>
9699
+
9700
+ <div class="stats" id="stats">
9701
+ <div class="stat-card success">
9702
+ <div class="stat-label">总事件数</div>
9703
+ <div class="stat-value" id="total-count">-</div>
9704
+ </div>
9705
+ <div class="stat-card info">
9706
+ <div class="stat-label">存储大小</div>
9707
+ <div class="stat-value" id="storage-size">-</div>
9708
+ </div>
9709
+ <div class="stat-card warning">
9710
+ <div class="stat-label">事件类型分布</div>
9711
+ <div class="stat-value" id="type-distribution">-</div>
9712
+ </div>
9713
+ </div>
9714
+
9715
+ <div class="filters">
9716
+ <label>
9717
+ 事件类型:
9718
+ <select id="filter-type" onchange="filterEvents()">
9719
+ <option value="">全部</option>
9720
+ <option value="pageview">页面浏览</option>
9721
+ <option value="click">点击</option>
9722
+ <option value="exposure">曝光</option>
9723
+ <option value="error">错误</option>
9724
+ <option value="performance">性能</option>
9725
+ <option value="custom">自定义</option>
9726
+ </select>
9727
+ </label>
9728
+ <label>
9729
+ 搜索:
9730
+ <input type="text" id="search-input" placeholder="搜索事件..." oninput="filterEvents()">
9731
+ </label>
9732
+ <label>
9733
+ 排序:
9734
+ <select id="sort-order" onchange="filterEvents()">
9735
+ <option value="desc">最新优先</option>
9736
+ <option value="asc">最旧优先</option>
9737
+ </select>
9738
+ </label>
9739
+ </div>
9740
+
9741
+ <div class="events-container" id="events-container">
9742
+ <div class="loading">
9743
+ <div class="spinner"></div>
9744
+ <p>加载中...</p>
9745
+ </div>
9746
+ </div>
9747
+ </div>
9748
+
9749
+ <script>
9750
+ // 标记这是SDK内部面板,不应被监控
9751
+ window.__trackerInternalPanel = true;
9752
+
9753
+ let allEvents = [];
9754
+ let filteredEvents = [];
9755
+
9756
+ // 从父窗口获取Tracker实例
9757
+ function getTrackerInstance() {
9758
+ if (window.opener && window.opener.__trackerInstance) {
9759
+ return window.opener.__trackerInstance;
9760
+ }
9761
+ // 尝试从全局变量获取
9762
+ if (window.__trackerInstance) {
9763
+ return window.__trackerInstance;
9764
+ }
9765
+ return null;
9766
+ }
9767
+
9768
+ // 加载事件
9769
+ async function loadEvents() {
9770
+ const container = document.getElementById('events-container');
9771
+ container.innerHTML = '<div class="loading"><div class="spinner"></div><p>加载中...</p></div>';
9772
+
9773
+ try {
9774
+ const tracker = getTrackerInstance();
9775
+ if (tracker) {
9776
+ allEvents = await tracker.getOfflineEvents();
9777
+ } else {
9778
+ // 直接访问IndexedDB
9779
+ allEvents = await getEventsFromIndexedDB();
9780
+ }
9781
+
9782
+ updateStats();
9783
+ filterEvents();
9784
+ } catch (error) {
9785
+ container.innerHTML = \`<div class="empty-state"><p>加载失败: \${error.message}</p></div>\`;
9786
+ console.error('加载事件失败:', error);
9787
+ }
9788
+ }
9789
+
9790
+ // 直接从IndexedDB获取事件
9791
+ async function getEventsFromIndexedDB() {
9792
+ return new Promise((resolve, reject) => {
9793
+ const request = indexedDB.open('__tracker_db__', 1);
9794
+
9795
+ request.onsuccess = (event) => {
9796
+ const db = event.target.result;
9797
+ if (!db.objectStoreNames.contains('__tracker_store__')) {
9798
+ resolve([]);
9799
+ return;
9800
+ }
9801
+ const transaction = db.transaction(['__tracker_store__'], 'readonly');
9802
+ const store = transaction.objectStore('__tracker_store__');
9803
+ const index = store.index('key');
9804
+ const getRequest = index.get('__tracker_offline_events__');
9805
+
9806
+ getRequest.onsuccess = () => {
9807
+ const result = getRequest.result;
9808
+ if (result && result.value) {
9809
+ try {
9810
+ const events = JSON.parse(result.value);
9811
+ resolve(events);
9812
+ } catch (e) {
9813
+ resolve([]);
9814
+ }
9815
+ } else {
9816
+ resolve([]);
9817
+ }
9818
+ };
9819
+
9820
+ getRequest.onerror = () => {
9821
+ reject(new Error('获取数据失败'));
9822
+ };
9823
+ };
9824
+
9825
+ request.onerror = () => {
9826
+ reject(new Error('打开数据库失败'));
9827
+ };
9828
+ });
9829
+ }
9830
+
9831
+ // 更新统计信息
9832
+ function updateStats() {
9833
+ const totalCount = allEvents.length;
9834
+ document.getElementById('total-count').textContent = totalCount;
9835
+
9836
+ const size = new Blob([JSON.stringify(allEvents)]).size;
9837
+ const sizeKB = (size / 1024).toFixed(2);
9838
+ document.getElementById('storage-size').textContent = sizeKB + ' KB';
9839
+
9840
+ const typeCount = {};
9841
+ allEvents.forEach(event => {
9842
+ typeCount[event.type] = (typeCount[event.type] || 0) + 1;
9843
+ });
9844
+ const typeStr = Object.entries(typeCount)
9845
+ .map(([type, count]) => \`\${type}: \${count}\`)
9846
+ .join(', ');
9847
+ document.getElementById('type-distribution').textContent = typeStr || '无';
9848
+ }
9849
+
9850
+ // 过滤事件
9851
+ function filterEvents() {
9852
+ const typeFilter = document.getElementById('filter-type').value;
9853
+ const searchText = document.getElementById('search-input').value.toLowerCase();
9854
+ const sortOrder = document.getElementById('sort-order').value;
9855
+
9856
+ filteredEvents = allEvents.filter(event => {
9857
+ const matchType = !typeFilter || event.type === typeFilter;
9858
+ const matchSearch = !searchText ||
9859
+ JSON.stringify(event).toLowerCase().includes(searchText);
9860
+ return matchType && matchSearch;
9861
+ });
9862
+
9863
+ filteredEvents.sort((a, b) => {
9864
+ return sortOrder === 'desc' ? b.timestamp - a.timestamp : a.timestamp - b.timestamp;
9865
+ });
9866
+
9867
+ renderEvents();
9868
+ }
9869
+
9870
+ // 渲染事件列表
9871
+ function renderEvents() {
9872
+ const container = document.getElementById('events-container');
9873
+
9874
+ if (filteredEvents.length === 0) {
9875
+ container.innerHTML = \`
9876
+ <div class="empty-state">
9877
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
9878
+ <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
9879
+ </svg>
9880
+ <p>没有找到事件数据</p>
9881
+ <p style="margin-top: 10px; font-size: 12px;">请确保已经使用Tracker SDK收集了一些事件</p>
9882
+ </div>
9883
+ \`;
9884
+ return;
9885
+ }
9886
+
9887
+ const typeLabels = {
9888
+ pageview: '页面浏览',
9889
+ click: '点击',
9890
+ exposure: '曝光',
9891
+ error: '错误',
9892
+ performance: '性能',
9893
+ custom: '自定义'
9894
+ };
9895
+
9896
+ container.innerHTML = filteredEvents.map((event) => {
9897
+ const date = new Date(event.timestamp);
9898
+ return \`
9899
+ <div class="event-item">
9900
+ <div class="event-header">
9901
+ <div>
9902
+ <span class="event-type \${event.type}">\${typeLabels[event.type] || event.type}</span>
9903
+ <span style="margin-left: 10px; font-weight: bold;">\${event.name || event.eventName || '-'}</span>
9904
+ </div>
9905
+ <div class="event-time">\${date.toLocaleString()}</div>
9906
+ </div>
9907
+ <div class="event-details">
9908
+ <div><strong>URL:</strong> \${event.url || '-'}</div>
9909
+ <div style="margin-top: 5px;"><strong>标题:</strong> \${event.title || '-'}</div>
9910
+ \${event.userId ? \`<div style="margin-top: 5px;"><strong>用户ID:</strong> \${event.userId}</div>\` : ''}
9911
+ \${event.properties ? \`
9912
+ <div style="margin-top: 10px;">
9913
+ <strong>属性:</strong>
9914
+ <div class="json-viewer">\${JSON.stringify(event.properties, null, 2)}</div>
9915
+ </div>
9916
+ \` : ''}
9917
+ \${event.errorType ? \`<div style="margin-top: 5px;"><strong>错误类型:</strong> \${event.errorType}</div>\` : ''}
9918
+ \${event.message ? \`<div style="margin-top: 5px;"><strong>消息:</strong> \${event.message}</div>\` : ''}
9919
+ <div style="margin-top: 10px; font-size: 11px; color: #999;">
9920
+ <details>
9921
+ <summary style="cursor: pointer;">查看完整数据</summary>
9922
+ <div class="json-viewer" style="margin-top: 10px;">\${JSON.stringify(event, null, 2)}</div>
9923
+ </details>
9924
+ </div>
9925
+ </div>
9926
+ </div>
9927
+ \`;
9928
+ }).join('');
9929
+ }
9930
+
9931
+ // 导出数据
9932
+ function exportData() {
9933
+ const dataStr = JSON.stringify(allEvents, null, 2);
9934
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
9935
+ const url = URL.createObjectURL(dataBlob);
9936
+ const link = document.createElement('a');
9937
+ link.href = url;
9938
+ link.download = \`tracker-offline-events-\${new Date().toISOString().split('T')[0]}.json\`;
9939
+ link.click();
9940
+ URL.revokeObjectURL(url);
9941
+ }
9942
+
9943
+ // 清空存储
9944
+ async function clearStorage() {
9945
+ if (!confirm('确定要清空所有离线存储的数据吗?此操作不可恢复!')) {
9946
+ return;
9947
+ }
9948
+
9949
+ try {
9950
+ const tracker = getTrackerInstance();
9951
+ if (tracker) {
9952
+ await tracker.clearOfflineStorage();
9953
+ } else {
9954
+ const request = indexedDB.open('__tracker_db__', 1);
9955
+ request.onsuccess = (event) => {
9956
+ const db = event.target.result;
9957
+ if (!db.objectStoreNames.contains('__tracker_store__')) {
9958
+ alert('存储已清空(存储不存在)');
9959
+ loadEvents();
9960
+ return;
9961
+ }
9962
+ const transaction = db.transaction(['__tracker_store__'], 'readwrite');
9963
+ const store = transaction.objectStore('__tracker_store__');
9964
+ const index = store.index('key');
9965
+ const getRequest = index.get('__tracker_offline_events__');
9966
+ getRequest.onsuccess = () => {
9967
+ if (getRequest.result) {
9968
+ store.delete(getRequest.result.id);
9969
+ }
9970
+ };
9971
+ };
9972
+ }
9973
+ alert('存储已清空');
9974
+ loadEvents();
9975
+ } catch (error) {
9976
+ alert('清空失败: ' + error.message);
9977
+ console.error('清空存储失败:', error);
9978
+ }
9979
+ }
9980
+
9981
+ // 打开开发者工具
9982
+ function openDevTools() {
9983
+ alert('请在浏览器中按F12打开开发者工具,然后:\\n\\n1. 打开 Application 标签\\n2. 选择 IndexedDB\\n3. 找到 TrackerStorage 数据库\\n4. 查看 keyValueStore 中的数据');
9984
+ }
9985
+
9986
+ // 页面加载时自动加载数据
9987
+ window.addEventListener('load', () => {
9988
+ setTimeout(loadEvents, 500);
9989
+ });
9990
+ <\/script>
9991
+ </body>
9992
+ </html>
9993
+ `;
9994
+ class PanelManager {
9995
+ /**
9996
+ * 设置Tracker实例
9997
+ */
9998
+ setTrackerInstance(instance) {
9999
+ if (typeof window !== "undefined") {
10000
+ window.__trackerInstance = instance;
10001
+ }
10002
+ }
10003
+ /**
10004
+ * 打开性能监控面板
10005
+ */
10006
+ openPerformancePanel() {
10007
+ if (typeof window === "undefined") {
10008
+ console.warn("PanelManager: Cannot open panel in non-browser environment");
10009
+ return;
10010
+ }
10011
+ try {
10012
+ const win = window.open("about:blank", "_blank", "width=1400,height=900,resizable=yes,scrollbars=yes");
10013
+ if (!win) {
10014
+ alert("无法打开性能监控面板,请检查浏览器弹窗设置");
10015
+ return;
10016
+ }
10017
+ win.document.write(PERFORMANCE_PANEL_HTML);
10018
+ win.document.close();
10019
+ win.document.title = "性能监控面板 - Tracker SDK";
10020
+ } catch (error) {
10021
+ console.error("PanelManager: Failed to open performance panel:", error);
10022
+ alert("打开性能监控面板失败: " + (error instanceof Error ? error.message : String(error)));
10023
+ }
10024
+ }
10025
+ /**
10026
+ * 打开离线存储查看器
10027
+ */
10028
+ openOfflineStorageViewer() {
10029
+ if (typeof window === "undefined") {
10030
+ console.warn("PanelManager: Cannot open panel in non-browser environment");
10031
+ return;
10032
+ }
10033
+ try {
10034
+ const win = window.open("about:blank", "_blank", "width=1400,height=900,resizable=yes,scrollbars=yes");
10035
+ if (!win) {
10036
+ alert("无法打开离线存储查看器,请检查浏览器弹窗设置");
10037
+ return;
10038
+ }
10039
+ win.document.write(OFFLINE_STORAGE_VIEWER_HTML);
10040
+ win.document.close();
10041
+ win.document.title = "离线存储数据查看器 - Tracker SDK";
10042
+ } catch (error) {
10043
+ console.error("PanelManager: Failed to open offline storage viewer:", error);
10044
+ alert("打开离线存储查看器失败: " + (error instanceof Error ? error.message : String(error)));
10045
+ }
10046
+ }
10047
+ /**
10048
+ * 获取性能监控面板HTML
10049
+ */
10050
+ getPerformancePanelHTML() {
10051
+ return PERFORMANCE_PANEL_HTML;
10052
+ }
10053
+ /**
10054
+ * 获取离线存储查看器HTML
10055
+ */
10056
+ getOfflineStorageViewerHTML() {
10057
+ return OFFLINE_STORAGE_VIEWER_HTML;
10058
+ }
10059
+ }
10060
+ class Tracker {
10061
+ constructor(config) {
10062
+ __publicField(this, "config");
10063
+ __publicField(this, "autoTracker", null);
10064
+ __publicField(this, "errorTracker", null);
10065
+ __publicField(this, "performanceTracker", null);
10066
+ __publicField(this, "customTracker");
10067
+ __publicField(this, "dataProcessor");
10068
+ __publicField(this, "batchSender");
10069
+ __publicField(this, "pluginManager");
10070
+ __publicField(this, "behaviorAnalysis");
10071
+ __publicField(this, "sessionReplay", null);
10072
+ __publicField(this, "performanceMonitor");
10073
+ __publicField(this, "panelManager");
10074
+ __publicField(this, "userInfo", null);
10075
+ __publicField(this, "deviceId");
10076
+ __publicField(this, "sessionId");
10077
+ if (!config.appId) {
10078
+ throw new Error("Tracker: appId is required");
10079
+ }
10080
+ this.config = new ConfigManager(config);
10081
+ this.deviceId = config.deviceId || getDeviceId();
10082
+ this.sessionId = getSessionId();
10083
+ this.customTracker = new CustomTracker();
10084
+ this.dataProcessor = new DataProcessor();
10085
+ const serverUrl = config.serverUrl || "about:blank";
10086
+ this.batchSender = new BatchSender(serverUrl, {
10087
+ batchSize: this.config.get("batchSize"),
10088
+ batchWait: this.config.get("batchWait"),
10089
+ enableOfflineStorage: this.config.get("enableOfflineStorage") ?? true,
10090
+ // 未设置serverUrl时强制启用离线存储
10091
+ maxOfflineStorageSize: this.config.get("maxOfflineStorageSize")
10092
+ });
10093
+ if (!config.serverUrl) {
10094
+ this.batchSender.setServerUrl("");
10095
+ }
10096
+ this.pluginManager = new PluginManager();
10097
+ this.behaviorAnalysis = new BehaviorAnalysis();
10098
+ this.performanceMonitor = new PerformanceMonitor();
10099
+ this.panelManager = new PanelManager();
10100
+ this.pluginManager.setTrackerInstance({
10101
+ send: this.send.bind(this),
10102
+ getConfig: () => this.config.getAll(),
10103
+ setUser: this.setUser.bind(this),
10104
+ getUser: () => this.userInfo
10105
+ });
10106
+ this.panelManager.setTrackerInstance(this);
10107
+ this.init();
10108
+ if (typeof window !== "undefined") {
10109
+ window.__trackerEventBus = {
10110
+ emit: (event, data) => {
10111
+ eventBus.emit(event, data);
10112
+ }
10113
+ };
10114
+ }
10115
+ }
10116
+ /**
10117
+ * 初始化
10118
+ */
10119
+ init() {
10120
+ if (this.config.get("enableAutoTrack")) {
10121
+ this.autoTracker = new AutoTracker();
10122
+ this.autoTracker.init({
10123
+ enableVueTracking: this.config.get("enableVueTracking") ?? false,
10124
+ enableNaiveUITracking: this.config.get("enableNaiveUITracking") ?? false,
10125
+ extractComponentProps: this.config.get("extractComponentProps") ?? true,
10126
+ extractComponentValue: this.config.get("extractComponentValue") ?? true
10127
+ });
10128
+ this.setupAutoTrackListeners();
10129
+ }
10130
+ if (this.config.get("enableErrorTrack")) {
10131
+ this.errorTracker = new ErrorTracker();
10132
+ this.errorTracker.init();
10133
+ }
10134
+ if (this.config.get("enablePerformanceTrack")) {
10135
+ this.performanceTracker = new PerformanceTracker();
10136
+ this.performanceTracker.init();
10137
+ }
10138
+ if (this.config.get("enableSessionReplay")) {
10139
+ this.sessionReplay = new SessionReplay();
10140
+ this.sessionReplay.start();
10141
+ }
10142
+ if (this.config.get("userId")) {
10143
+ this.setUser({ userId: this.config.get("userId") });
10144
+ }
10145
+ }
10146
+ /**
10147
+ * 设置自动埋点监听器
10148
+ */
10149
+ setupAutoTrackListeners() {
10150
+ eventBus.on("pageview", (event) => {
10151
+ this.send(event);
10152
+ });
10153
+ eventBus.on("click", (event) => {
10154
+ this.send(event);
10155
+ });
10156
+ eventBus.on("exposure", (event) => {
10157
+ this.send(event);
10158
+ });
10159
+ eventBus.on("input", (event) => {
10160
+ this.send(event);
10161
+ });
10162
+ eventBus.on("error", (event) => {
10163
+ this.send(event);
10164
+ });
10165
+ eventBus.on("performance", (event) => {
10166
+ this.send(event);
10167
+ });
10168
+ }
10169
+ /**
10170
+ * 发送事件
10171
+ */
10172
+ send(event) {
10173
+ if (typeof window !== "undefined" && isTrackerInternalPanel()) {
10174
+ return;
10175
+ }
10176
+ try {
10177
+ const enrichedEvent = this.enrichEvent(event);
10178
+ const processedEvent = this.dataProcessor.process(enrichedEvent, {
10179
+ sampleRate: this.config.get("sampleRate"),
10180
+ enableDeduplication: true,
10181
+ isCritical: event.type === "error"
10182
+ });
10183
+ if (!processedEvent) {
10184
+ return;
10185
+ }
10186
+ const beforeSend = this.config.get("beforeSend");
10187
+ if (beforeSend) {
10188
+ const result = beforeSend(processedEvent);
10189
+ if (result === false || result === null) {
10190
+ return;
10191
+ }
10192
+ if (result) {
10193
+ Object.assign(processedEvent, result);
10194
+ }
10195
+ }
10196
+ this.batchSender.add(processedEvent, {
10197
+ onBatchComplete: (events, success) => {
10198
+ const afterSend = this.config.get("afterSend");
10199
+ if (afterSend) {
10200
+ events.forEach((event2) => {
10201
+ setTimeout(() => {
10202
+ afterSend(event2, success);
10203
+ }, 0);
10204
+ });
10205
+ }
10206
+ }
10207
+ });
10208
+ if (event.type === "pageview") {
10209
+ this.behaviorAnalysis.analyzePageView(processedEvent);
10210
+ }
10211
+ } catch (error) {
10212
+ const onError = this.config.get("onError");
10213
+ if (onError) {
10214
+ onError(error);
10215
+ } else {
10216
+ console.error("Tracker: Error sending event:", error);
10217
+ }
10218
+ }
10219
+ }
10220
+ /**
10221
+ * 丰富事件数据
10222
+ */
10223
+ enrichEvent(event) {
10224
+ var _a;
10225
+ const userId = event.userId || ((_a = this.userInfo) == null ? void 0 : _a.userId) || this.config.get("userId");
10226
+ const customProperties = this.config.get("customProperties") || {};
10227
+ const eventProperties = event.properties || {};
10228
+ const mergedProperties = {
10229
+ ...customProperties,
10230
+ ...eventProperties
10231
+ };
10232
+ const finalProperties = Object.keys(mergedProperties).length > 0 ? mergedProperties : void 0;
10233
+ const enriched = {
10234
+ ...event,
10235
+ deviceId: event.deviceId || this.deviceId,
10236
+ sessionId: event.sessionId || this.sessionId
10237
+ };
10238
+ if (userId && userId !== "") {
10239
+ enriched.userId = userId;
10240
+ }
10241
+ if (finalProperties) {
10242
+ enriched.properties = finalProperties;
10243
+ }
10244
+ return enriched;
10245
+ }
10246
+ /**
10247
+ * 追踪自定义事件
10248
+ */
10249
+ track(eventName, properties) {
10250
+ const event = this.customTracker.track(eventName, properties);
10251
+ this.send(event);
10252
+ }
10253
+ /**
10254
+ * 设置用户信息
10255
+ */
10256
+ setUser(user) {
10257
+ this.userInfo = { ...this.userInfo, ...user };
10258
+ this.config.set("userId", user.userId || "");
7817
10259
  }
7818
10260
  /**
7819
10261
  * 获取用户信息
@@ -7887,6 +10329,36 @@ class Tracker {
7887
10329
  getConfig() {
7888
10330
  return this.config.getAll();
7889
10331
  }
10332
+ /**
10333
+ * 打开性能监控面板
10334
+ */
10335
+ openPerformancePanel() {
10336
+ this.panelManager.openPerformancePanel();
10337
+ }
10338
+ /**
10339
+ * 打开离线存储查看器
10340
+ */
10341
+ openOfflineStorageViewer() {
10342
+ this.panelManager.openOfflineStorageViewer();
10343
+ }
10344
+ /**
10345
+ * 获取性能监控数据
10346
+ */
10347
+ getPerformanceMetrics(startTime, endTime) {
10348
+ return this.performanceMonitor.getMetrics(startTime, endTime);
10349
+ }
10350
+ /**
10351
+ * 生成性能报告
10352
+ */
10353
+ generatePerformanceReport(options) {
10354
+ return this.performanceMonitor.generateReport(options);
10355
+ }
10356
+ /**
10357
+ * 获取性能监控器实例
10358
+ */
10359
+ getPerformanceMonitor() {
10360
+ return this.performanceMonitor;
10361
+ }
7890
10362
  /**
7891
10363
  * 获取行为分析数据
7892
10364
  */
@@ -7934,6 +10406,7 @@ class Tracker {
7934
10406
  (_b = this.errorTracker) == null ? void 0 : _b.destroy();
7935
10407
  (_c = this.performanceTracker) == null ? void 0 : _c.destroy();
7936
10408
  (_d = this.sessionReplay) == null ? void 0 : _d.destroy();
10409
+ this.performanceMonitor.destroy();
7937
10410
  this.batchSender.destroy();
7938
10411
  this.pluginManager.clear();
7939
10412
  eventBus.clear();
@@ -7947,6 +10420,7 @@ class Tracker {
7947
10420
  (_b = this.errorTracker) == null ? void 0 : _b.destroy();
7948
10421
  (_c = this.performanceTracker) == null ? void 0 : _c.destroy();
7949
10422
  (_d = this.sessionReplay) == null ? void 0 : _d.destroy();
10423
+ this.performanceMonitor.destroy();
7950
10424
  await this.batchSender.destroyAsync();
7951
10425
  this.pluginManager.clear();
7952
10426
  eventBus.clear();
@@ -8430,14 +10904,587 @@ function createNetworkInterceptorPlugin(options = {}) {
8430
10904
  };
8431
10905
  }
8432
10906
  const networkInterceptorPlugin = createNetworkInterceptorPlugin();
10907
+ function vuePlugin(app, options) {
10908
+ const { tracker, enableAutoTrack = true, enableComponentInfo = true } = options;
10909
+ if (!tracker) {
10910
+ console.error("VuePlugin: Tracker instance is required");
10911
+ return;
10912
+ }
10913
+ app.config.globalProperties.$tracker = tracker;
10914
+ app.provide("tracker", tracker);
10915
+ if (enableAutoTrack) {
10916
+ app.mixin({
10917
+ mounted() {
10918
+ }
10919
+ });
10920
+ }
10921
+ }
10922
+ function useTracker() {
10923
+ var _a, _b;
10924
+ if (typeof window === "undefined") {
10925
+ return void 0;
10926
+ }
10927
+ const vueApp = window.__VUE_APP__;
10928
+ if ((_b = (_a = vueApp == null ? void 0 : vueApp.config) == null ? void 0 : _a.globalProperties) == null ? void 0 : _b.$tracker) {
10929
+ return vueApp.config.globalProperties.$tracker;
10930
+ }
10931
+ const globalTracker = window.__TRACKER_INSTANCE__;
10932
+ if (globalTracker) {
10933
+ return globalTracker;
10934
+ }
10935
+ return void 0;
10936
+ }
10937
+ class UserActionSequence {
10938
+ constructor(sessionId, deviceId, userId, options = {}) {
10939
+ __publicField(this, "events", []);
10940
+ __publicField(this, "sessionId");
10941
+ __publicField(this, "userId");
10942
+ __publicField(this, "deviceId");
10943
+ __publicField(this, "startTime");
10944
+ __publicField(this, "maxEvents");
10945
+ __publicField(this, "sampleRate");
10946
+ __publicField(this, "enableSampling");
10947
+ __publicField(this, "performanceMode");
10948
+ __publicField(this, "performanceBudget");
10949
+ // 索引优化
10950
+ __publicField(this, "eventTypeCounts", /* @__PURE__ */ new Map());
10951
+ __publicField(this, "isSorted", true);
10952
+ __publicField(this, "lastEventTimestamp", 0);
10953
+ __publicField(this, "eventIndexes", {
10954
+ pageViews: [],
10955
+ clicks: [],
10956
+ inputs: [],
10957
+ errors: [],
10958
+ customEvents: []
10959
+ });
10960
+ // 缓存优化
10961
+ __publicField(this, "cachedProfile", null);
10962
+ __publicField(this, "cacheTimestamp", 0);
10963
+ __publicField(this, "cacheTTL", 5e3);
10964
+ // 5秒缓存
10965
+ // 异步处理队列
10966
+ __publicField(this, "eventQueue", []);
10967
+ __publicField(this, "isProcessing", false);
10968
+ __publicField(this, "maxQueueSize", 100);
10969
+ // 性能监控
10970
+ __publicField(this, "performanceMetrics", {
10971
+ addEventTimes: [],
10972
+ profileGenerationTimes: []
10973
+ });
10974
+ // 性能模式状态
10975
+ __publicField(this, "performanceModeActive", false);
10976
+ var _a, _b, _c, _d, _e, _f, _g, _h;
10977
+ this.sessionId = sessionId;
10978
+ this.deviceId = deviceId;
10979
+ this.userId = userId;
10980
+ this.startTime = Date.now();
10981
+ this.maxEvents = options.maxEvents ?? 1e4;
10982
+ this.sampleRate = options.sampleRate ?? 1;
10983
+ this.enableSampling = options.enableSampling ?? true;
10984
+ this.performanceMode = {
10985
+ mode: ((_a = options.performanceMode) == null ? void 0 : _a.mode) ?? "balanced",
10986
+ asyncProcessing: ((_b = options.performanceMode) == null ? void 0 : _b.asyncProcessing) ?? true,
10987
+ useWebWorker: ((_c = options.performanceMode) == null ? void 0 : _c.useWebWorker) ?? false,
10988
+ eventSampleRate: ((_d = options.performanceMode) == null ? void 0 : _d.eventSampleRate) ?? this.sampleRate,
10989
+ maxEvents: ((_e = options.performanceMode) == null ? void 0 : _e.maxEvents) ?? this.maxEvents,
10990
+ enablePerformanceMonitoring: ((_f = options.performanceMode) == null ? void 0 : _f.enablePerformanceMonitoring) ?? true
10991
+ };
10992
+ this.performanceBudget = {
10993
+ addEventMaxTime: ((_g = options.performanceBudget) == null ? void 0 : _g.addEventMaxTime) ?? 1,
10994
+ profileGenerationMaxTime: ((_h = options.performanceBudget) == null ? void 0 : _h.profileGenerationMaxTime) ?? 16
10995
+ };
10996
+ }
10997
+ /**
10998
+ * 添加事件(异步版本,推荐使用)
10999
+ * 快速入队,不阻塞主线程
11000
+ */
11001
+ addEventAsync(event) {
11002
+ if (!this.performanceMode.asyncProcessing) {
11003
+ this.addEventInternal(event);
11004
+ return;
11005
+ }
11006
+ if (this.eventQueue.length >= this.maxQueueSize) {
11007
+ this.eventQueue.shift();
11008
+ }
11009
+ this.eventQueue.push(event);
11010
+ if (!this.isProcessing) {
11011
+ this.processQueueAsync();
11012
+ }
11013
+ }
11014
+ /**
11015
+ * 添加事件(同步版本)
11016
+ * 注意:如果事件很多,可能影响性能
11017
+ */
11018
+ addEvent(event) {
11019
+ const start = performance.now();
11020
+ try {
11021
+ if (this.shouldSkipForPerformance(event)) {
11022
+ return;
11023
+ }
11024
+ this.addEventInternal(event);
11025
+ } finally {
11026
+ const duration = performance.now() - start;
11027
+ this.recordPerformance("addEvent", duration);
11028
+ if (duration > this.performanceBudget.addEventMaxTime) {
11029
+ this.enablePerformanceMode();
11030
+ }
11031
+ }
11032
+ }
11033
+ /**
11034
+ * 内部添加事件逻辑
11035
+ */
11036
+ addEventInternal(event) {
11037
+ if (this.events.length >= this.maxEvents) {
11038
+ this.cleanupOldEvents();
11039
+ }
11040
+ if (this.enableSampling && Math.random() > this.sampleRate) {
11041
+ if (event.type !== "error" && event.type !== "custom") {
11042
+ return;
11043
+ }
11044
+ }
11045
+ const lightweightEvent = {
11046
+ type: event.type,
11047
+ name: event.name,
11048
+ timestamp: event.timestamp,
11049
+ url: event.url,
11050
+ title: event.title,
11051
+ sessionId: this.sessionId,
11052
+ deviceId: this.deviceId,
11053
+ userId: this.userId || event.userId,
11054
+ properties: this.sanitizeProperties(event.properties),
11055
+ // 如果是自定义事件,需要保留 eventName
11056
+ ...event.type === "custom" && "eventName" in event ? { eventName: event.eventName } : {}
11057
+ };
11058
+ const index = this.events.length;
11059
+ this.events.push(lightweightEvent);
11060
+ this.updateIndexes(event.type, index);
11061
+ this.eventTypeCounts.set(
11062
+ event.type,
11063
+ (this.eventTypeCounts.get(event.type) || 0) + 1
11064
+ );
11065
+ if (event.timestamp < this.lastEventTimestamp) {
11066
+ this.isSorted = false;
11067
+ }
11068
+ this.lastEventTimestamp = event.timestamp;
11069
+ this.cachedProfile = null;
11070
+ }
11071
+ /**
11072
+ * 异步处理队列
11073
+ */
11074
+ processQueueAsync() {
11075
+ if (this.eventQueue.length === 0) {
11076
+ this.isProcessing = false;
11077
+ return;
11078
+ }
11079
+ this.isProcessing = true;
11080
+ if (typeof window !== "undefined" && "requestIdleCallback" in window) {
11081
+ requestIdleCallback(
11082
+ () => {
11083
+ this.processBatch();
11084
+ this.processQueueAsync();
11085
+ },
11086
+ { timeout: 100 }
11087
+ );
11088
+ } else {
11089
+ setTimeout(() => {
11090
+ this.processBatch();
11091
+ this.processQueueAsync();
11092
+ }, 0);
11093
+ }
11094
+ }
11095
+ /**
11096
+ * 处理一批事件
11097
+ */
11098
+ processBatch() {
11099
+ const batchSize = 10;
11100
+ const batch = this.eventQueue.splice(0, batchSize);
11101
+ batch.forEach((event) => {
11102
+ this.addEventInternal(event);
11103
+ });
11104
+ }
11105
+ /**
11106
+ * 判断是否应该跳过事件(性能模式)
11107
+ */
11108
+ shouldSkipForPerformance(event) {
11109
+ if (!this.performanceModeActive) {
11110
+ return false;
11111
+ }
11112
+ if (event.type === "error" || event.type === "custom") {
11113
+ return false;
11114
+ }
11115
+ return Math.random() > 0.5;
11116
+ }
11117
+ /**
11118
+ * 启用性能模式
11119
+ */
11120
+ enablePerformanceMode() {
11121
+ if (!this.performanceModeActive) {
11122
+ console.warn("UserActionSequence: Entering performance mode due to slow operations");
11123
+ this.performanceModeActive = true;
11124
+ this.sampleRate = Math.max(0.1, this.sampleRate * 0.5);
11125
+ this.maxEvents = Math.floor(this.maxEvents * 0.5);
11126
+ this.cleanupOldEvents();
11127
+ }
11128
+ }
11129
+ /**
11130
+ * 清理属性,限制大小
11131
+ */
11132
+ sanitizeProperties(properties) {
11133
+ if (!properties) return void 0;
11134
+ const sanitized = {};
11135
+ for (const [key, value] of Object.entries(properties)) {
11136
+ const valueStr = JSON.stringify(value);
11137
+ if (valueStr.length > 1e3) {
11138
+ sanitized[key] = valueStr.substring(0, 1e3) + "...";
11139
+ } else {
11140
+ sanitized[key] = value;
11141
+ }
11142
+ }
11143
+ return Object.keys(sanitized).length > 0 ? sanitized : void 0;
11144
+ }
11145
+ /**
11146
+ * 更新索引
11147
+ */
11148
+ updateIndexes(eventType, index) {
11149
+ switch (eventType) {
11150
+ case "pageview":
11151
+ this.eventIndexes.pageViews.push(index);
11152
+ break;
11153
+ case "click":
11154
+ this.eventIndexes.clicks.push(index);
11155
+ break;
11156
+ case "input":
11157
+ this.eventIndexes.inputs.push(index);
11158
+ break;
11159
+ case "error":
11160
+ this.eventIndexes.errors.push(index);
11161
+ break;
11162
+ case "custom":
11163
+ this.eventIndexes.customEvents.push(index);
11164
+ break;
11165
+ }
11166
+ }
11167
+ /**
11168
+ * 清理旧事件
11169
+ */
11170
+ cleanupOldEvents() {
11171
+ const keepCount = Math.floor(this.maxEvents / 2);
11172
+ const removedCount = this.events.length - keepCount;
11173
+ this.events = this.events.slice(-keepCount);
11174
+ this.rebuildIndexes();
11175
+ this.recalculateCounts();
11176
+ if (this.performanceMode.enablePerformanceMonitoring) {
11177
+ console.warn(
11178
+ `UserActionSequence: Cleaned up ${removedCount} old events`
11179
+ );
11180
+ }
11181
+ }
11182
+ /**
11183
+ * 重建索引
11184
+ */
11185
+ rebuildIndexes() {
11186
+ this.eventIndexes = {
11187
+ pageViews: [],
11188
+ clicks: [],
11189
+ inputs: [],
11190
+ errors: [],
11191
+ customEvents: []
11192
+ };
11193
+ this.events.forEach((event, index) => {
11194
+ this.updateIndexes(event.type, index);
11195
+ });
11196
+ }
11197
+ /**
11198
+ * 重新计算统计
11199
+ */
11200
+ recalculateCounts() {
11201
+ this.eventTypeCounts.clear();
11202
+ this.events.forEach((event) => {
11203
+ this.eventTypeCounts.set(
11204
+ event.type,
11205
+ (this.eventTypeCounts.get(event.type) || 0) + 1
11206
+ );
11207
+ });
11208
+ }
11209
+ /**
11210
+ * 获取事件序列(已排序)
11211
+ */
11212
+ getSequence() {
11213
+ if (!this.isSorted) {
11214
+ this.events.sort((a, b) => a.timestamp - b.timestamp);
11215
+ this.isSorted = true;
11216
+ this.rebuildIndexes();
11217
+ }
11218
+ return this.events;
11219
+ }
11220
+ /**
11221
+ * 获取会话时长
11222
+ */
11223
+ getDuration() {
11224
+ if (this.events.length === 0) return 0;
11225
+ const sequence = this.getSequence();
11226
+ const lastEvent = sequence[sequence.length - 1];
11227
+ return lastEvent.timestamp - this.startTime;
11228
+ }
11229
+ /**
11230
+ * 使用索引快速获取特定类型的事件
11231
+ */
11232
+ getEventsByType(type) {
11233
+ const indexes = this.getIndexesByType(type);
11234
+ const sequence = this.getSequence();
11235
+ return indexes.filter((index) => index >= 0 && index < sequence.length).map((index) => sequence[index]).filter(Boolean);
11236
+ }
11237
+ /**
11238
+ * 根据类型获取索引
11239
+ */
11240
+ getIndexesByType(type) {
11241
+ switch (type) {
11242
+ case "pageview":
11243
+ return this.eventIndexes.pageViews;
11244
+ case "click":
11245
+ return this.eventIndexes.clicks;
11246
+ case "input":
11247
+ return this.eventIndexes.inputs;
11248
+ case "error":
11249
+ return this.eventIndexes.errors;
11250
+ case "custom":
11251
+ return this.eventIndexes.customEvents;
11252
+ default:
11253
+ return [];
11254
+ }
11255
+ }
11256
+ /**
11257
+ * 生成操作画像(同步版本)
11258
+ */
11259
+ generateProfile() {
11260
+ const now = Date.now();
11261
+ if (this.cachedProfile && now - this.cacheTimestamp < this.cacheTTL) {
11262
+ return this.cachedProfile;
11263
+ }
11264
+ const sequence = this.getSequence();
11265
+ const filterValidIndexes = (indexes) => {
11266
+ return indexes.filter((index) => index >= 0 && index < sequence.length).map((index) => sequence[index]).filter(Boolean);
11267
+ };
11268
+ const profile = {
11269
+ sessionId: this.sessionId,
11270
+ userId: this.userId,
11271
+ deviceId: this.deviceId,
11272
+ startTime: this.startTime,
11273
+ duration: this.getDuration(),
11274
+ totalEvents: sequence.length,
11275
+ eventTypes: Object.fromEntries(this.eventTypeCounts),
11276
+ pageViews: filterValidIndexes(this.eventIndexes.pageViews),
11277
+ clicks: filterValidIndexes(this.eventIndexes.clicks),
11278
+ inputs: filterValidIndexes(this.eventIndexes.inputs),
11279
+ errors: filterValidIndexes(this.eventIndexes.errors),
11280
+ customEvents: filterValidIndexes(this.eventIndexes.customEvents),
11281
+ userJourney: this.buildUserJourneyOptimized(sequence),
11282
+ behaviorMetrics: this.calculateBehaviorMetricsOptimized(sequence)
11283
+ };
11284
+ this.cachedProfile = profile;
11285
+ this.cacheTimestamp = now;
11286
+ return profile;
11287
+ }
11288
+ /**
11289
+ * 生成操作画像(异步版本)
11290
+ */
11291
+ generateProfileAsync() {
11292
+ return new Promise((resolve) => {
11293
+ if (typeof window !== "undefined" && "requestIdleCallback" in window) {
11294
+ requestIdleCallback(
11295
+ () => {
11296
+ const start = performance.now();
11297
+ const profile = this.generateProfile();
11298
+ const duration = performance.now() - start;
11299
+ this.recordPerformance("profileGeneration", duration);
11300
+ resolve(profile);
11301
+ },
11302
+ { timeout: 1e3 }
11303
+ );
11304
+ } else {
11305
+ setTimeout(() => {
11306
+ const start = performance.now();
11307
+ const profile = this.generateProfile();
11308
+ const duration = performance.now() - start;
11309
+ this.recordPerformance("profileGeneration", duration);
11310
+ resolve(profile);
11311
+ }, 0);
11312
+ }
11313
+ });
11314
+ }
11315
+ /**
11316
+ * 生成轻量级画像(不包含完整事件数组)
11317
+ */
11318
+ generateLightweightProfile() {
11319
+ const sequence = this.getSequence();
11320
+ return {
11321
+ sessionId: this.sessionId,
11322
+ userId: this.userId,
11323
+ deviceId: this.deviceId,
11324
+ startTime: this.startTime,
11325
+ duration: this.getDuration(),
11326
+ totalEvents: sequence.length,
11327
+ eventTypes: Object.fromEntries(this.eventTypeCounts),
11328
+ behaviorMetrics: this.calculateBehaviorMetricsOptimized(sequence)
11329
+ };
11330
+ }
11331
+ /**
11332
+ * 优化的用户旅程构建
11333
+ */
11334
+ buildUserJourneyOptimized(events) {
11335
+ const journey = [];
11336
+ let prevTimestamp = this.startTime;
11337
+ for (let i = 0; i < events.length; i++) {
11338
+ const event = events[i];
11339
+ journey.push({
11340
+ step: i + 1,
11341
+ timestamp: event.timestamp,
11342
+ type: event.type,
11343
+ url: event.url,
11344
+ title: event.title,
11345
+ action: this.extractAction(event),
11346
+ duration: event.timestamp - prevTimestamp
11347
+ });
11348
+ prevTimestamp = event.timestamp;
11349
+ }
11350
+ return journey;
11351
+ }
11352
+ /**
11353
+ * 提取操作描述
11354
+ */
11355
+ extractAction(event) {
11356
+ var _a, _b;
11357
+ switch (event.type) {
11358
+ case "pageview":
11359
+ return `访问页面: ${event.title || event.url}`;
11360
+ case "click":
11361
+ const clickEvent = event;
11362
+ return `点击: ${((_a = clickEvent.elementInfo) == null ? void 0 : _a.text) || clickEvent.elementPath || "未知元素"}`;
11363
+ case "input":
11364
+ const inputEvent = event;
11365
+ return `输入: ${((_b = inputEvent.elementInfo) == null ? void 0 : _b.name) || inputEvent.elementPath || "未知字段"}`;
11366
+ case "error":
11367
+ const errorEvent = event;
11368
+ return `错误: ${errorEvent.message}`;
11369
+ case "custom":
11370
+ const customEvent = event;
11371
+ return `自定义事件: ${customEvent.eventName}`;
11372
+ default:
11373
+ return `${event.type} 事件`;
11374
+ }
11375
+ }
11376
+ /**
11377
+ * 优化的行为指标计算
11378
+ */
11379
+ calculateBehaviorMetricsOptimized(events) {
11380
+ const pageViewIndexes = this.eventIndexes.pageViews.filter(
11381
+ (i) => i >= 0 && i < events.length
11382
+ );
11383
+ const pageViews = pageViewIndexes.map(
11384
+ (i) => events[i]
11385
+ );
11386
+ let totalDuration = 0;
11387
+ const uniquePages = /* @__PURE__ */ new Set();
11388
+ for (const pv of pageViews) {
11389
+ if (pv.duration) {
11390
+ totalDuration += pv.duration;
11391
+ }
11392
+ uniquePages.add(pv.url);
11393
+ }
11394
+ return {
11395
+ pv: pageViewIndexes.length,
11396
+ avgPageDuration: pageViewIndexes.length > 0 ? totalDuration / pageViewIndexes.length : 0,
11397
+ clickCount: this.eventIndexes.clicks.length,
11398
+ inputCount: this.eventIndexes.inputs.length,
11399
+ errorCount: this.eventIndexes.errors.length,
11400
+ uniquePages: uniquePages.size
11401
+ };
11402
+ }
11403
+ /**
11404
+ * 记录性能指标
11405
+ */
11406
+ recordPerformance(operation, duration) {
11407
+ if (!this.performanceMode.enablePerformanceMonitoring) {
11408
+ return;
11409
+ }
11410
+ const metrics = this.performanceMetrics[`${operation}Times`];
11411
+ if (metrics) {
11412
+ metrics.push(duration);
11413
+ if (metrics.length > 100) {
11414
+ metrics.shift();
11415
+ }
11416
+ }
11417
+ }
11418
+ /**
11419
+ * 获取性能统计
11420
+ */
11421
+ getPerformanceStats() {
11422
+ const addEventTimes = this.performanceMetrics.addEventTimes;
11423
+ const profileTimes = this.performanceMetrics.profileGenerationTimes;
11424
+ return {
11425
+ avgAddEventTime: addEventTimes.length > 0 ? addEventTimes.reduce((a, b) => a + b, 0) / addEventTimes.length : 0,
11426
+ avgProfileGenerationTime: profileTimes.length > 0 ? profileTimes.reduce((a, b) => a + b, 0) / profileTimes.length : 0,
11427
+ p95AddEventTime: this.percentile(addEventTimes, 95),
11428
+ p95ProfileGenerationTime: this.percentile(profileTimes, 95)
11429
+ };
11430
+ }
11431
+ /**
11432
+ * 计算百分位数
11433
+ */
11434
+ percentile(arr, p) {
11435
+ if (arr.length === 0) return 0;
11436
+ const sorted = [...arr].sort((a, b) => a - b);
11437
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
11438
+ return sorted[Math.max(0, index)] || 0;
11439
+ }
11440
+ /**
11441
+ * 重置序列
11442
+ */
11443
+ reset() {
11444
+ this.events = [];
11445
+ this.eventQueue = [];
11446
+ this.eventTypeCounts.clear();
11447
+ this.eventIndexes = {
11448
+ pageViews: [],
11449
+ clicks: [],
11450
+ inputs: [],
11451
+ errors: [],
11452
+ customEvents: []
11453
+ };
11454
+ this.cachedProfile = null;
11455
+ this.isSorted = true;
11456
+ this.lastEventTimestamp = 0;
11457
+ this.startTime = Date.now();
11458
+ this.performanceModeActive = false;
11459
+ }
11460
+ }
8433
11461
  export {
11462
+ PerformanceMonitor,
11463
+ UserActionSequence,
8434
11464
  createNetworkInterceptorPlugin,
8435
11465
  Tracker as default,
8436
11466
  eventBus,
11467
+ extractNaiveUIComponentInfo,
11468
+ extractVueComponentInfo,
11469
+ filterSensitiveData,
11470
+ getComponentAttrs,
11471
+ getComponentName,
11472
+ getComponentProps,
11473
+ getComponentValue,
11474
+ getDeviceId,
11475
+ getNaiveUIComponentTypes,
11476
+ getSessionId,
11477
+ getVueComponentInstance,
11478
+ identifyNaiveUIComponent,
8437
11479
  indexedDBStorage,
11480
+ isNaiveUIComponent,
11481
+ isNaiveUIComponentType,
11482
+ isVueComponent,
8438
11483
  localStorage$1 as localStorage,
8439
11484
  memoryStorage,
8440
11485
  networkInterceptorPlugin,
8441
- sessionStorage$1 as sessionStorage
11486
+ sessionStorage$1 as sessionStorage,
11487
+ useTracker,
11488
+ vuePlugin
8442
11489
  };
8443
11490
  //# sourceMappingURL=index.es.js.map