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
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
|
+
};
|