sentry-miniapp 1.2.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.
- package/CHANGELOG.md +123 -0
- package/README.md +113 -0
- package/dist/sentry-miniapp.cjs.js +187 -39
- package/dist/sentry-miniapp.cjs.js.map +1 -1
- package/dist/sentry-miniapp.esm.js +187 -39
- package/dist/sentry-miniapp.esm.js.map +1 -1
- package/dist/sentry-miniapp.umd.js +187 -39
- package/dist/sentry-miniapp.umd.js.map +1 -1
- package/dist/types/client.d.ts +4 -6
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/integrations/index.d.ts +2 -0
- package/dist/types/integrations/index.d.ts.map +1 -1
- package/dist/types/integrations/networkbreadcrumbs.d.ts +29 -0
- package/dist/types/integrations/networkbreadcrumbs.d.ts.map +1 -0
- package/dist/types/integrations/rewriteframes.d.ts +35 -0
- package/dist/types/integrations/rewriteframes.d.ts.map +1 -0
- package/dist/types/sdk.d.ts +6 -4
- package/dist/types/sdk.d.ts.map +1 -1
- package/dist/types/transports/offlineStore.d.ts +4 -1
- package/dist/types/transports/offlineStore.d.ts.map +1 -1
- package/dist/types/types.d.ts +6 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +94 -16
- package/src/.keep +0 -0
- package/src/client.ts +203 -0
- package/src/crossPlatform.ts +407 -0
- package/src/eventbuilder.ts +291 -0
- package/src/helpers.ts +214 -0
- package/src/index.ts +86 -0
- package/src/integrations/dedupe.ts +215 -0
- package/src/integrations/globalhandlers.ts +209 -0
- package/src/integrations/httpcontext.ts +140 -0
- package/src/integrations/index.ts +10 -0
- package/src/integrations/linkederrors.ts +107 -0
- package/src/integrations/networkbreadcrumbs.ts +155 -0
- package/src/integrations/performance.ts +622 -0
- package/src/integrations/rewriteframes.ts +77 -0
- package/src/integrations/router.ts +180 -0
- package/src/integrations/system.ts +135 -0
- package/src/integrations/trycatch.ts +233 -0
- package/src/polyfills.ts +242 -0
- package/src/sdk.ts +182 -0
- package/src/transports/index.ts +3 -0
- package/src/transports/offlineStore.ts +85 -0
- package/src/transports/xhr.ts +68 -0
- package/src/types.ts +129 -0
- package/src/version.ts +3 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
declare const wx: any; // 微信小程序、微信小游戏
|
|
2
|
+
declare const my: any; // 支付宝小程序
|
|
3
|
+
declare const tt: any; // 字节跳动小程序
|
|
4
|
+
declare const dd: any; // 钉钉小程序
|
|
5
|
+
declare const qq: any; // QQ 小程序、QQ 小游戏
|
|
6
|
+
declare const swan: any; // 百度小程序
|
|
7
|
+
declare const ks: any; // 快手小程序
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 小程序平台 SDK 接口
|
|
11
|
+
*/
|
|
12
|
+
interface SDK {
|
|
13
|
+
request: Function;
|
|
14
|
+
httpRequest?: Function; // 针对钉钉小程序
|
|
15
|
+
getSystemInfoSync?: Function; // 已弃用,保留兼容性
|
|
16
|
+
canIUse?: Function; // 检查API是否可用
|
|
17
|
+
getSystemSetting?: Function; // 新 API
|
|
18
|
+
getAppAuthorizeSetting?: Function; // 新 API
|
|
19
|
+
getDeviceInfo?: Function; // 新 API
|
|
20
|
+
getWindowInfo?: Function; // 新 API
|
|
21
|
+
getAppBaseInfo?: Function; // 新 API
|
|
22
|
+
onError?: Function;
|
|
23
|
+
onUnhandledRejection?: Function;
|
|
24
|
+
onPageNotFound?: Function;
|
|
25
|
+
onMemoryWarning?: Function;
|
|
26
|
+
getLaunchOptionsSync?: Function;
|
|
27
|
+
getAccountInfoSync?: Function;
|
|
28
|
+
getUpdateManager?: Function;
|
|
29
|
+
showModal?: Function;
|
|
30
|
+
URLSearchParams?: Function;
|
|
31
|
+
// Performance API
|
|
32
|
+
getPerformance?: Function; // 获取性能管理器
|
|
33
|
+
reportPerformance?: Function; // 上报性能数据
|
|
34
|
+
// Storage API
|
|
35
|
+
setStorageSync?: Function;
|
|
36
|
+
getStorageSync?: Function;
|
|
37
|
+
getStorageInfoSync?: Function;
|
|
38
|
+
removeStorageSync?: Function;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 小程序平台类型
|
|
43
|
+
*/
|
|
44
|
+
export type AppName =
|
|
45
|
+
| 'wechat'
|
|
46
|
+
| 'alipay'
|
|
47
|
+
| 'bytedance'
|
|
48
|
+
| 'dingtalk'
|
|
49
|
+
| 'qq'
|
|
50
|
+
| 'swan'
|
|
51
|
+
| 'kuaishou'
|
|
52
|
+
| 'unknown';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 系统信息接口
|
|
56
|
+
*/
|
|
57
|
+
export interface SystemInfo {
|
|
58
|
+
brand: string;
|
|
59
|
+
model: string;
|
|
60
|
+
pixelRatio: number;
|
|
61
|
+
screenWidth: number;
|
|
62
|
+
screenHeight: number;
|
|
63
|
+
windowWidth: number;
|
|
64
|
+
windowHeight: number;
|
|
65
|
+
statusBarHeight: number;
|
|
66
|
+
language: string;
|
|
67
|
+
version: string;
|
|
68
|
+
system: string;
|
|
69
|
+
platform: string;
|
|
70
|
+
fontSizeSetting: number;
|
|
71
|
+
SDKVersion: string;
|
|
72
|
+
benchmarkLevel?: number;
|
|
73
|
+
albumAuthorized?: boolean;
|
|
74
|
+
cameraAuthorized?: boolean;
|
|
75
|
+
locationAuthorized?: boolean;
|
|
76
|
+
microphoneAuthorized?: boolean;
|
|
77
|
+
notificationAuthorized?: boolean;
|
|
78
|
+
bluetoothEnabled?: boolean;
|
|
79
|
+
locationEnabled?: boolean;
|
|
80
|
+
wifiEnabled?: boolean;
|
|
81
|
+
safeArea?: {
|
|
82
|
+
left: number;
|
|
83
|
+
right: number;
|
|
84
|
+
top: number;
|
|
85
|
+
bottom: number;
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 获取跨平台的 SDK
|
|
93
|
+
*/
|
|
94
|
+
const getSDK = (): SDK => {
|
|
95
|
+
let currentSdk: SDK = {
|
|
96
|
+
// tslint:disable-next-line: no-empty
|
|
97
|
+
request: () => { },
|
|
98
|
+
// tslint:disable-next-line: no-empty
|
|
99
|
+
httpRequest: () => { },
|
|
100
|
+
// tslint:disable-next-line: no-empty
|
|
101
|
+
getSystemInfoSync: () => ({}),
|
|
102
|
+
// tslint:disable-next-line: no-empty
|
|
103
|
+
URLSearchParams: () => { }
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (typeof wx === 'object' && wx !== null) {
|
|
107
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
108
|
+
currentSdk = wx;
|
|
109
|
+
} else if (typeof my === 'object' && my !== null) {
|
|
110
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
111
|
+
currentSdk = my;
|
|
112
|
+
} else if (typeof tt === 'object' && tt !== null) {
|
|
113
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
114
|
+
currentSdk = tt;
|
|
115
|
+
} else if (typeof dd === 'object' && dd !== null) {
|
|
116
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
117
|
+
currentSdk = dd;
|
|
118
|
+
} else if (typeof qq === 'object' && qq !== null) {
|
|
119
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
120
|
+
currentSdk = qq;
|
|
121
|
+
} else if (typeof swan === 'object' && swan !== null) {
|
|
122
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
123
|
+
currentSdk = swan;
|
|
124
|
+
} else if (typeof ks === 'object' && ks !== null) {
|
|
125
|
+
// tslint:disable-next-line: no-unsafe-any
|
|
126
|
+
currentSdk = ks;
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('sentry-miniapp 暂不支持此平台');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 支付宝小程序的网络请求 API 是 my.httpRequest
|
|
132
|
+
if (typeof my === 'object' && my !== null && currentSdk === my && !currentSdk.request && currentSdk.httpRequest) {
|
|
133
|
+
currentSdk.request = currentSdk.httpRequest;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 支付宝和钉钉的 Storage API 参数是对象形式,这里做一层抹平包装
|
|
137
|
+
if ((typeof my === 'object' && my !== null && currentSdk === my) ||
|
|
138
|
+
(typeof dd === 'object' && dd !== null && currentSdk === dd)) {
|
|
139
|
+
if (currentSdk.getStorageSync) {
|
|
140
|
+
const originalGet = currentSdk.getStorageSync;
|
|
141
|
+
currentSdk.getStorageSync = (key: string) => {
|
|
142
|
+
const res = originalGet.call(currentSdk, { key });
|
|
143
|
+
return res ? res.data : null;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (currentSdk.setStorageSync) {
|
|
147
|
+
const originalSet = currentSdk.setStorageSync;
|
|
148
|
+
currentSdk.setStorageSync = (key: string, data: any) => {
|
|
149
|
+
originalSet.call(currentSdk, { key, data });
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (currentSdk.removeStorageSync) {
|
|
153
|
+
const originalRemove = currentSdk.removeStorageSync;
|
|
154
|
+
currentSdk.removeStorageSync = (key: string) => {
|
|
155
|
+
originalRemove.call(currentSdk, { key });
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return currentSdk;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 获取平台名称
|
|
165
|
+
*/
|
|
166
|
+
const getAppName = (): AppName => {
|
|
167
|
+
let currentAppName: AppName = 'unknown';
|
|
168
|
+
|
|
169
|
+
if (typeof wx === 'object' && wx !== null) {
|
|
170
|
+
currentAppName = 'wechat';
|
|
171
|
+
} else if (typeof my === 'object' && my !== null) {
|
|
172
|
+
currentAppName = 'alipay';
|
|
173
|
+
} else if (typeof tt === 'object' && tt !== null) {
|
|
174
|
+
currentAppName = 'bytedance';
|
|
175
|
+
} else if (typeof dd === 'object' && dd !== null) {
|
|
176
|
+
currentAppName = 'dingtalk';
|
|
177
|
+
} else if (typeof qq === 'object' && qq !== null) {
|
|
178
|
+
currentAppName = 'qq';
|
|
179
|
+
} else if (typeof swan === 'object' && swan !== null) {
|
|
180
|
+
currentAppName = 'swan';
|
|
181
|
+
} else if (typeof ks === 'object' && ks !== null) {
|
|
182
|
+
currentAppName = 'kuaishou';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return currentAppName;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 获取系统信息
|
|
190
|
+
* 优先使用新的 API,保持向后兼容性
|
|
191
|
+
*/
|
|
192
|
+
const getSystemInfo = (): SystemInfo | null => {
|
|
193
|
+
try {
|
|
194
|
+
const currentSdk = getSDK();
|
|
195
|
+
const result: any = {};
|
|
196
|
+
let hasNewApi = false;
|
|
197
|
+
|
|
198
|
+
// 1. 基础信息
|
|
199
|
+
if (currentSdk.getAppBaseInfo) {
|
|
200
|
+
const baseInfo = currentSdk.getAppBaseInfo();
|
|
201
|
+
Object.assign(result, baseInfo);
|
|
202
|
+
hasNewApi = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 2. 窗口信息
|
|
206
|
+
if (currentSdk.getWindowInfo) {
|
|
207
|
+
const windowInfo = currentSdk.getWindowInfo();
|
|
208
|
+
Object.assign(result, windowInfo);
|
|
209
|
+
hasNewApi = true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 3. 设备信息
|
|
213
|
+
if (currentSdk.getDeviceInfo) {
|
|
214
|
+
const deviceInfo = currentSdk.getDeviceInfo();
|
|
215
|
+
Object.assign(result, deviceInfo);
|
|
216
|
+
hasNewApi = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 4. 授权设置 (需要转换类型)
|
|
220
|
+
if (currentSdk.getAppAuthorizeSetting) {
|
|
221
|
+
const authSetting = currentSdk.getAppAuthorizeSetting();
|
|
222
|
+
result.albumAuthorized = authSetting.albumAuthorized === 'authorized';
|
|
223
|
+
result.cameraAuthorized = authSetting.cameraAuthorized === 'authorized';
|
|
224
|
+
result.locationAuthorized = authSetting.locationAuthorized === 'authorized';
|
|
225
|
+
result.microphoneAuthorized = authSetting.microphoneAuthorized === 'authorized';
|
|
226
|
+
result.notificationAuthorized = authSetting.notificationAuthorized === 'authorized';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 5. 系统设置
|
|
230
|
+
if (currentSdk.getSystemSetting) {
|
|
231
|
+
const sysSetting = currentSdk.getSystemSetting();
|
|
232
|
+
Object.assign(result, sysSetting);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 如果成功获取了主要信息,则返回结果
|
|
236
|
+
if (hasNewApi) {
|
|
237
|
+
return result as SystemInfo;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 兜底使用旧的 API(已弃用但保持兼容性)
|
|
241
|
+
if (currentSdk.getSystemInfoSync) {
|
|
242
|
+
const syncInfo = currentSdk.getSystemInfoSync();
|
|
243
|
+
// 支付宝小程序等平台,版本信息可能叫 version 而不是 SDKVersion
|
|
244
|
+
if (!syncInfo.SDKVersion && syncInfo.version) {
|
|
245
|
+
syncInfo.SDKVersion = syncInfo.version;
|
|
246
|
+
}
|
|
247
|
+
return syncInfo as SystemInfo;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.warn('[Sentry] Failed to get system info:', error);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 检查是否在小程序环境中
|
|
259
|
+
*/
|
|
260
|
+
const isMiniappEnvironment = (): boolean => {
|
|
261
|
+
try {
|
|
262
|
+
getSDK();
|
|
263
|
+
return true;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// 懒加载 SDK 和 appName,避免在模块导入时就执行平台检测
|
|
270
|
+
export let _sdk: SDK | null = null;
|
|
271
|
+
let _appName: string | null = null;
|
|
272
|
+
|
|
273
|
+
export const sdk = (): SDK => {
|
|
274
|
+
if (_sdk === null) {
|
|
275
|
+
_sdk = getSDK();
|
|
276
|
+
}
|
|
277
|
+
return _sdk;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export const appName = (): string => {
|
|
281
|
+
if (_appName === null) {
|
|
282
|
+
_appName = getAppName();
|
|
283
|
+
}
|
|
284
|
+
return _appName;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 性能指标类型
|
|
289
|
+
*/
|
|
290
|
+
export interface PerformanceEntry {
|
|
291
|
+
name: string;
|
|
292
|
+
entryType: string;
|
|
293
|
+
startTime: number;
|
|
294
|
+
duration: number;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 导航性能指标
|
|
299
|
+
*/
|
|
300
|
+
export interface NavigationPerformanceEntry extends PerformanceEntry {
|
|
301
|
+
entryType: 'navigation';
|
|
302
|
+
// 小程序启动相关
|
|
303
|
+
appLaunchTime?: number;
|
|
304
|
+
pageReadyTime?: number;
|
|
305
|
+
firstRenderTime?: number;
|
|
306
|
+
// 页面导航相关
|
|
307
|
+
navigationStart?: number;
|
|
308
|
+
navigationEnd?: number;
|
|
309
|
+
loadEventStart?: number;
|
|
310
|
+
loadEventEnd?: number;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 渲染性能指标
|
|
315
|
+
*/
|
|
316
|
+
export interface RenderPerformanceEntry extends PerformanceEntry {
|
|
317
|
+
entryType: 'render';
|
|
318
|
+
// 渲染相关
|
|
319
|
+
renderStart?: number;
|
|
320
|
+
renderEnd?: number;
|
|
321
|
+
// 脚本执行
|
|
322
|
+
scriptStart?: number;
|
|
323
|
+
scriptEnd?: number;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 资源加载性能指标
|
|
328
|
+
*/
|
|
329
|
+
export interface ResourcePerformanceEntry extends PerformanceEntry {
|
|
330
|
+
entryType: 'resource';
|
|
331
|
+
// 资源类型
|
|
332
|
+
initiatorType?: string;
|
|
333
|
+
// 网络时序
|
|
334
|
+
fetchStart?: number;
|
|
335
|
+
domainLookupStart?: number;
|
|
336
|
+
domainLookupEnd?: number;
|
|
337
|
+
connectStart?: number;
|
|
338
|
+
connectEnd?: number;
|
|
339
|
+
requestStart?: number;
|
|
340
|
+
responseStart?: number;
|
|
341
|
+
responseEnd?: number;
|
|
342
|
+
// 资源大小
|
|
343
|
+
transferSize?: number;
|
|
344
|
+
encodedBodySize?: number;
|
|
345
|
+
decodedBodySize?: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 用户交互性能指标
|
|
350
|
+
*/
|
|
351
|
+
export interface UserTimingPerformanceEntry extends PerformanceEntry {
|
|
352
|
+
entryType: 'measure' | 'mark';
|
|
353
|
+
detail?: any;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Performance Observer 回调
|
|
358
|
+
*/
|
|
359
|
+
export interface PerformanceObserverCallback {
|
|
360
|
+
(entries: PerformanceEntry[]): void;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Performance API 管理器接口
|
|
365
|
+
*/
|
|
366
|
+
export interface PerformanceManager {
|
|
367
|
+
// 获取性能条目
|
|
368
|
+
getEntries(): PerformanceEntry[];
|
|
369
|
+
getEntriesByType(type: string): PerformanceEntry[];
|
|
370
|
+
getEntriesByName(name: string, type?: string): PerformanceEntry[];
|
|
371
|
+
|
|
372
|
+
// 标记和测量
|
|
373
|
+
mark(name: string): void;
|
|
374
|
+
measure(name: string, startMark?: string, endMark?: string): void;
|
|
375
|
+
|
|
376
|
+
// 清除
|
|
377
|
+
clearMarks(name?: string): void;
|
|
378
|
+
clearMeasures(name?: string): void;
|
|
379
|
+
|
|
380
|
+
// 观察者
|
|
381
|
+
createObserver(callback: PerformanceObserverCallback): PerformanceObserver;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Performance Observer 接口
|
|
386
|
+
*/
|
|
387
|
+
export interface PerformanceObserver {
|
|
388
|
+
observe(options: { entryTypes: string[] }): void;
|
|
389
|
+
disconnect(): void;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 获取性能管理器
|
|
394
|
+
*/
|
|
395
|
+
export const getPerformanceManager = (): PerformanceManager | null => {
|
|
396
|
+
try {
|
|
397
|
+
const currentSdk = sdk();
|
|
398
|
+
if (currentSdk.getPerformance && typeof currentSdk.getPerformance === 'function') {
|
|
399
|
+
return currentSdk.getPerformance();
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.warn('Failed to get performance manager:', error);
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export { getSDK, getSystemInfo, isMiniappEnvironment };
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Note: eventFromException and eventFromMessage are now handled by the client
|
|
2
|
+
import type { Event, EventHint, SeverityLevel } from '@sentry/core';
|
|
3
|
+
|
|
4
|
+
// 为小程序环境定义 ErrorEvent 接口
|
|
5
|
+
interface ErrorEvent {
|
|
6
|
+
error: Error;
|
|
7
|
+
message: string;
|
|
8
|
+
filename?: string;
|
|
9
|
+
lineno?: number;
|
|
10
|
+
colno?: number;
|
|
11
|
+
constructor: { name: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Simple implementations for compatibility
|
|
15
|
+
export function eventFromException(existingEvent: any, exception: any, _hint?: EventHint): Event {
|
|
16
|
+
return {
|
|
17
|
+
...existingEvent,
|
|
18
|
+
exception: {
|
|
19
|
+
values: [{
|
|
20
|
+
type: (exception && exception.name) || 'Error',
|
|
21
|
+
value: (exception && exception.message) || String(exception),
|
|
22
|
+
}]
|
|
23
|
+
},
|
|
24
|
+
level: 'error',
|
|
25
|
+
} as Event;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function eventFromMessage(existingEvent: any, message: string, level: any = 'info', _hint?: EventHint): Event {
|
|
29
|
+
return {
|
|
30
|
+
...existingEvent,
|
|
31
|
+
message: message,
|
|
32
|
+
level,
|
|
33
|
+
} as Event;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
import { isError, isPlainObject } from './helpers';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds and Event from a Exception
|
|
40
|
+
* @hidden
|
|
41
|
+
*/
|
|
42
|
+
export function eventFromUnknownInput(
|
|
43
|
+
stackParser: any,
|
|
44
|
+
exception: unknown,
|
|
45
|
+
hint?: EventHint,
|
|
46
|
+
): Event {
|
|
47
|
+
let event: Event;
|
|
48
|
+
|
|
49
|
+
if (isErrorEvent(exception) && (exception as ErrorEvent).error) {
|
|
50
|
+
// If it is an ErrorEvent with `error` property, extract it to get actual Error
|
|
51
|
+
const errorEvent = exception as ErrorEvent;
|
|
52
|
+
return eventFromException(stackParser, errorEvent.error, hint);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If it is a `DOMError` (which is a legacy API, but still supported in some browsers) or `DOMException`
|
|
56
|
+
if (isDOMError(exception) || isDOMException(exception)) {
|
|
57
|
+
const domException = exception as DOMException;
|
|
58
|
+
|
|
59
|
+
if ('stack' in exception) {
|
|
60
|
+
event = eventFromException(stackParser, exception as Error, hint);
|
|
61
|
+
} else {
|
|
62
|
+
const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
|
|
63
|
+
const message = domException.message ? `${name}: ${domException.message}` : name;
|
|
64
|
+
event = eventFromMessage(stackParser, message, 'error', hint);
|
|
65
|
+
event.exception = {
|
|
66
|
+
values: [
|
|
67
|
+
{
|
|
68
|
+
type: name,
|
|
69
|
+
value: domException.message,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return event;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isError(exception)) {
|
|
78
|
+
// we have a real Error object, do nothing
|
|
79
|
+
return eventFromException(stackParser, exception, hint);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isPlainObject(exception) && hint && hint.syntheticException) {
|
|
83
|
+
// If it is plain Object, serialize it manually and extract options
|
|
84
|
+
// This will allow us to group events based on top-level keys
|
|
85
|
+
// which is much better than creating new group when any key/value change
|
|
86
|
+
const message = `Non-Error exception captured with keys: ${extractExceptionKeysForMessage(exception)}`;
|
|
87
|
+
const syntheticException = hint.syntheticException;
|
|
88
|
+
event = eventFromException(stackParser, syntheticException, hint);
|
|
89
|
+
event.message = message;
|
|
90
|
+
event.exception!.values![0] = {
|
|
91
|
+
...event.exception!.values![0],
|
|
92
|
+
type: 'Object',
|
|
93
|
+
value: message,
|
|
94
|
+
};
|
|
95
|
+
event.extra = {
|
|
96
|
+
...event.extra,
|
|
97
|
+
__serialized__: normalizeToSize(exception),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return event;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If none of previous checks were valid, then it means that it's not:
|
|
104
|
+
// - an instance of DOMError
|
|
105
|
+
// - an instance of DOMException
|
|
106
|
+
// - an instance of Event
|
|
107
|
+
// - an instance of Error
|
|
108
|
+
// - a valid ErrorEvent (one with an error property)
|
|
109
|
+
// - a plain Object
|
|
110
|
+
//
|
|
111
|
+
// So bail out and capture it as a simple message:
|
|
112
|
+
const stringException = exception as string;
|
|
113
|
+
event = eventFromMessage(stackParser, stringException, undefined, hint);
|
|
114
|
+
event.exception = {
|
|
115
|
+
values: [
|
|
116
|
+
{
|
|
117
|
+
type: 'UnhandledException',
|
|
118
|
+
value: `Non-Error exception captured: ${stringException}`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return event;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @hidden
|
|
128
|
+
*/
|
|
129
|
+
export function eventFromString(
|
|
130
|
+
stackParser: any,
|
|
131
|
+
input: string,
|
|
132
|
+
level: SeverityLevel = 'info',
|
|
133
|
+
hint?: EventHint,
|
|
134
|
+
): Event {
|
|
135
|
+
const event = eventFromMessage(stackParser, input, level, hint);
|
|
136
|
+
event.level = level;
|
|
137
|
+
return event;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Checks whether given value's type is ErrorEvent
|
|
142
|
+
* {@link isErrorEvent}.
|
|
143
|
+
*
|
|
144
|
+
* @param wat A value to be checked.
|
|
145
|
+
* @returns A boolean representing the result.
|
|
146
|
+
*/
|
|
147
|
+
function isErrorEvent(wat: any): wat is ErrorEvent {
|
|
148
|
+
return wat && wat.constructor && wat.constructor.name === 'ErrorEvent';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Checks whether given value's type is DOMError
|
|
153
|
+
* {@link isDOMError}.
|
|
154
|
+
*
|
|
155
|
+
* @param wat A value to be checked.
|
|
156
|
+
* @returns A boolean representing the result.
|
|
157
|
+
*/
|
|
158
|
+
function isDOMError(wat: any): wat is Error {
|
|
159
|
+
return wat && wat.constructor && wat.constructor.name === 'DOMError';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks whether given value's type is DOMException
|
|
164
|
+
* {@link isDOMException}.
|
|
165
|
+
*
|
|
166
|
+
* @param wat A value to be checked.
|
|
167
|
+
* @returns A boolean representing the result.
|
|
168
|
+
*/
|
|
169
|
+
function isDOMException(wat: any): wat is DOMException {
|
|
170
|
+
return wat && wat.constructor && wat.constructor.name === 'DOMException';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @hidden
|
|
175
|
+
*/
|
|
176
|
+
function extractExceptionKeysForMessage(exception: Record<string, unknown>, maxLength: number = 40): string {
|
|
177
|
+
const keys = Object.keys(exception);
|
|
178
|
+
keys.sort();
|
|
179
|
+
|
|
180
|
+
if (!keys.length) {
|
|
181
|
+
return '[object has no keys]';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (keys[0] && keys[0].length >= maxLength) {
|
|
185
|
+
return keys[0];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) {
|
|
189
|
+
const serialized = keys.slice(0, includedKeys).join(', ');
|
|
190
|
+
if (serialized.length > maxLength) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (includedKeys === keys.length) {
|
|
194
|
+
return serialized;
|
|
195
|
+
}
|
|
196
|
+
return `${serialized}\u2026`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return '';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Normalize any value to a reasonable size for serialization
|
|
204
|
+
*/
|
|
205
|
+
function normalizeToSize(object: any, depth: number = 3, maxProperties: number = 1000): any {
|
|
206
|
+
const normalized = normalizeValue(object, depth, maxProperties);
|
|
207
|
+
|
|
208
|
+
// truncate the object
|
|
209
|
+
if (normalized && typeof normalized === 'object' && Object.keys(normalized).length > maxProperties) {
|
|
210
|
+
return '[Object too large]';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Normalize a value
|
|
218
|
+
*/
|
|
219
|
+
function normalizeValue(value: any, depth: number, maxProperties: number): any {
|
|
220
|
+
if (depth === 0) {
|
|
221
|
+
return '[Object]';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN(value))) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const stringified = stringifyValue(value);
|
|
229
|
+
if (!stringified.startsWith('[object ')) {
|
|
230
|
+
return stringified;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (value['__sentry_skip_normalization__']) {
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (typeof value === 'function') {
|
|
238
|
+
return `[Function: ${value.name || '<anonymous>'}]`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (typeof value === 'symbol') {
|
|
242
|
+
return `[${String(value)}]`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (typeof value !== 'object') {
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (isError(value)) {
|
|
250
|
+
return {
|
|
251
|
+
message: value.message,
|
|
252
|
+
name: value.name,
|
|
253
|
+
stack: value.stack,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
return value.map((v) => normalizeValue(v, depth - 1, maxProperties));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const normalized: Record<string, any> = {};
|
|
262
|
+
let numAdded = 0;
|
|
263
|
+
|
|
264
|
+
for (const key in value) {
|
|
265
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (numAdded >= maxProperties) {
|
|
270
|
+
normalized['[...]'] = '[Object too large]';
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const normalizedKey = typeof key === 'symbol' ? `[${String(key)}]` : key;
|
|
275
|
+
normalized[normalizedKey] = normalizeValue(value[key], depth - 1, maxProperties);
|
|
276
|
+
numAdded += 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return normalized;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Stringify a value
|
|
284
|
+
*/
|
|
285
|
+
function stringifyValue(value: any): string {
|
|
286
|
+
try {
|
|
287
|
+
return JSON.stringify(value);
|
|
288
|
+
} catch (_oO) {
|
|
289
|
+
return '[Object]';
|
|
290
|
+
}
|
|
291
|
+
}
|