sentry-miniapp 1.3.0 → 1.4.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 (46) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +88 -0
  3. package/dist/sentry-miniapp.cjs.js +142 -39
  4. package/dist/sentry-miniapp.cjs.js.map +1 -1
  5. package/dist/sentry-miniapp.esm.js +142 -39
  6. package/dist/sentry-miniapp.esm.js.map +1 -1
  7. package/dist/sentry-miniapp.umd.js +142 -39
  8. package/dist/sentry-miniapp.umd.js.map +1 -1
  9. package/dist/types/client.d.ts +4 -6
  10. package/dist/types/client.d.ts.map +1 -1
  11. package/dist/types/integrations/index.d.ts +1 -0
  12. package/dist/types/integrations/index.d.ts.map +1 -1
  13. package/dist/types/integrations/networkbreadcrumbs.d.ts +29 -0
  14. package/dist/types/integrations/networkbreadcrumbs.d.ts.map +1 -0
  15. package/dist/types/sdk.d.ts +6 -4
  16. package/dist/types/sdk.d.ts.map +1 -1
  17. package/dist/types/transports/offlineStore.d.ts +4 -1
  18. package/dist/types/transports/offlineStore.d.ts.map +1 -1
  19. package/dist/types/types.d.ts +4 -0
  20. package/dist/types/types.d.ts.map +1 -1
  21. package/dist/types/version.d.ts +1 -1
  22. package/package.json +94 -16
  23. package/src/.keep +0 -0
  24. package/src/client.ts +203 -0
  25. package/src/crossPlatform.ts +407 -0
  26. package/src/eventbuilder.ts +291 -0
  27. package/src/helpers.ts +214 -0
  28. package/src/index.ts +86 -0
  29. package/src/integrations/dedupe.ts +215 -0
  30. package/src/integrations/globalhandlers.ts +209 -0
  31. package/src/integrations/httpcontext.ts +140 -0
  32. package/src/integrations/index.ts +10 -0
  33. package/src/integrations/linkederrors.ts +107 -0
  34. package/src/integrations/networkbreadcrumbs.ts +155 -0
  35. package/src/integrations/performance.ts +622 -0
  36. package/src/integrations/rewriteframes.ts +77 -0
  37. package/src/integrations/router.ts +180 -0
  38. package/src/integrations/system.ts +135 -0
  39. package/src/integrations/trycatch.ts +233 -0
  40. package/src/polyfills.ts +242 -0
  41. package/src/sdk.ts +182 -0
  42. package/src/transports/index.ts +3 -0
  43. package/src/transports/offlineStore.ts +85 -0
  44. package/src/transports/xhr.ts +68 -0
  45. package/src/types.ts +129 -0
  46. package/src/version.ts +3 -0
@@ -0,0 +1,622 @@
1
+ import { getCurrentScope } from '@sentry/core';
2
+ import type { Integration, IntegrationFn } from '@sentry/core';
3
+ import { startSpan } from '@sentry/core';
4
+
5
+ import {
6
+ getPerformanceManager,
7
+ getSystemInfo,
8
+ sdk,
9
+ type PerformanceEntry,
10
+ type NavigationPerformanceEntry,
11
+ type RenderPerformanceEntry,
12
+ type ResourcePerformanceEntry,
13
+ type UserTimingPerformanceEntry,
14
+ type PerformanceManager,
15
+ type PerformanceObserver
16
+ } from '../crossPlatform';
17
+
18
+ /**
19
+ * Performance API 集成配置
20
+ */
21
+ export interface PerformanceIntegrationOptions {
22
+ /** 是否启用导航性能监控 */
23
+ enableNavigation?: boolean;
24
+ /** 是否启用渲染性能监控 */
25
+ enableRender?: boolean;
26
+ /** 是否启用资源加载性能监控 */
27
+ enableResource?: boolean;
28
+ /** 是否启用用户自定义性能监控 */
29
+ enableUserTiming?: boolean;
30
+ /** 性能数据采样率 (0-1) */
31
+ sampleRate?: number;
32
+ /** 性能条目缓冲区大小 */
33
+ bufferSize?: number;
34
+ /** 自动上报间隔 (毫秒) */
35
+ reportInterval?: number;
36
+ }
37
+
38
+ /** Performance API 集成 */
39
+ export class PerformanceIntegration implements Integration {
40
+ /**
41
+ * @inheritDoc
42
+ */
43
+ public static id: string = 'PerformanceAPI';
44
+
45
+ /**
46
+ * @inheritDoc
47
+ */
48
+ public name: string = PerformanceIntegration.id;
49
+
50
+ private _options: Required<PerformanceIntegrationOptions>;
51
+ private _performanceManager: PerformanceManager | null = null;
52
+ private _observers: PerformanceObserver[] = [];
53
+ private _entryBuffer: PerformanceEntry[] = [];
54
+ private _reportTimer: any = null;
55
+
56
+ constructor(options: PerformanceIntegrationOptions = {}) {
57
+ this._options = {
58
+ enableNavigation: true,
59
+ enableRender: true,
60
+ enableResource: true,
61
+ enableUserTiming: false,
62
+ sampleRate: 1.0,
63
+ bufferSize: 100,
64
+ reportInterval: 30000, // 30秒
65
+ ...options,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * @inheritDoc
71
+ */
72
+ public setupOnce(): void {
73
+ this._initializePerformanceManager();
74
+ this._setupPerformanceObservers();
75
+ this._startAutoReporting();
76
+ this._addPerformanceContext();
77
+ }
78
+
79
+ /**
80
+ * 初始化性能管理器
81
+ */
82
+ private _initializePerformanceManager(): void {
83
+ try {
84
+ this._performanceManager = getPerformanceManager();
85
+ if (!this._performanceManager) {
86
+ console.warn('[Sentry Performance] Performance API not available');
87
+ return;
88
+ }
89
+
90
+ // 设置性能监控标签
91
+ const scope = getCurrentScope();
92
+ scope.setTag('performance.api.available', true);
93
+ scope.setContext('performance', {
94
+ api_version: 'miniapp-1.0',
95
+ sample_rate: this._options.sampleRate,
96
+ buffer_size: this._options.bufferSize,
97
+ });
98
+ } catch (error) {
99
+ console.warn('[Sentry Performance] Failed to initialize performance manager:', error);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * 设置性能观察者
105
+ */
106
+ private _setupPerformanceObservers(): void {
107
+ if (!this._performanceManager) {
108
+ return;
109
+ }
110
+
111
+ try {
112
+ const entryTypes: string[] = [];
113
+
114
+ if (this._options.enableNavigation) {
115
+ entryTypes.push('navigation');
116
+ }
117
+ if (this._options.enableRender) {
118
+ entryTypes.push('render');
119
+ }
120
+ if (this._options.enableResource) {
121
+ entryTypes.push('resource');
122
+ }
123
+
124
+ let canObserveUserTiming = this._options.enableUserTiming;
125
+
126
+ if (canObserveUserTiming) {
127
+ try {
128
+ const systemInfo = getSystemInfo();
129
+
130
+ if (systemInfo && systemInfo.platform === 'devtools') {
131
+ canObserveUserTiming = false;
132
+ }
133
+
134
+ if (canObserveUserTiming) {
135
+ const globalObj: any =
136
+ typeof globalThis !== 'undefined'
137
+ ? globalThis
138
+ : typeof window !== 'undefined'
139
+ ? window
140
+ : {};
141
+
142
+ const performanceObserverCtor: any = (globalObj as any).PerformanceObserver;
143
+ const supportedTypes: string[] | undefined =
144
+ performanceObserverCtor && Array.isArray((performanceObserverCtor as any).supportedEntryTypes)
145
+ ? (performanceObserverCtor as any).supportedEntryTypes
146
+ : undefined;
147
+
148
+ if (!supportedTypes || !supportedTypes.includes('measure') || !supportedTypes.includes('mark')) {
149
+ canObserveUserTiming = false;
150
+ }
151
+ }
152
+ } catch {
153
+ canObserveUserTiming = false;
154
+ }
155
+ }
156
+
157
+ if (canObserveUserTiming) {
158
+ entryTypes.push('measure', 'mark');
159
+ }
160
+
161
+ if (entryTypes.length === 0) {
162
+ return;
163
+ }
164
+
165
+ // 创建性能观察者
166
+ const observer = this._performanceManager.createObserver((entries) => {
167
+ this._handlePerformanceEntries(entries);
168
+ });
169
+
170
+ try {
171
+ observer.observe({ entryTypes });
172
+ } catch (e) {
173
+ // 如果失败(例如微信小程序不支持 measure/mark),尝试移除这些类型后重试
174
+ const safeTypes = entryTypes.filter(t => t !== 'measure' && t !== 'mark');
175
+
176
+ if (safeTypes.length < entryTypes.length && safeTypes.length > 0) {
177
+ // 降级重试
178
+ observer.observe({ entryTypes: safeTypes });
179
+ console.warn('[Sentry Performance] Failed to observe all types, falling back to:', safeTypes);
180
+
181
+ // 更新 entryTypes 以便日志记录正确
182
+ entryTypes.length = 0;
183
+ entryTypes.push(...safeTypes);
184
+ } else {
185
+ throw e; // 如果没有可降级的类型或仍然失败,则抛出
186
+ }
187
+ }
188
+
189
+ this._observers.push(observer);
190
+
191
+ const globalProcess = typeof globalThis !== 'undefined' ? (globalThis as any).process : undefined;
192
+ if (globalProcess && globalProcess.env && globalProcess.env.NODE_ENV !== 'production') {
193
+ console.log('[Sentry Performance] Performance observers setup for:', entryTypes);
194
+ }
195
+ } catch (error) {
196
+ console.warn('[Sentry Performance] Failed to setup performance observers:', error);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * 处理性能条目
202
+ */
203
+ private _handlePerformanceEntries(entries: PerformanceEntry[] | any): void {
204
+ if (!entries) {
205
+ return;
206
+ }
207
+
208
+ // 确保 entries 是数组格式
209
+ let entriesArray: PerformanceEntry[];
210
+ if (Array.isArray(entries)) {
211
+ entriesArray = entries;
212
+ } else if (typeof entries === 'object' && entries.getEntries) {
213
+ // 微信小程序可能传入 PerformanceObserverEntryList 对象
214
+ entriesArray = entries.getEntries();
215
+ } else if (typeof entries === 'object') {
216
+ // 如果是单个对象,转换为数组
217
+ entriesArray = [entries];
218
+ } else {
219
+ console.warn('[Sentry Performance] Invalid entries format:', typeof entries);
220
+ return;
221
+ }
222
+
223
+ if (entriesArray.length === 0) {
224
+ return;
225
+ }
226
+
227
+ // 采样控制
228
+ if (Math.random() > this._options.sampleRate) {
229
+ return;
230
+ }
231
+
232
+ entriesArray.forEach(entry => {
233
+ try {
234
+ this._processPerformanceEntry(entry);
235
+ this._addToBuffer(entry);
236
+ } catch (error) {
237
+ console.warn('[Sentry Performance] Failed to process performance entry:', error);
238
+ }
239
+ });
240
+ }
241
+
242
+ /**
243
+ * 处理单个性能条目
244
+ */
245
+ private _processPerformanceEntry(entry: PerformanceEntry): void {
246
+ switch (entry.entryType) {
247
+ case 'navigation':
248
+ this._processNavigationEntry(entry as NavigationPerformanceEntry);
249
+ break;
250
+ case 'render':
251
+ this._processRenderEntry(entry as RenderPerformanceEntry);
252
+ break;
253
+ case 'resource':
254
+ this._processResourceEntry(entry as ResourcePerformanceEntry);
255
+ break;
256
+ case 'measure':
257
+ case 'mark':
258
+ this._processUserTimingEntry(entry as UserTimingPerformanceEntry);
259
+ break;
260
+ default:
261
+ console.log('[Sentry Performance] Unknown entry type:', entry.entryType);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * 处理导航性能条目
267
+ */
268
+ private _processNavigationEntry(entry: NavigationPerformanceEntry): void {
269
+ // 添加面包屑
270
+ const scope = getCurrentScope();
271
+ scope.addBreadcrumb({
272
+ message: `页面导航: ${entry.name}`,
273
+ category: 'performance.navigation',
274
+ level: 'info',
275
+ data: {
276
+ duration: entry.duration,
277
+ appLaunchTime: entry.appLaunchTime,
278
+ pageReadyTime: entry.pageReadyTime,
279
+ },
280
+ });
281
+
282
+ startSpan({
283
+ name: `Navigation: ${entry.name}`,
284
+ op: 'navigation',
285
+ startTime: entry.startTime / 1000, // 转换为秒
286
+ }, (span) => {
287
+ span.setAttributes({
288
+ 'navigation.name': entry.name,
289
+ 'navigation.duration': entry.duration,
290
+ 'navigation.app_launch_time': entry.appLaunchTime || 0,
291
+ 'navigation.page_ready_time': entry.pageReadyTime || 0,
292
+ 'navigation.first_render_time': entry.firstRenderTime || 0,
293
+ });
294
+
295
+ span.end((entry.startTime + entry.duration) / 1000);
296
+ });
297
+ }
298
+
299
+ /**
300
+ * 处理渲染性能条目
301
+ */
302
+ private _processRenderEntry(entry: RenderPerformanceEntry): void {
303
+ startSpan({
304
+ name: `Render: ${entry.name}`,
305
+ op: 'render',
306
+ startTime: entry.startTime / 1000,
307
+ }, (span) => {
308
+ span.setAttributes({
309
+ 'render.name': entry.name,
310
+ 'render.duration': entry.duration,
311
+ 'render.start': entry.renderStart || 0,
312
+ 'render.end': entry.renderEnd || 0,
313
+ 'render.script_start': entry.scriptStart || 0,
314
+ 'render.script_end': entry.scriptEnd || 0,
315
+ });
316
+
317
+ span.end((entry.startTime + entry.duration) / 1000);
318
+ });
319
+ }
320
+
321
+ /**
322
+ * 处理资源加载性能条目
323
+ */
324
+ private _processResourceEntry(entry: ResourcePerformanceEntry): void {
325
+ startSpan({
326
+ name: `Resource: ${entry.name}`,
327
+ op: 'resource',
328
+ startTime: entry.startTime / 1000,
329
+ }, (span) => {
330
+ span.setAttributes({
331
+ 'resource.name': entry.name,
332
+ 'resource.duration': entry.duration,
333
+ 'resource.type': entry.initiatorType || 'unknown',
334
+ 'resource.transfer_size': entry.transferSize || 0,
335
+ 'resource.encoded_size': entry.encodedBodySize || 0,
336
+ 'resource.decoded_size': entry.decodedBodySize || 0,
337
+ });
338
+
339
+ // 网络时序信息
340
+ if (entry.fetchStart && entry.responseEnd) {
341
+ span.setAttributes({
342
+ 'resource.fetch_start': entry.fetchStart,
343
+ 'resource.response_end': entry.responseEnd,
344
+ 'resource.network_time': entry.responseEnd - entry.fetchStart,
345
+ });
346
+ }
347
+
348
+ span.end((entry.startTime + entry.duration) / 1000);
349
+ });
350
+ }
351
+
352
+ /**
353
+ * 处理用户自定义性能条目
354
+ */
355
+ private _processUserTimingEntry(entry: UserTimingPerformanceEntry): void {
356
+ if (entry.entryType === 'measure') {
357
+ startSpan({
358
+ name: `Measure: ${entry.name}`,
359
+ op: 'measure',
360
+ startTime: entry.startTime / 1000,
361
+ }, (span) => {
362
+ span.setAttributes({
363
+ 'measure.name': entry.name,
364
+ 'measure.duration': entry.duration,
365
+ 'measure.detail': entry.detail ? JSON.stringify(entry.detail) : undefined,
366
+ });
367
+
368
+ span.end((entry.startTime + entry.duration) / 1000);
369
+ });
370
+ } else if (entry.entryType === 'mark') {
371
+ // 标记事件作为面包屑记录
372
+ const scope = getCurrentScope();
373
+ scope.addBreadcrumb({
374
+ message: `性能标记: ${entry.name}`,
375
+ category: 'performance.mark',
376
+ level: 'info',
377
+ data: {
378
+ timestamp: entry.startTime,
379
+ detail: entry.detail,
380
+ },
381
+ });
382
+ }
383
+ }
384
+
385
+ /**
386
+ * 添加到缓冲区
387
+ */
388
+ private _addToBuffer(entry: PerformanceEntry): void {
389
+ this._entryBuffer.push(entry);
390
+
391
+ // 缓冲区溢出处理
392
+ if (this._entryBuffer.length > this._options.bufferSize) {
393
+ this._entryBuffer = this._entryBuffer.slice(-this._options.bufferSize);
394
+ }
395
+ }
396
+
397
+ /**
398
+ * 开始自动上报
399
+ */
400
+ private _startAutoReporting(): void {
401
+ if (this._options.reportInterval <= 0) {
402
+ return;
403
+ }
404
+
405
+ this._reportTimer = setInterval(() => {
406
+ this._reportBufferedEntries();
407
+ }, this._options.reportInterval);
408
+ }
409
+
410
+ /**
411
+ * 上报缓冲的性能条目
412
+ */
413
+ private _reportBufferedEntries(): void {
414
+ if (this._entryBuffer.length === 0) {
415
+ return;
416
+ }
417
+
418
+ try {
419
+ const scope = getCurrentScope();
420
+
421
+ // 计算性能统计
422
+ const stats = this._calculatePerformanceStats();
423
+
424
+ scope.setContext('performance_summary', {
425
+ total_entries: this._entryBuffer.length,
426
+ navigation_count: this._entryBuffer.filter(e => e.entryType === 'navigation').length,
427
+ render_count: this._entryBuffer.filter(e => e.entryType === 'render').length,
428
+ resource_count: this._entryBuffer.filter(e => e.entryType === 'resource').length,
429
+ measure_count: this._entryBuffer.filter(e => e.entryType === 'measure').length,
430
+ mark_count: this._entryBuffer.filter(e => e.entryType === 'mark').length,
431
+ report_time: new Date().toISOString(),
432
+ ...stats,
433
+ });
434
+
435
+ // 检查性能阈值并发送警告
436
+ this._checkPerformanceThresholds(stats);
437
+
438
+ // 使用小程序原生 API 上报性能数据
439
+ this._reportToNativeAPI();
440
+
441
+ // 清空缓冲区
442
+ this._entryBuffer = [];
443
+ } catch (error) {
444
+ console.warn('[Sentry Performance] Failed to report buffered entries:', error);
445
+ }
446
+ }
447
+
448
+ /**
449
+ * 计算性能统计数据
450
+ */
451
+ private _calculatePerformanceStats(): Record<string, any> {
452
+ const navigationEntries = this._entryBuffer.filter(e => e.entryType === 'navigation');
453
+ const renderEntries = this._entryBuffer.filter(e => e.entryType === 'render');
454
+ const resourceEntries = this._entryBuffer.filter(e => e.entryType === 'resource');
455
+
456
+ const stats: Record<string, any> = {};
457
+
458
+ // 导航性能统计
459
+ if (navigationEntries.length > 0) {
460
+ const durations = navigationEntries.map(e => e.duration);
461
+ stats['navigation_stats'] = {
462
+ avg_duration: durations.reduce((a, b) => a + b, 0) / durations.length,
463
+ max_duration: Math.max(...durations),
464
+ min_duration: Math.min(...durations),
465
+ };
466
+ }
467
+
468
+ // 渲染性能统计
469
+ if (renderEntries.length > 0) {
470
+ const durations = renderEntries.map(e => e.duration);
471
+ stats['render_stats'] = {
472
+ avg_duration: durations.reduce((a, b) => a + b, 0) / durations.length,
473
+ max_duration: Math.max(...durations),
474
+ min_duration: Math.min(...durations),
475
+ };
476
+ }
477
+
478
+ // 资源加载统计
479
+ if (resourceEntries.length > 0) {
480
+ const durations = resourceEntries.map(e => e.duration);
481
+ const sizes = resourceEntries
482
+ .map(e => (e as ResourcePerformanceEntry).transferSize || 0)
483
+ .filter(size => size > 0);
484
+
485
+ stats['resource_stats'] = {
486
+ avg_load_time: durations.reduce((a, b) => a + b, 0) / durations.length,
487
+ max_load_time: Math.max(...durations),
488
+ total_transfer_size: sizes.reduce((a, b) => a + b, 0),
489
+ avg_transfer_size: sizes.length > 0 ? sizes.reduce((a, b) => a + b, 0) / sizes.length : 0,
490
+ };
491
+ }
492
+
493
+ return stats;
494
+ }
495
+
496
+ /**
497
+ * 检查性能阈值
498
+ */
499
+ private _checkPerformanceThresholds(stats: Record<string, any>): void {
500
+ const scope = getCurrentScope();
501
+
502
+ // 检查导航性能
503
+ if (stats['navigation_stats']?.avg_duration > 3000) {
504
+ scope.addBreadcrumb({
505
+ message: '页面导航性能较慢',
506
+ category: 'performance.warning',
507
+ level: 'warning',
508
+ data: {
509
+ avg_duration: stats['navigation_stats'].avg_duration,
510
+ threshold: 3000,
511
+ },
512
+ });
513
+ }
514
+
515
+ // 检查渲染性能
516
+ if (stats['render_stats']?.avg_duration > 1000) {
517
+ scope.addBreadcrumb({
518
+ message: '页面渲染性能较慢',
519
+ category: 'performance.warning',
520
+ level: 'warning',
521
+ data: {
522
+ avg_duration: stats['render_stats'].avg_duration,
523
+ threshold: 1000,
524
+ },
525
+ });
526
+ }
527
+
528
+ // 检查资源加载
529
+ if (stats['resource_stats']?.avg_load_time > 2000) {
530
+ scope.addBreadcrumb({
531
+ message: '资源加载性能较慢',
532
+ category: 'performance.warning',
533
+ level: 'warning',
534
+ data: {
535
+ avg_load_time: stats['resource_stats'].avg_load_time,
536
+ threshold: 2000,
537
+ },
538
+ });
539
+ }
540
+ }
541
+
542
+ /**
543
+ * 使用小程序原生 API 上报性能数据
544
+ */
545
+ private _reportToNativeAPI(): void {
546
+ try {
547
+ const currentSdk = sdk();
548
+ if (currentSdk.reportPerformance && this._entryBuffer.length > 0) {
549
+ const performanceData = {
550
+ entries: this._entryBuffer.map(entry => ({
551
+ name: entry.name,
552
+ entryType: entry.entryType,
553
+ startTime: entry.startTime,
554
+ duration: entry.duration,
555
+ })),
556
+ timestamp: Date.now(),
557
+ sampleRate: this._options.sampleRate,
558
+ };
559
+
560
+ currentSdk.reportPerformance(performanceData);
561
+ }
562
+ } catch (error) {
563
+ console.warn('[Sentry Performance] Failed to report to native API:', error);
564
+ }
565
+ }
566
+
567
+ /**
568
+ * 添加性能上下文信息
569
+ */
570
+ private _addPerformanceContext(): void {
571
+ try {
572
+ const scope = getCurrentScope();
573
+ const currentSdk = sdk();
574
+
575
+ // 检查 Performance API 支持情况
576
+ const hasPerformanceAPI = !!(currentSdk.getPerformance);
577
+ const hasReportAPI = !!(currentSdk.reportPerformance);
578
+
579
+ scope.setContext('performance_support', {
580
+ has_performance_api: hasPerformanceAPI,
581
+ has_report_api: hasReportAPI,
582
+ integration_enabled: true,
583
+ options: this._options,
584
+ });
585
+
586
+ scope.setTag('performance.integration', 'enabled');
587
+ } catch (error) {
588
+ console.warn('[Sentry Performance] Failed to add performance context:', error);
589
+ }
590
+ }
591
+
592
+ /**
593
+ * 清理资源
594
+ */
595
+ public cleanup(): void {
596
+ // 断开所有观察者
597
+ this._observers.forEach(observer => {
598
+ try {
599
+ observer.disconnect();
600
+ } catch (error) {
601
+ console.warn('[Sentry Performance] Failed to disconnect observer:', error);
602
+ }
603
+ });
604
+ this._observers = [];
605
+
606
+ // 清除定时器
607
+ if (this._reportTimer) {
608
+ clearInterval(this._reportTimer);
609
+ this._reportTimer = null;
610
+ }
611
+
612
+ // 最后一次上报
613
+ this._reportBufferedEntries();
614
+ }
615
+ }
616
+
617
+ /**
618
+ * Performance API 集成工厂函数
619
+ */
620
+ export const performanceIntegration = (options?: PerformanceIntegrationOptions): IntegrationFn => {
621
+ return () => new PerformanceIntegration(options);
622
+ };
@@ -0,0 +1,77 @@
1
+ import type { Event, Integration, Exception, StackFrame } from '@sentry/core';
2
+
3
+ /**
4
+ * Normalize miniapp stack trace paths to a standard format for source map resolution.
5
+ * E.g., 'appservice/pages/index.js' -> 'app:///pages/index.js'
6
+ */
7
+ export class RewriteFrames implements Integration {
8
+ /**
9
+ * @inheritDoc
10
+ */
11
+ public static id: string = 'RewriteFrames';
12
+
13
+ /**
14
+ * @inheritDoc
15
+ */
16
+ public name: string = RewriteFrames.id;
17
+
18
+ /**
19
+ * Optional prefix to append to the normalized path. Defaults to 'app:///'
20
+ */
21
+ private readonly _prefix: string;
22
+
23
+ public constructor(options: { prefix?: string } = {}) {
24
+ this._prefix = options.prefix || 'app:///';
25
+ }
26
+
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ public setupOnce(): void {
31
+ // In Sentry v10, we usually use addGlobalEventProcessor or similar to add an event processor
32
+ // For integrations, we can just return the processor in processEvent or register it globally.
33
+ // However, the cleanest way in v10 is to use `processEvent`.
34
+ }
35
+
36
+ /**
37
+ * @inheritDoc
38
+ */
39
+ public processEvent(event: Event): Event {
40
+ if (event.exception && event.exception.values) {
41
+ event.exception.values.forEach((exception: Exception) => {
42
+ if (exception.stacktrace && exception.stacktrace.frames) {
43
+ exception.stacktrace.frames.forEach((frame: StackFrame) => {
44
+ if (frame.filename) {
45
+ frame.filename = this._normalizeFilename(frame.filename);
46
+ }
47
+ });
48
+ }
49
+ });
50
+ }
51
+ return event;
52
+ }
53
+
54
+ /**
55
+ * Normalizes a filename from various miniapp platforms
56
+ */
57
+ private _normalizeFilename(filename: string): string {
58
+ let normalized = filename;
59
+
60
+ // Remove common platform prefixes
61
+ // WeChat: appservice/, app-service/, WAService.js
62
+ // Alipay: https://appx/...
63
+ // ByteDance: tt://...
64
+ normalized = normalized
65
+ .replace(/^(appservice|app-service|WAService)\//i, '')
66
+ .replace(/^https?:\/\/[^/]+\//i, '') // Remove alipay http(s) protocol and domain
67
+ .replace(/^[a-z]+:\/\//i, '') // Remove other protocols like tt://, swan://
68
+ .replace(/^\//, ''); // Remove leading slash if any
69
+
70
+ // Prevent double prefixing if it's already an absolute path
71
+ if (normalized.startsWith(this._prefix)) {
72
+ return normalized;
73
+ }
74
+
75
+ return `${this._prefix}${normalized}`;
76
+ }
77
+ }