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
package/src/helpers.ts ADDED
@@ -0,0 +1,214 @@
1
+ import { captureException, getCurrentScope } from '@sentry/core';
2
+ import type { WrappedFunction } from '@sentry/core';
3
+
4
+ /**
5
+ * Wrap a function to capture exceptions
6
+ */
7
+ export function wrap(
8
+ fn: WrappedFunction,
9
+ options: {
10
+ mechanism?: {
11
+ data?: Record<string, any>;
12
+ handled?: boolean;
13
+ type?: string;
14
+ };
15
+ } = {},
16
+ before?: WrappedFunction,
17
+ ): any {
18
+ // tslint:disable-next-line:strict-type-predicates
19
+ if (typeof fn !== 'function') {
20
+ return fn;
21
+ }
22
+
23
+ try {
24
+ // We don't wanna wrap it twice
25
+ if ((fn as any).__sentry__) {
26
+ return fn;
27
+ }
28
+
29
+ // If this has already been wrapped in the past, return that wrapped function
30
+ if (fn.__sentry_wrapped__) {
31
+ return fn.__sentry_wrapped__;
32
+ }
33
+ } catch (e) {
34
+ // Just accessing custom props in some environments
35
+ // can cause a "Permission denied" exception.
36
+ // Bail on wrapping and return the function as-is.
37
+ return fn;
38
+ }
39
+
40
+ const sentryWrapped: WrappedFunction = function (this: any, ...args: any[]): any {
41
+ // tslint:disable-next-line:strict-type-predicates
42
+ if (before && typeof before === 'function') {
43
+ before.apply(this, args);
44
+ }
45
+
46
+ try {
47
+ return fn.apply(this, args);
48
+ } catch (ex) {
49
+ const scope = getCurrentScope();
50
+
51
+ scope.addEventProcessor((event) => {
52
+ const processedEvent = { ...event };
53
+
54
+ if (options.mechanism) {
55
+ processedEvent.exception = processedEvent.exception || {};
56
+ (processedEvent.exception as any).mechanism = options.mechanism;
57
+ }
58
+
59
+ processedEvent.extra = {
60
+ ...processedEvent.extra,
61
+ arguments: args,
62
+ };
63
+
64
+ return processedEvent;
65
+ });
66
+
67
+ captureException(ex);
68
+ throw ex;
69
+ }
70
+ };
71
+
72
+ // Accessing some objects may throw
73
+ try {
74
+ // tslint:disable-next-line: no-for-in
75
+ for (const property in fn) {
76
+ if (Object.prototype.hasOwnProperty.call(fn, property)) {
77
+ (sentryWrapped as any)[property] = (fn as any)[property];
78
+ }
79
+ }
80
+ } catch (_oO) {
81
+ // no-empty
82
+ }
83
+
84
+ fn.prototype = fn.prototype || {};
85
+ sentryWrapped.prototype = fn.prototype;
86
+
87
+ Object.defineProperty(fn, '__sentry_wrapped__', {
88
+ enumerable: false,
89
+ value: sentryWrapped,
90
+ });
91
+
92
+ // Signal that this function has been wrapped/filled already
93
+ Object.defineProperties(sentryWrapped, {
94
+ __sentry__: {
95
+ enumerable: false,
96
+ value: true,
97
+ },
98
+ __sentry_original__: {
99
+ enumerable: false,
100
+ value: fn,
101
+ },
102
+ });
103
+
104
+ // Restore original function name
105
+ try {
106
+ const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') as PropertyDescriptor;
107
+ if (descriptor.configurable) {
108
+ Object.defineProperty(sentryWrapped, 'name', {
109
+ get(): string {
110
+ return fn.name;
111
+ },
112
+ });
113
+ }
114
+ } catch (_oO) {
115
+ // no-empty
116
+ }
117
+
118
+ return sentryWrapped;
119
+ }
120
+
121
+ /**
122
+ * Check if we should ignore the next onError event
123
+ */
124
+ let ignoreNextOnError = 0;
125
+
126
+ /**
127
+ * Check if we should ignore onError
128
+ */
129
+ export function shouldIgnoreOnError(): boolean {
130
+ return ignoreNextOnError > 0;
131
+ }
132
+
133
+ /**
134
+ * Ignore next onError
135
+ */
136
+ export function ignoreNextOnErrorCall(): void {
137
+ ignoreNextOnError += 1;
138
+ setTimeout(() => {
139
+ ignoreNextOnError -= 1;
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Safely extract function name from itself
145
+ */
146
+ export function getFunctionName(fn: any): string {
147
+ try {
148
+ return (fn && fn.name) || '<anonymous>';
149
+ } catch (e) {
150
+ return '<anonymous>';
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Fill an object with a new value, keeping a reference to the original
156
+ */
157
+ export function fill(source: { [key: string]: any }, name: string, replacementFactory: (...args: any[]) => any): void {
158
+ if (!(name in source)) {
159
+ return;
160
+ }
161
+
162
+ const original = source[name] as () => any;
163
+ const wrapped = replacementFactory(original);
164
+
165
+ if (typeof wrapped === 'function') {
166
+ try {
167
+ wrapped.prototype = wrapped.prototype || {};
168
+ wrapped.prototype.constructor = wrapped;
169
+ } catch (_Oo) {
170
+ // This can throw in some funky environments
171
+ }
172
+ }
173
+
174
+ source[name] = wrapped;
175
+ }
176
+
177
+ /**
178
+ * Check if value is an instance of Error
179
+ */
180
+ export function isError(wat: any): wat is Error {
181
+ switch (Object.prototype.toString.call(wat)) {
182
+ case '[object Error]':
183
+ case '[object Exception]':
184
+ case '[object DOMException]':
185
+ return true;
186
+ default:
187
+ return isInstanceOf(wat, Error);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Check if value is an instance of the given constructor
193
+ */
194
+ export function isInstanceOf(wat: any, base: any): boolean {
195
+ try {
196
+ return wat instanceof base;
197
+ } catch (_e) {
198
+ return false;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Check if value is a string
204
+ */
205
+ export function isString(wat: any): wat is string {
206
+ return Object.prototype.toString.call(wat) === '[object String]';
207
+ }
208
+
209
+ /**
210
+ * Check if value is a plain object
211
+ */
212
+ export function isPlainObject(wat: any): wat is Record<string, any> {
213
+ return Object.prototype.toString.call(wat) === '[object Object]';
214
+ }
package/src/index.ts ADDED
@@ -0,0 +1,86 @@
1
+ // Sentry Miniapp SDK for WeChat Mini Program
2
+ // Based on @sentry/core 9.38.0
3
+ // Development Mode: Auto-rebuild enabled
4
+
5
+ // Install polyfills for miniapp environment
6
+ import { ensurePolyfills } from './polyfills';
7
+ ensurePolyfills();
8
+
9
+ // Export types from @sentry/core (v9 moved types from @sentry/types to @sentry/core)
10
+ export type {
11
+ Breadcrumb,
12
+ BreadcrumbHint,
13
+ Event,
14
+ EventHint,
15
+ Exception,
16
+ SdkInfo,
17
+ Session,
18
+ SeverityLevel,
19
+ StackFrame,
20
+ Stacktrace,
21
+ Thread,
22
+ User,
23
+ Integration,
24
+ Options,
25
+ Client,
26
+ Scope,
27
+ Transport,
28
+ BaseTransportOptions,
29
+ } from '@sentry/core';
30
+
31
+ // Export core functions from @sentry/core
32
+ export {
33
+ addEventProcessor,
34
+ addIntegration,
35
+ captureException,
36
+ captureEvent,
37
+ captureMessage,
38
+ getClient,
39
+ getCurrentScope,
40
+ getIsolationScope,
41
+ withScope,
42
+ startSpan,
43
+ startInactiveSpan,
44
+ setContext,
45
+ setExtra,
46
+ setExtras,
47
+ setTag,
48
+ setTags,
49
+ setUser,
50
+ addBreadcrumb,
51
+ flush,
52
+ close,
53
+ lastEventId,
54
+ isEnabled,
55
+ // Session management APIs
56
+ startSession,
57
+ endSession,
58
+ captureSession,
59
+ } from '@sentry/core';
60
+
61
+ // Export SDK specific exports
62
+ export { SDK_NAME, SDK_VERSION } from './version';
63
+ export { init, showReportDialog, wrap, captureFeedback } from './sdk';
64
+ export type { MiniappOptions, SendFeedbackParams } from './types';
65
+ export { MiniappClient } from './client';
66
+ export * as Integrations from './integrations/index';
67
+ export * as Transports from './transports/index';
68
+
69
+ // Performance API exports
70
+ export {
71
+ getPerformanceManager,
72
+ type PerformanceEntry,
73
+ type NavigationPerformanceEntry,
74
+ type RenderPerformanceEntry,
75
+ type ResourcePerformanceEntry,
76
+ type UserTimingPerformanceEntry,
77
+ type PerformanceManager,
78
+ type PerformanceObserver,
79
+ } from './crossPlatform';
80
+ export { performanceIntegration, type PerformanceIntegrationOptions } from './integrations/performance';
81
+
82
+ // Export Session utility functions from @sentry/core
83
+ export { makeSession, closeSession, updateSession } from '@sentry/core';
84
+
85
+ // Export default integrations
86
+ export { defaultIntegrations, getDefaultIntegrations } from './sdk';
@@ -0,0 +1,215 @@
1
+ import type { Event, EventHint, Integration, IntegrationFn } from '@sentry/core';
2
+
3
+ /** Deduplication filter */
4
+ export class Dedupe implements Integration {
5
+ /**
6
+ * @inheritDoc
7
+ */
8
+ public static id: string = 'Dedupe';
9
+
10
+ /**
11
+ * @inheritDoc
12
+ */
13
+ public name: string = Dedupe.id;
14
+
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ private _previousEvent?: Event;
19
+
20
+ /**
21
+ * @inheritDoc
22
+ */
23
+ public setupOnce(): void {
24
+ // This integration doesn't need setup
25
+ }
26
+
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ public processEvent(currentEvent: Event, _hint?: EventHint): Event | null {
31
+ // We want to ignore any non-error type events, e.g. transactions or replays
32
+ // These should never be deduped, and also not be compared against as _previousEvent.
33
+ if (currentEvent.type) {
34
+ return currentEvent;
35
+ }
36
+
37
+ // Juuust in case something goes wrong
38
+ try {
39
+ if (this._shouldDropEvent(currentEvent, this._previousEvent)) {
40
+ return null;
41
+ }
42
+ } catch (_oO) {
43
+ return (this._previousEvent = currentEvent);
44
+ }
45
+
46
+ return (this._previousEvent = currentEvent);
47
+ }
48
+
49
+ /** JSDoc */
50
+ private _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean {
51
+ if (!previousEvent) {
52
+ return false;
53
+ }
54
+
55
+ if (this._isSameMessageEvent(currentEvent, previousEvent)) {
56
+ return true;
57
+ }
58
+
59
+ if (this._isSameExceptionEvent(currentEvent, previousEvent)) {
60
+ return true;
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /** JSDoc */
67
+ private _isSameMessageEvent(currentEvent: Event, previousEvent: Event): boolean {
68
+ const currentMessage = currentEvent.message;
69
+ const previousMessage = previousEvent.message;
70
+
71
+ // If neither event has a message property, they were both exceptions, so bail out
72
+ if (!currentMessage && !previousMessage) {
73
+ return false;
74
+ }
75
+
76
+ // If only one event has a message property, the events are not the same
77
+ if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) {
78
+ return false;
79
+ }
80
+
81
+ if (currentMessage !== previousMessage) {
82
+ return false;
83
+ }
84
+
85
+ if (!this._isSameFingerprint(currentEvent, previousEvent)) {
86
+ return false;
87
+ }
88
+
89
+ if (!this._isSameStacktrace(currentEvent, previousEvent)) {
90
+ return false;
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ /** JSDoc */
97
+ private _isSameExceptionEvent(currentEvent: Event, previousEvent: Event): boolean {
98
+ const currentException = this._getExceptionFromEvent(currentEvent);
99
+ const previousException = this._getExceptionFromEvent(previousEvent);
100
+
101
+ if (!currentException || !previousException) {
102
+ return false;
103
+ }
104
+
105
+ if (currentException.type !== previousException.type || currentException.value !== previousException.value) {
106
+ return false;
107
+ }
108
+
109
+ if (!this._isSameFingerprint(currentEvent, previousEvent)) {
110
+ return false;
111
+ }
112
+
113
+ if (!this._isSameStacktrace(currentEvent, previousEvent)) {
114
+ return false;
115
+ }
116
+
117
+ return true;
118
+ }
119
+
120
+ /** JSDoc */
121
+ private _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean {
122
+ let currentFrames = this._getFramesFromEvent(currentEvent);
123
+ let previousFrames = this._getFramesFromEvent(previousEvent);
124
+
125
+ // If neither event has a stacktrace, they are assumed to be the same
126
+ if (!currentFrames && !previousFrames) {
127
+ return true;
128
+ }
129
+
130
+ // If only one event has a stacktrace, but not the other one, they are not the same
131
+ if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) {
132
+ return false;
133
+ }
134
+
135
+ currentFrames = currentFrames as any[];
136
+ previousFrames = previousFrames as any[];
137
+
138
+ // If number of frames differ, they are not the same
139
+ if (previousFrames.length !== currentFrames.length) {
140
+ return false;
141
+ }
142
+
143
+ // Otherwise, compare the frames
144
+ for (let i = 0; i < previousFrames.length; i++) {
145
+ const frameA = previousFrames[i];
146
+ const frameB = currentFrames[i];
147
+
148
+ if (
149
+ frameA.filename !== frameB.filename ||
150
+ frameA.lineno !== frameB.lineno ||
151
+ frameA.colno !== frameB.colno ||
152
+ frameA.function !== frameB.function
153
+ ) {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ return true;
159
+ }
160
+
161
+ /** JSDoc */
162
+ private _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean {
163
+ let currentFingerprint = currentEvent.fingerprint;
164
+ let previousFingerprint = previousEvent.fingerprint;
165
+
166
+ // If neither event has a fingerprint, they are assumed to be the same
167
+ if (!currentFingerprint && !previousFingerprint) {
168
+ return true;
169
+ }
170
+
171
+ // If only one event has a fingerprint, but not the other one, they are not the same
172
+ if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) {
173
+ return false;
174
+ }
175
+
176
+ currentFingerprint = currentFingerprint as string[];
177
+ previousFingerprint = previousFingerprint as string[];
178
+
179
+ // Otherwise, compare the fingerprints
180
+ try {
181
+ return !!(currentFingerprint.join('') === previousFingerprint.join(''));
182
+ } catch (_oO) {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /** JSDoc */
188
+ private _getExceptionFromEvent(event: Event): any {
189
+ return event.exception && event.exception.values && event.exception.values[0];
190
+ }
191
+
192
+ /** JSDoc */
193
+ private _getFramesFromEvent(event: Event): any[] | undefined {
194
+ const exception = event.exception;
195
+
196
+ if (exception) {
197
+ try {
198
+ // @ts-expect-error Object could be undefined
199
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
200
+ return exception.values[0].stacktrace.frames;
201
+ } catch (_oO) {
202
+ // ignore
203
+ }
204
+ }
205
+
206
+ return undefined;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Dedupe integration
212
+ */
213
+ export const dedupeIntegration: IntegrationFn = () => {
214
+ return new Dedupe();
215
+ };
@@ -0,0 +1,209 @@
1
+ import { captureException, getCurrentScope } from '@sentry/core';
2
+ import type { Integration, IntegrationFn } from '@sentry/core';
3
+
4
+ import { sdk } from '../crossPlatform';
5
+
6
+ /** JSDoc */
7
+ interface GlobalHandlersIntegrations {
8
+ onerror: boolean;
9
+ onunhandledrejection: boolean;
10
+ onpagenotfound: boolean;
11
+ onmemorywarning: boolean;
12
+ }
13
+
14
+ /** Global handlers */
15
+ export class GlobalHandlers implements Integration {
16
+ /**
17
+ * @inheritDoc
18
+ */
19
+ public static id: string = 'GlobalHandlers';
20
+
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ public name: string = GlobalHandlers.id;
25
+
26
+ /** JSDoc */
27
+ private readonly _options: GlobalHandlersIntegrations;
28
+
29
+ /** JSDoc */
30
+ private _onErrorHandlerInstalled: boolean = false;
31
+
32
+ /** JSDoc */
33
+ private _onUnhandledRejectionHandlerInstalled: boolean = false;
34
+
35
+ /** JSDoc */
36
+ private _onPageNotFoundHandlerInstalled: boolean = false;
37
+
38
+ /** JSDoc */
39
+ private _onMemoryWarningHandlerInstalled: boolean = false;
40
+
41
+ /** JSDoc */
42
+ public constructor(options?: Partial<GlobalHandlersIntegrations>) {
43
+ this._options = {
44
+ onerror: true,
45
+ onunhandledrejection: true,
46
+ onpagenotfound: true,
47
+ onmemorywarning: true,
48
+ ...options,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @inheritDoc
54
+ */
55
+ public setupOnce(): void {
56
+ Error.stackTraceLimit = 50;
57
+
58
+ if (this._options.onerror) {
59
+ this._installGlobalOnErrorHandler();
60
+ }
61
+
62
+ if (this._options.onunhandledrejection) {
63
+ this._installGlobalOnUnhandledRejectionHandler();
64
+ }
65
+
66
+ if (this._options.onpagenotfound) {
67
+ this._installGlobalOnPageNotFoundHandler();
68
+ }
69
+
70
+ if (this._options.onmemorywarning) {
71
+ this._installGlobalOnMemoryWarningHandler();
72
+ }
73
+ }
74
+
75
+ /** JSDoc */
76
+ private _installGlobalOnErrorHandler(): void {
77
+ if (this._onErrorHandlerInstalled) {
78
+ return;
79
+ }
80
+
81
+ if (sdk().onError) {
82
+ // https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onError.html
83
+ sdk().onError?.((err: string | Error) => {
84
+ const error = typeof err === 'string' ? new Error(err) : err;
85
+ captureException(error, {
86
+ mechanism: {
87
+ type: 'onerror',
88
+ handled: false,
89
+ },
90
+ });
91
+ });
92
+ }
93
+
94
+ this._onErrorHandlerInstalled = true;
95
+ }
96
+
97
+ /** JSDoc */
98
+ private _installGlobalOnUnhandledRejectionHandler(): void {
99
+ if (this._onUnhandledRejectionHandlerInstalled) {
100
+ return;
101
+ }
102
+
103
+ if (sdk().onUnhandledRejection) {
104
+ /** JSDoc */
105
+ interface OnUnhandledRejectionRes {
106
+ reason: string | Error;
107
+ promise: Promise<any>;
108
+ }
109
+
110
+ // https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html
111
+ sdk().onUnhandledRejection?.(({ reason, promise }: OnUnhandledRejectionRes) => {
112
+ const error = typeof reason === 'string' ? new Error(reason) : reason;
113
+ (captureException as any)(error, {
114
+ mechanism: {
115
+ type: 'onunhandledrejection',
116
+ handled: false,
117
+ },
118
+ extra: {
119
+ promise,
120
+ },
121
+ });
122
+ });
123
+ }
124
+
125
+ this._onUnhandledRejectionHandlerInstalled = true;
126
+ }
127
+
128
+ /** JSDoc */
129
+ private _installGlobalOnPageNotFoundHandler(): void {
130
+ if (this._onPageNotFoundHandlerInstalled) {
131
+ return;
132
+ }
133
+
134
+ if (sdk().onPageNotFound) {
135
+ sdk().onPageNotFound?.((res: { path: string; query: Record<string, any>; isEntryPage: boolean }) => {
136
+ const scope = getCurrentScope();
137
+ const url = res.path.split('?')[0];
138
+
139
+ scope.setTag('pagenotfound', url);
140
+ scope.setContext('page_not_found', {
141
+ path: res.path,
142
+ query: res.query,
143
+ isEntryPage: res.isEntryPage,
144
+ });
145
+
146
+ (captureException as any)(new Error(`页面无法找到: ${url}`), {
147
+ level: 'warning',
148
+ mechanism: {
149
+ type: 'onpagenotfound',
150
+ handled: true,
151
+ },
152
+ });
153
+ });
154
+ }
155
+
156
+ this._onPageNotFoundHandlerInstalled = true;
157
+ }
158
+
159
+ /** JSDoc */
160
+ private _installGlobalOnMemoryWarningHandler(): void {
161
+ if (this._onMemoryWarningHandlerInstalled) {
162
+ return;
163
+ }
164
+
165
+ if (sdk().onMemoryWarning) {
166
+ sdk().onMemoryWarning?.(({ level = -1 }: { level: number }) => {
167
+ let levelMessage = '没有获取到告警级别信息';
168
+
169
+ switch (level) {
170
+ case 5:
171
+ levelMessage = 'TRIM_MEMORY_RUNNING_MODERATE';
172
+ break;
173
+ case 10:
174
+ levelMessage = 'TRIM_MEMORY_RUNNING_LOW';
175
+ break;
176
+ case 15:
177
+ levelMessage = 'TRIM_MEMORY_RUNNING_CRITICAL';
178
+ break;
179
+ default:
180
+ return;
181
+ }
182
+
183
+ const scope = getCurrentScope();
184
+ scope.setTag('memory-warning', String(level));
185
+ scope.setContext('memory_warning', {
186
+ level,
187
+ message: levelMessage,
188
+ });
189
+
190
+ (captureException as any)(new Error('内存不足告警'), {
191
+ level: 'warning',
192
+ mechanism: {
193
+ type: 'onmemorywarning',
194
+ handled: true,
195
+ },
196
+ });
197
+ });
198
+ }
199
+
200
+ this._onMemoryWarningHandlerInstalled = true;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Global handlers integration
206
+ */
207
+ export const globalHandlersIntegration: IntegrationFn = (options?: Partial<GlobalHandlersIntegrations>) => {
208
+ return new GlobalHandlers(options);
209
+ };