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.
Files changed (48) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +113 -0
  3. package/dist/sentry-miniapp.cjs.js +187 -39
  4. package/dist/sentry-miniapp.cjs.js.map +1 -1
  5. package/dist/sentry-miniapp.esm.js +187 -39
  6. package/dist/sentry-miniapp.esm.js.map +1 -1
  7. package/dist/sentry-miniapp.umd.js +187 -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 +2 -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/integrations/rewriteframes.d.ts +35 -0
  16. package/dist/types/integrations/rewriteframes.d.ts.map +1 -0
  17. package/dist/types/sdk.d.ts +6 -4
  18. package/dist/types/sdk.d.ts.map +1 -1
  19. package/dist/types/transports/offlineStore.d.ts +4 -1
  20. package/dist/types/transports/offlineStore.d.ts.map +1 -1
  21. package/dist/types/types.d.ts +6 -0
  22. package/dist/types/types.d.ts.map +1 -1
  23. package/dist/types/version.d.ts +1 -1
  24. package/package.json +94 -16
  25. package/src/.keep +0 -0
  26. package/src/client.ts +203 -0
  27. package/src/crossPlatform.ts +407 -0
  28. package/src/eventbuilder.ts +291 -0
  29. package/src/helpers.ts +214 -0
  30. package/src/index.ts +86 -0
  31. package/src/integrations/dedupe.ts +215 -0
  32. package/src/integrations/globalhandlers.ts +209 -0
  33. package/src/integrations/httpcontext.ts +140 -0
  34. package/src/integrations/index.ts +10 -0
  35. package/src/integrations/linkederrors.ts +107 -0
  36. package/src/integrations/networkbreadcrumbs.ts +155 -0
  37. package/src/integrations/performance.ts +622 -0
  38. package/src/integrations/rewriteframes.ts +77 -0
  39. package/src/integrations/router.ts +180 -0
  40. package/src/integrations/system.ts +135 -0
  41. package/src/integrations/trycatch.ts +233 -0
  42. package/src/polyfills.ts +242 -0
  43. package/src/sdk.ts +182 -0
  44. package/src/transports/index.ts +3 -0
  45. package/src/transports/offlineStore.ts +85 -0
  46. package/src/transports/xhr.ts +68 -0
  47. package/src/types.ts +129 -0
  48. 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
+ }