sentry-miniapp 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -0
- package/README.md +88 -0
- package/dist/sentry-miniapp.cjs.js +142 -39
- package/dist/sentry-miniapp.cjs.js.map +1 -1
- package/dist/sentry-miniapp.esm.js +142 -39
- package/dist/sentry-miniapp.esm.js.map +1 -1
- package/dist/sentry-miniapp.umd.js +142 -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 +1 -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/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 +4 -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,140 @@
|
|
|
1
|
+
import { getCurrentScope } from '@sentry/core';
|
|
2
|
+
import type { Event, Integration, IntegrationFn } from '@sentry/core';
|
|
3
|
+
|
|
4
|
+
import { sdk, getSystemInfo } from '../crossPlatform';
|
|
5
|
+
|
|
6
|
+
/** Add node request data to the event */
|
|
7
|
+
export class HttpContext implements Integration {
|
|
8
|
+
/**
|
|
9
|
+
* @inheritDoc
|
|
10
|
+
*/
|
|
11
|
+
public static id: string = 'HttpContext';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @inheritDoc
|
|
15
|
+
*/
|
|
16
|
+
public name: string = HttpContext.id;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @inheritDoc
|
|
20
|
+
*/
|
|
21
|
+
public setupOnce(): void {
|
|
22
|
+
// This integration adds context during event processing
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @inheritDoc
|
|
27
|
+
*/
|
|
28
|
+
public processEvent(event: Event): Event {
|
|
29
|
+
const scope = getCurrentScope();
|
|
30
|
+
|
|
31
|
+
// Add miniapp specific context
|
|
32
|
+
const context = {
|
|
33
|
+
runtime: {
|
|
34
|
+
name: 'miniapp',
|
|
35
|
+
version: this._getMiniappVersion(),
|
|
36
|
+
},
|
|
37
|
+
app: {
|
|
38
|
+
name: this._getAppName(),
|
|
39
|
+
version: this._getAppVersion(),
|
|
40
|
+
},
|
|
41
|
+
device: this._getDeviceInfo(),
|
|
42
|
+
network: this._getNetworkInfo(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
scope.setContext('runtime', context.runtime);
|
|
46
|
+
scope.setContext('app', context.app);
|
|
47
|
+
scope.setContext('device', context.device);
|
|
48
|
+
scope.setContext('network', context.network);
|
|
49
|
+
|
|
50
|
+
return event;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get miniapp version
|
|
55
|
+
*/
|
|
56
|
+
private _getMiniappVersion(): string {
|
|
57
|
+
const sys = getSystemInfo();
|
|
58
|
+
return sys?.version || sys?.SDKVersion || 'unknown';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get app name
|
|
63
|
+
*/
|
|
64
|
+
private _getAppName(): string {
|
|
65
|
+
try {
|
|
66
|
+
if (sdk().getAccountInfoSync) {
|
|
67
|
+
const accountInfo = sdk().getAccountInfoSync?.();
|
|
68
|
+
return accountInfo.miniProgram?.appId || 'unknown';
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Ignore errors
|
|
72
|
+
}
|
|
73
|
+
return 'unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get app version
|
|
78
|
+
*/
|
|
79
|
+
private _getAppVersion(): string {
|
|
80
|
+
try {
|
|
81
|
+
if (sdk().getAccountInfoSync) {
|
|
82
|
+
const accountInfo = sdk().getAccountInfoSync?.();
|
|
83
|
+
return accountInfo?.miniProgram?.version || 'unknown';
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore errors
|
|
87
|
+
}
|
|
88
|
+
return 'unknown';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get device information
|
|
93
|
+
*/
|
|
94
|
+
private _getDeviceInfo(): Record<string, any> {
|
|
95
|
+
const sys = getSystemInfo();
|
|
96
|
+
if (!sys) return {};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
brand: sys.brand,
|
|
100
|
+
model: sys.model,
|
|
101
|
+
system: sys.system,
|
|
102
|
+
platform: sys.platform,
|
|
103
|
+
screenWidth: sys.screenWidth,
|
|
104
|
+
screenHeight: sys.screenHeight,
|
|
105
|
+
windowWidth: sys.windowWidth,
|
|
106
|
+
windowHeight: sys.windowHeight,
|
|
107
|
+
pixelRatio: sys.pixelRatio,
|
|
108
|
+
language: sys.language,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get network information
|
|
114
|
+
*/
|
|
115
|
+
private _getNetworkInfo(): Record<string, any> {
|
|
116
|
+
try {
|
|
117
|
+
if ((sdk() as any).getNetworkType) {
|
|
118
|
+
(sdk() as any).getNetworkType({
|
|
119
|
+
success: (res: { networkType: string }) => {
|
|
120
|
+
const scope = getCurrentScope();
|
|
121
|
+
scope.setTag('network.type', res.networkType);
|
|
122
|
+
scope.setContext('network', {
|
|
123
|
+
type: res.networkType,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// Ignore errors
|
|
130
|
+
}
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* HttpContext integration
|
|
137
|
+
*/
|
|
138
|
+
export const httpContextIntegration: IntegrationFn = () => {
|
|
139
|
+
return new HttpContext();
|
|
140
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { GlobalHandlers } from './globalhandlers';
|
|
2
|
+
export { TryCatch } from './trycatch';
|
|
3
|
+
export { LinkedErrors } from './linkederrors';
|
|
4
|
+
export { HttpContext } from './httpcontext';
|
|
5
|
+
export { Dedupe } from './dedupe';
|
|
6
|
+
export { System } from './system';
|
|
7
|
+
export { Router } from './router';
|
|
8
|
+
export { PerformanceIntegration, performanceIntegration } from './performance';
|
|
9
|
+
export { RewriteFrames } from './rewriteframes';
|
|
10
|
+
export { NetworkBreadcrumbs } from './networkbreadcrumbs';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Event, EventHint, Exception, ExtendedError, Integration, IntegrationFn } from '@sentry/core';
|
|
2
|
+
import { exceptionFromError, getCurrentScope } from '@sentry/core';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_KEY = 'cause';
|
|
5
|
+
const DEFAULT_LIMIT = 5;
|
|
6
|
+
|
|
7
|
+
/** Adds SDK info to the event */
|
|
8
|
+
export class LinkedErrors implements Integration {
|
|
9
|
+
/**
|
|
10
|
+
* @inheritDoc
|
|
11
|
+
*/
|
|
12
|
+
public static id: string = 'LinkedErrors';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @inheritDoc
|
|
16
|
+
*/
|
|
17
|
+
public name: string = LinkedErrors.id;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @inheritDoc
|
|
21
|
+
*/
|
|
22
|
+
private readonly _key: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @inheritDoc
|
|
26
|
+
*/
|
|
27
|
+
private readonly _limit: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @inheritDoc
|
|
31
|
+
*/
|
|
32
|
+
public constructor(options: { key?: string; limit?: number } = {}) {
|
|
33
|
+
this._key = options.key || DEFAULT_KEY;
|
|
34
|
+
this._limit = options.limit || DEFAULT_LIMIT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @inheritDoc
|
|
39
|
+
*/
|
|
40
|
+
public setupOnce(): void {
|
|
41
|
+
// This integration doesn't need setup
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @inheritDoc
|
|
46
|
+
*/
|
|
47
|
+
public processEvent(event: Event, hint?: EventHint): Event {
|
|
48
|
+
const client = getCurrentScope().getClient();
|
|
49
|
+
if (!client) {
|
|
50
|
+
return event;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// const options = client.getOptions();
|
|
54
|
+
// Note: getCurrentScope() doesn't have getIntegration method, so we'll process directly
|
|
55
|
+
return this._handler(event, hint);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @inheritDoc
|
|
60
|
+
*/
|
|
61
|
+
private _handler(event: Event, hint?: EventHint): Event {
|
|
62
|
+
if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) {
|
|
63
|
+
return event;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const linkedErrors = this._walkErrorTree(hint.originalException as ExtendedError, this._key);
|
|
67
|
+
event.exception.values = [...linkedErrors, ...event.exception.values];
|
|
68
|
+
|
|
69
|
+
return event;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @inheritDoc
|
|
74
|
+
*/
|
|
75
|
+
private _walkErrorTree(error: ExtendedError, key: string, stack: Exception[] = []): Exception[] {
|
|
76
|
+
if (!isInstanceOf(error[key], Error) || stack.length + 1 >= this._limit) {
|
|
77
|
+
return stack;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const exception = exceptionFromError(() => [], error[key]);
|
|
81
|
+
return this._walkErrorTree(error[key], key, [exception, ...stack]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks whether given value's type is one of a few Error or Error-like
|
|
87
|
+
* {@link isError}.
|
|
88
|
+
*
|
|
89
|
+
* @param wat A value to be checked.
|
|
90
|
+
* @returns A boolean representing the result.
|
|
91
|
+
*/
|
|
92
|
+
function isInstanceOf(wat: any, base: any): boolean {
|
|
93
|
+
try {
|
|
94
|
+
return wat instanceof base;
|
|
95
|
+
} catch (_e) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* LinkedErrors integration
|
|
104
|
+
*/
|
|
105
|
+
export const linkedErrorsIntegration: IntegrationFn = (options?: { key?: string; limit?: number }) => {
|
|
106
|
+
return new LinkedErrors(options);
|
|
107
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { addBreadcrumb, getClient } from '@sentry/core';
|
|
2
|
+
import type { Integration } from '@sentry/core';
|
|
3
|
+
import { fill } from '../helpers';
|
|
4
|
+
import { sdk } from '../crossPlatform';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Network Breadcrumbs Integration.
|
|
8
|
+
* Monkey patches miniapp network API (e.g. wx.request, my.httpRequest)
|
|
9
|
+
* to record network breadcrumbs, including request and response body if configured.
|
|
10
|
+
*/
|
|
11
|
+
export class NetworkBreadcrumbs implements Integration {
|
|
12
|
+
/**
|
|
13
|
+
* @inheritDoc
|
|
14
|
+
*/
|
|
15
|
+
public static id: string = 'NetworkBreadcrumbs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @inheritDoc
|
|
19
|
+
*/
|
|
20
|
+
public name: string = NetworkBreadcrumbs.id;
|
|
21
|
+
|
|
22
|
+
private readonly _traceNetworkBody: boolean;
|
|
23
|
+
|
|
24
|
+
public constructor(options: { traceNetworkBody?: boolean | undefined } = {}) {
|
|
25
|
+
this._traceNetworkBody = !!options.traceNetworkBody;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @inheritDoc
|
|
30
|
+
*/
|
|
31
|
+
public setupOnce(): void {
|
|
32
|
+
const miniappSdk = sdk();
|
|
33
|
+
|
|
34
|
+
// Intercept standard request (WeChat, ByteDance, Swan, etc.)
|
|
35
|
+
if (miniappSdk && typeof miniappSdk.request === 'function') {
|
|
36
|
+
fill(miniappSdk, 'request', this._createRequestWrapper.bind(this));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Intercept Alipay request
|
|
40
|
+
if (miniappSdk && typeof miniappSdk.httpRequest === 'function') {
|
|
41
|
+
fill(miniappSdk, 'httpRequest', this._createRequestWrapper.bind(this));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wraps the miniapp request API to capture breadcrumbs
|
|
47
|
+
*/
|
|
48
|
+
private _createRequestWrapper(originalRequest: Function): Function {
|
|
49
|
+
const traceNetworkBody = this._traceNetworkBody;
|
|
50
|
+
|
|
51
|
+
return function (this: any, options: any): any {
|
|
52
|
+
if (!options || typeof options !== 'object') {
|
|
53
|
+
return originalRequest.call(this, options);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const url = options.url || '';
|
|
57
|
+
|
|
58
|
+
// Get the configured DSN/Transport URL from the current client
|
|
59
|
+
const client = getClient();
|
|
60
|
+
let dsnUrl = '';
|
|
61
|
+
if (client) {
|
|
62
|
+
const dsn = client.getOptions().dsn;
|
|
63
|
+
if (dsn) {
|
|
64
|
+
// A DSN looks like https://key@sentry.io/123
|
|
65
|
+
// We extract just the host part to filter against
|
|
66
|
+
try {
|
|
67
|
+
const dsnMatch = dsn.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?([^:/\n]+)/i);
|
|
68
|
+
if (dsnMatch && dsnMatch[1]) {
|
|
69
|
+
dsnUrl = dsnMatch[1];
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// fallback
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Ignore Sentry's own requests to prevent infinite loops
|
|
78
|
+
// Fallback to 'sentry.io' if DSN extraction fails but it's the SaaS version
|
|
79
|
+
if (typeof url === 'string') {
|
|
80
|
+
const isSentryRequest = (dsnUrl && url.indexOf(dsnUrl) !== -1) || url.indexOf('sentry.io') !== -1;
|
|
81
|
+
if (isSentryRequest) {
|
|
82
|
+
return originalRequest.call(this, options);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const method = (options.method || 'GET').toUpperCase();
|
|
87
|
+
const requestData = options.data;
|
|
88
|
+
|
|
89
|
+
const breadcrumbData: Record<string, any> = {
|
|
90
|
+
url,
|
|
91
|
+
method,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (traceNetworkBody && requestData) {
|
|
95
|
+
try {
|
|
96
|
+
breadcrumbData['request_body'] = typeof requestData === 'string'
|
|
97
|
+
? requestData
|
|
98
|
+
: JSON.stringify(requestData);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
breadcrumbData['request_body'] = '[Cannot serialize request body]';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const originalSuccess = options.success;
|
|
105
|
+
const originalFail = options.fail;
|
|
106
|
+
|
|
107
|
+
// Wrap success callback
|
|
108
|
+
options.success = function (this: any, ...args: any[]) {
|
|
109
|
+
const res = args[0] || {};
|
|
110
|
+
const statusCode = res.statusCode || res.status;
|
|
111
|
+
breadcrumbData['status_code'] = statusCode;
|
|
112
|
+
|
|
113
|
+
if (traceNetworkBody && res.data) {
|
|
114
|
+
try {
|
|
115
|
+
breadcrumbData['response_body'] = typeof res.data === 'string'
|
|
116
|
+
? res.data
|
|
117
|
+
: JSON.stringify(res.data);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
breadcrumbData['response_body'] = '[Cannot serialize response body]';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
addBreadcrumb({
|
|
124
|
+
type: 'http',
|
|
125
|
+
category: 'xhr',
|
|
126
|
+
data: breadcrumbData,
|
|
127
|
+
level: statusCode >= 400 ? 'warning' : 'info',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (typeof originalSuccess === 'function') {
|
|
131
|
+
return originalSuccess.apply(this, args);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Wrap fail callback
|
|
136
|
+
options.fail = function (this: any, ...args: any[]) {
|
|
137
|
+
const err = args[0] || {};
|
|
138
|
+
breadcrumbData['error'] = err.errMsg || err.errorMessage || 'Network request failed';
|
|
139
|
+
|
|
140
|
+
addBreadcrumb({
|
|
141
|
+
type: 'http',
|
|
142
|
+
category: 'xhr',
|
|
143
|
+
data: breadcrumbData,
|
|
144
|
+
level: 'error',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (typeof originalFail === 'function') {
|
|
148
|
+
return originalFail.apply(this, args);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return originalRequest.call(this, options);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|