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
package/src/polyfills.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polyfills for miniapp environment
|
|
3
|
+
* 小程序环境的 polyfill 实现
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* URLSearchParams polyfill for miniapp environment
|
|
8
|
+
* 小程序环境的 URLSearchParams polyfill
|
|
9
|
+
*/
|
|
10
|
+
class URLSearchParamsPolyfill {
|
|
11
|
+
private params: Record<string, string> = {};
|
|
12
|
+
|
|
13
|
+
constructor(init?: string | Record<string, string> | URLSearchParamsPolyfill | string[][]) {
|
|
14
|
+
if (typeof init === 'string') {
|
|
15
|
+
this.parseString(init);
|
|
16
|
+
} else if (Array.isArray(init)) {
|
|
17
|
+
// Handle string[][] format
|
|
18
|
+
for (const pair of init) {
|
|
19
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
20
|
+
this.append(pair[0] || '', pair[1] || '');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} else if (init && typeof init === 'object') {
|
|
24
|
+
if (init instanceof URLSearchParamsPolyfill) {
|
|
25
|
+
this.params = { ...init.params };
|
|
26
|
+
} else {
|
|
27
|
+
this.params = { ...init };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get size(): number {
|
|
33
|
+
return Object.keys(this.params).length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private parseString(str: string): void {
|
|
37
|
+
if (str.startsWith('?')) {
|
|
38
|
+
str = str.slice(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!str) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pairs = str.split('&');
|
|
46
|
+
for (const pair of pairs) {
|
|
47
|
+
const [key, value = ''] = pair.split('=');
|
|
48
|
+
if (key) {
|
|
49
|
+
this.params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
append(name: string, value: string): void {
|
|
55
|
+
// URLSearchParams.append should add to existing values, not replace
|
|
56
|
+
const existing = this.params[name];
|
|
57
|
+
if (existing) {
|
|
58
|
+
this.params[name] = existing + ',' + value;
|
|
59
|
+
} else {
|
|
60
|
+
this.params[name] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
delete(name: string): void {
|
|
65
|
+
delete this.params[name];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get(name: string): string | null {
|
|
69
|
+
return this.params[name] ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getAll(name: string): string[] {
|
|
73
|
+
// For simplicity, we only store one value per key
|
|
74
|
+
// In a full implementation, this would return an array of all values
|
|
75
|
+
const value = this.params[name];
|
|
76
|
+
return value ? [value] : [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
has(name: string): boolean {
|
|
80
|
+
return name in this.params;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
set(name: string, value: string): void {
|
|
84
|
+
this.params[name] = String(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
sort(): void {
|
|
88
|
+
const sortedKeys = Object.keys(this.params).sort();
|
|
89
|
+
const sortedParams: Record<string, string> = {};
|
|
90
|
+
for (const key of sortedKeys) {
|
|
91
|
+
sortedParams[key] = this.params[key] || '';
|
|
92
|
+
}
|
|
93
|
+
this.params = sortedParams;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
toString(): string {
|
|
97
|
+
const pairs: string[] = [];
|
|
98
|
+
for (const [key, value] of Object.entries(this.params)) {
|
|
99
|
+
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
100
|
+
}
|
|
101
|
+
return pairs.join('&');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
forEach(callback: (value: string, key: string, parent: URLSearchParamsPolyfill) => void): void {
|
|
105
|
+
for (const [key, value] of Object.entries(this.params)) {
|
|
106
|
+
callback(value, key, this);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
*keys(): IterableIterator<string> {
|
|
111
|
+
for (const key of Object.keys(this.params)) {
|
|
112
|
+
yield key;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
*values(): IterableIterator<string> {
|
|
117
|
+
for (const value of Object.values(this.params)) {
|
|
118
|
+
yield value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
*entries(): IterableIterator<[string, string]> {
|
|
123
|
+
for (const [key, value] of Object.entries(this.params)) {
|
|
124
|
+
yield [key, value];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Symbol.iterator to make it iterable
|
|
129
|
+
*[Symbol.iterator](): IterableIterator<[string, string]> {
|
|
130
|
+
yield* this.entries();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the global object for the current environment
|
|
136
|
+
* 获取当前环境的全局对象
|
|
137
|
+
*/
|
|
138
|
+
function getGlobalObject(): any {
|
|
139
|
+
// 尝试获取全局对象
|
|
140
|
+
const globalScope = Function('return this')();
|
|
141
|
+
|
|
142
|
+
// 微信小程序环境
|
|
143
|
+
if (globalScope && typeof globalScope.wx !== 'undefined' && globalScope.wx) {
|
|
144
|
+
return globalScope.wx;
|
|
145
|
+
}
|
|
146
|
+
// 支付宝小程序环境
|
|
147
|
+
if (globalScope && typeof globalScope.my !== 'undefined' && globalScope.my) {
|
|
148
|
+
return globalScope.my;
|
|
149
|
+
}
|
|
150
|
+
// 百度小程序环境
|
|
151
|
+
if (globalScope && typeof globalScope.swan !== 'undefined' && globalScope.swan) {
|
|
152
|
+
return globalScope.swan;
|
|
153
|
+
}
|
|
154
|
+
// 字节跳动小程序环境
|
|
155
|
+
if (globalScope && typeof globalScope.tt !== 'undefined' && globalScope.tt) {
|
|
156
|
+
return globalScope.tt;
|
|
157
|
+
}
|
|
158
|
+
// QQ小程序环境
|
|
159
|
+
if (globalScope && typeof globalScope.qq !== 'undefined' && globalScope.qq) {
|
|
160
|
+
return globalScope.qq;
|
|
161
|
+
}
|
|
162
|
+
// 通用全局对象检测
|
|
163
|
+
if (typeof globalThis !== 'undefined') {
|
|
164
|
+
return globalThis;
|
|
165
|
+
}
|
|
166
|
+
// 浏览器环境
|
|
167
|
+
if (typeof window !== 'undefined') {
|
|
168
|
+
return window;
|
|
169
|
+
}
|
|
170
|
+
// Node.js 环境
|
|
171
|
+
if (typeof global !== 'undefined') {
|
|
172
|
+
return global;
|
|
173
|
+
}
|
|
174
|
+
// Web Worker 环境
|
|
175
|
+
if (typeof self !== 'undefined') {
|
|
176
|
+
return self;
|
|
177
|
+
}
|
|
178
|
+
// 返回全局作用域
|
|
179
|
+
return globalScope;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Install polyfills for miniapp environment
|
|
184
|
+
* 为小程序环境安装 polyfill
|
|
185
|
+
*/
|
|
186
|
+
export function installPolyfills(): void {
|
|
187
|
+
try {
|
|
188
|
+
const globalObj = getGlobalObject();
|
|
189
|
+
|
|
190
|
+
if (!globalObj) {
|
|
191
|
+
console.warn('[Sentry] Unable to detect global object, polyfills may not work correctly');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Install URLSearchParams polyfill if not available
|
|
196
|
+
if (typeof globalObj.URLSearchParams === 'undefined') {
|
|
197
|
+
globalObj.URLSearchParams = URLSearchParamsPolyfill;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Also install to the global scope for direct access
|
|
201
|
+
// 同时安装到全局作用域以便直接访问
|
|
202
|
+
const globalScope = Function('return this')();
|
|
203
|
+
if (globalScope && typeof globalScope.URLSearchParams === 'undefined') {
|
|
204
|
+
globalScope.URLSearchParams = URLSearchParamsPolyfill as any;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// For environments where Function('return this')() doesn't work
|
|
208
|
+
// 对于 Function('return this')() 不起作用的环境
|
|
209
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.URLSearchParams === 'undefined') {
|
|
210
|
+
(globalThis as any).URLSearchParams = URLSearchParamsPolyfill;
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.warn('[Sentry] Failed to install polyfills:', error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if we're in a miniapp environment and install polyfills
|
|
219
|
+
* 检查是否在小程序环境中并安装 polyfill
|
|
220
|
+
*/
|
|
221
|
+
export function ensurePolyfills(): void {
|
|
222
|
+
// Always install polyfills regardless of environment to ensure compatibility
|
|
223
|
+
// This ensures URLSearchParams is always available when needed
|
|
224
|
+
installPolyfills();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if URLSearchParams is available in the current environment
|
|
229
|
+
* 检查当前环境是否支持 URLSearchParams
|
|
230
|
+
*/
|
|
231
|
+
export function isURLSearchParamsSupported(): boolean {
|
|
232
|
+
try {
|
|
233
|
+
const globalObj = getGlobalObject();
|
|
234
|
+
return globalObj && typeof globalObj.URLSearchParams !== 'undefined';
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Auto-install polyfills immediately when this module is loaded
|
|
241
|
+
// This runs before any other SDK functionality
|
|
242
|
+
ensurePolyfills();
|
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentScope,
|
|
3
|
+
initAndBind,
|
|
4
|
+
setContext,
|
|
5
|
+
withScope,
|
|
6
|
+
} from '@sentry/core';
|
|
7
|
+
import type {
|
|
8
|
+
Integration,
|
|
9
|
+
} from '@sentry/core';
|
|
10
|
+
|
|
11
|
+
import { MiniappClient } from './client';
|
|
12
|
+
import { appName, getSystemInfo, isMiniappEnvironment } from './crossPlatform';
|
|
13
|
+
import {
|
|
14
|
+
GlobalHandlers,
|
|
15
|
+
TryCatch,
|
|
16
|
+
LinkedErrors,
|
|
17
|
+
HttpContext,
|
|
18
|
+
Dedupe,
|
|
19
|
+
performanceIntegration,
|
|
20
|
+
RewriteFrames,
|
|
21
|
+
NetworkBreadcrumbs,
|
|
22
|
+
} from './integrations/index';
|
|
23
|
+
import type { MiniappOptions, ReportDialogOptions, SendFeedbackParams } from './types';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default integrations for the miniapp SDK
|
|
27
|
+
*/
|
|
28
|
+
export const defaultIntegrations: Integration[] = [
|
|
29
|
+
// Core integrations
|
|
30
|
+
new HttpContext(),
|
|
31
|
+
new Dedupe(),
|
|
32
|
+
new GlobalHandlers(),
|
|
33
|
+
new TryCatch(),
|
|
34
|
+
new LinkedErrors(),
|
|
35
|
+
// Performance monitoring
|
|
36
|
+
performanceIntegration({
|
|
37
|
+
enableNavigation: true,
|
|
38
|
+
enableRender: true,
|
|
39
|
+
enableResource: true,
|
|
40
|
+
enableUserTiming: true,
|
|
41
|
+
sampleRate: 1.0,
|
|
42
|
+
reportInterval: 30000,
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get default integrations for the miniapp SDK
|
|
48
|
+
* @returns Array of default integrations
|
|
49
|
+
*/
|
|
50
|
+
export function getDefaultIntegrations(): Integration[] {
|
|
51
|
+
return [...defaultIntegrations];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the Sentry Miniapp SDK
|
|
56
|
+
* @param options Configuration options for the SDK
|
|
57
|
+
*/
|
|
58
|
+
export function init(options: MiniappOptions = {} as any): MiniappClient | undefined {
|
|
59
|
+
if (!isMiniappEnvironment()) {
|
|
60
|
+
console.warn('sentry-miniapp: Not running in a supported miniapp environment');
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const opts = {
|
|
65
|
+
...options,
|
|
66
|
+
integrations: options.integrations || defaultIntegrations,
|
|
67
|
+
stackParser: () => [],
|
|
68
|
+
transport: options.transport,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (opts.enableSourceMap !== false) {
|
|
72
|
+
opts.integrations.push(new RewriteFrames());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
opts.integrations.push(new NetworkBreadcrumbs({ traceNetworkBody: opts.traceNetworkBody }));
|
|
76
|
+
|
|
77
|
+
// Set platform context
|
|
78
|
+
setContext('miniapp', {
|
|
79
|
+
platform: appName,
|
|
80
|
+
environment: 'miniapp',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Add system information
|
|
84
|
+
const systemInfo = getSystemInfo();
|
|
85
|
+
if (systemInfo) {
|
|
86
|
+
setContext('device', {
|
|
87
|
+
brand: systemInfo.brand,
|
|
88
|
+
model: systemInfo.model,
|
|
89
|
+
language: systemInfo.language,
|
|
90
|
+
system: systemInfo.system,
|
|
91
|
+
platform: systemInfo.platform,
|
|
92
|
+
screen_resolution: `${systemInfo.screenWidth}x${systemInfo.screenHeight}`,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
setContext('app', {
|
|
96
|
+
sdk_version: systemInfo.SDKVersion,
|
|
97
|
+
version: systemInfo.version,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
initAndBind(MiniappClient as any, opts as any);
|
|
102
|
+
return getCurrentScope().getClient() as MiniappClient;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Miniapp environment does not support Sentry's default HTML report dialog.
|
|
107
|
+
* Please implement your own UI form to collect user feedback (name, email, comments)
|
|
108
|
+
* and use `Sentry.captureFeedback()` to submit it to Sentry.
|
|
109
|
+
*
|
|
110
|
+
* 小程序环境不支持 Sentry 官方的 HTML 反馈弹窗。
|
|
111
|
+
* 请自行实现 UI 表单收集用户反馈,并调用 `Sentry.captureFeedback()` 进行上报。
|
|
112
|
+
*/
|
|
113
|
+
export function showReportDialog(_options: ReportDialogOptions = {}): void {
|
|
114
|
+
console.warn(
|
|
115
|
+
'[sentry-miniapp] showReportDialog is deprecated and does nothing. ' +
|
|
116
|
+
'Please build your own UI and use `Sentry.captureFeedback()` instead.'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the last event ID
|
|
122
|
+
*/
|
|
123
|
+
export function lastEventId(): string | undefined {
|
|
124
|
+
return getCurrentScope().lastEventId();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Flush pending events
|
|
129
|
+
*/
|
|
130
|
+
export function flush(timeout?: number): PromiseLike<boolean> {
|
|
131
|
+
const client = getCurrentScope().getClient();
|
|
132
|
+
if (client) {
|
|
133
|
+
return client.flush(timeout);
|
|
134
|
+
}
|
|
135
|
+
return Promise.resolve(false);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Close the SDK
|
|
140
|
+
*/
|
|
141
|
+
export function close(timeout?: number): PromiseLike<boolean> {
|
|
142
|
+
const client = getCurrentScope().getClient();
|
|
143
|
+
if (client) {
|
|
144
|
+
return client.close(timeout);
|
|
145
|
+
}
|
|
146
|
+
return Promise.resolve(false);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Wrap a function to capture exceptions
|
|
151
|
+
*/
|
|
152
|
+
export function wrap<T extends (...args: any[]) => any>(fn: T): T {
|
|
153
|
+
return (function (this: any, ...args: Parameters<T>) {
|
|
154
|
+
return withScope(() => {
|
|
155
|
+
try {
|
|
156
|
+
return fn.apply(this, args);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
getCurrentScope().captureException(error);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}) as T;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Capture feedback using the new feedback API.
|
|
169
|
+
* 使用新的反馈 API 捕获反馈
|
|
170
|
+
*
|
|
171
|
+
* @param params Feedback parameters
|
|
172
|
+
* @returns Event ID
|
|
173
|
+
*/
|
|
174
|
+
export function captureFeedback(params: SendFeedbackParams): string {
|
|
175
|
+
const client = getCurrentScope().getClient() as MiniappClient | undefined;
|
|
176
|
+
if (client) {
|
|
177
|
+
return client.captureFeedback(params);
|
|
178
|
+
} else {
|
|
179
|
+
console.warn('sentry-miniapp: No client available for captureFeedback');
|
|
180
|
+
return '';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Envelope } from '@sentry/core';
|
|
2
|
+
import type { OfflineStore, OfflineTransportOptions } from '@sentry/core/build/types/transports/offline';
|
|
3
|
+
import { sdk } from '../crossPlatform';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OFFLINE_CACHE_SIZE = 30; // 默认最大缓存数量
|
|
6
|
+
const OFFLINE_STORE_KEY = 'sentry_offline_store';
|
|
7
|
+
|
|
8
|
+
export interface MiniappOfflineStoreOptions extends OfflineTransportOptions {
|
|
9
|
+
offlineCacheLimit?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates an offline store using miniapp storage API
|
|
14
|
+
*/
|
|
15
|
+
export function createMiniappOfflineStore(options: MiniappOfflineStoreOptions): OfflineStore {
|
|
16
|
+
const maxCacheSize = options.offlineCacheLimit || DEFAULT_OFFLINE_CACHE_SIZE;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
push: async (env: Envelope): Promise<void> => {
|
|
20
|
+
try {
|
|
21
|
+
const store = getStore();
|
|
22
|
+
store.push(env);
|
|
23
|
+
if (store.length > maxCacheSize) {
|
|
24
|
+
store.shift(); // 移除最早的缓存以限制大小
|
|
25
|
+
}
|
|
26
|
+
setStore(store);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.warn('[Sentry] Failed to push to offline store', e);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
unshift: async (env: Envelope): Promise<void> => {
|
|
32
|
+
try {
|
|
33
|
+
const store = getStore();
|
|
34
|
+
store.unshift(env);
|
|
35
|
+
if (store.length > maxCacheSize) {
|
|
36
|
+
store.pop(); // 移除最旧的缓存以限制大小
|
|
37
|
+
}
|
|
38
|
+
setStore(store);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.warn('[Sentry] Failed to unshift to offline store', e);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
shift: async (): Promise<Envelope | undefined> => {
|
|
44
|
+
try {
|
|
45
|
+
const store = getStore();
|
|
46
|
+
if (store.length === 0) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const env = store.shift();
|
|
50
|
+
setStore(store);
|
|
51
|
+
return env;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.warn('[Sentry] Failed to shift from offline store', e);
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getStore(): Envelope[] {
|
|
61
|
+
try {
|
|
62
|
+
const storageApi = sdk().getStorageSync;
|
|
63
|
+
if (storageApi) {
|
|
64
|
+
const storedStr = storageApi(OFFLINE_STORE_KEY);
|
|
65
|
+
if (storedStr) {
|
|
66
|
+
// storage API may return string or parsed object directly depending on platform
|
|
67
|
+
return typeof storedStr === 'string' ? JSON.parse(storedStr) : storedStr;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// ignore
|
|
72
|
+
}
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function setStore(store: Envelope[]): void {
|
|
77
|
+
try {
|
|
78
|
+
const storageApi = sdk().setStorageSync;
|
|
79
|
+
if (storageApi) {
|
|
80
|
+
storageApi(OFFLINE_STORE_KEY, JSON.stringify(store));
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse } from '@sentry/core';
|
|
2
|
+
import { createTransport } from '@sentry/core';
|
|
3
|
+
|
|
4
|
+
import { sdk } from '../crossPlatform';
|
|
5
|
+
|
|
6
|
+
export interface MiniappTransportOptions extends BaseTransportOptions {
|
|
7
|
+
/** Custom headers for the request */
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a Transport that uses the miniapp request API to send events to Sentry.
|
|
13
|
+
*/
|
|
14
|
+
export function createMiniappTransport(options: MiniappTransportOptions): Transport {
|
|
15
|
+
// 保存 URL 到局部变量
|
|
16
|
+
const transportUrl = options.url;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Make a request using miniapp request API
|
|
20
|
+
*/
|
|
21
|
+
function makeRequest(request: any): Promise<TransportMakeRequestResponse> {
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const requestOptions = {
|
|
25
|
+
url: transportUrl,
|
|
26
|
+
method: 'POST' as const,
|
|
27
|
+
data: request.body,
|
|
28
|
+
header: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
...request.headers,
|
|
31
|
+
},
|
|
32
|
+
// Alipay uses `headers` instead of `header`
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
...request.headers,
|
|
36
|
+
},
|
|
37
|
+
timeout: 10000,
|
|
38
|
+
success: (res: any) => {
|
|
39
|
+
// Alipay uses `status` instead of `statusCode`, and `headers` instead of `header`
|
|
40
|
+
const status = res.statusCode || res.status;
|
|
41
|
+
const resHeaders = res.header || res.headers || {};
|
|
42
|
+
|
|
43
|
+
resolve({
|
|
44
|
+
statusCode: status,
|
|
45
|
+
headers: {
|
|
46
|
+
'x-sentry-rate-limits': resHeaders['x-sentry-rate-limits'],
|
|
47
|
+
'retry-after': resHeaders['retry-after'],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
fail: (error: any) => {
|
|
52
|
+
reject(new Error(`Network request failed: ${error.errMsg || error.errorMessage || error.message || 'Unknown error'}`));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Use the appropriate request method based on the platform
|
|
57
|
+
if (sdk().request) {
|
|
58
|
+
sdk().request?.(requestOptions);
|
|
59
|
+
} else if (sdk().httpRequest) {
|
|
60
|
+
sdk().httpRequest?.(requestOptions);
|
|
61
|
+
} else {
|
|
62
|
+
reject(new Error('No request method available in current miniapp environment'));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return createTransport(options, makeRequest);
|
|
68
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ClientOptions, BaseTransportOptions, Integration } from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the Sentry Miniapp SDK.
|
|
5
|
+
*/
|
|
6
|
+
export interface MiniappOptions {
|
|
7
|
+
/** Sentry DSN */
|
|
8
|
+
dsn?: string;
|
|
9
|
+
|
|
10
|
+
/** Environment */
|
|
11
|
+
environment?: string;
|
|
12
|
+
|
|
13
|
+
/** Debug mode */
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
|
|
16
|
+
/** Sample rate */
|
|
17
|
+
sampleRate?: number;
|
|
18
|
+
|
|
19
|
+
/** Release version */
|
|
20
|
+
release?: string;
|
|
21
|
+
|
|
22
|
+
/** Maximum number of breadcrumbs */
|
|
23
|
+
maxBreadcrumbs?: number;
|
|
24
|
+
|
|
25
|
+
/** Traces sample rate */
|
|
26
|
+
tracesSampleRate?: number;
|
|
27
|
+
|
|
28
|
+
/** Transport function */
|
|
29
|
+
transport?: any;
|
|
30
|
+
|
|
31
|
+
/** Before send hook */
|
|
32
|
+
beforeSend?: any;
|
|
33
|
+
|
|
34
|
+
/** Before breadcrumb hook */
|
|
35
|
+
beforeBreadcrumb?: any;
|
|
36
|
+
|
|
37
|
+
/** Miniapp platform type */
|
|
38
|
+
platform?: 'wechat' | 'alipay' | 'bytedance' | 'qq' | 'baidu' | 'dingtalk';
|
|
39
|
+
|
|
40
|
+
/** Whether to enable system info collection */
|
|
41
|
+
enableSystemInfo?: boolean;
|
|
42
|
+
|
|
43
|
+
/** Whether to enable user interaction breadcrumbs */
|
|
44
|
+
enableUserInteractionBreadcrumbs?: boolean;
|
|
45
|
+
|
|
46
|
+
/** Whether to enable console breadcrumbs */
|
|
47
|
+
enableConsoleBreadcrumbs?: boolean;
|
|
48
|
+
|
|
49
|
+
/** Whether to enable navigation breadcrumbs */
|
|
50
|
+
enableNavigationBreadcrumbs?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Whether to enable automatic source map path rewrite */
|
|
53
|
+
enableSourceMap?: boolean;
|
|
54
|
+
|
|
55
|
+
/** Whether to capture and record request and response body in network breadcrumbs */
|
|
56
|
+
traceNetworkBody?: boolean;
|
|
57
|
+
|
|
58
|
+
/** Whether to enable offline cache to retry sending events later */
|
|
59
|
+
enableOfflineCache?: boolean;
|
|
60
|
+
|
|
61
|
+
/** Maximum number of events to store in offline cache (default: 30) */
|
|
62
|
+
offlineCacheLimit?: number;
|
|
63
|
+
|
|
64
|
+
/** Array of strings or regexes that match error URLs which should be exclusively sent to Sentry */
|
|
65
|
+
allowUrls?: Array<string | RegExp>;
|
|
66
|
+
|
|
67
|
+
/** Array of strings or regexes that match error URLs which should not be sent to Sentry */
|
|
68
|
+
denyUrls?: Array<string | RegExp>;
|
|
69
|
+
|
|
70
|
+
/** Integrations */
|
|
71
|
+
integrations?: Integration[];
|
|
72
|
+
|
|
73
|
+
/** Default integrations */
|
|
74
|
+
defaultIntegrations?: Integration[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Client options for the Miniapp SDK.
|
|
79
|
+
*/
|
|
80
|
+
export interface MiniappClientOptions extends ClientOptions<BaseTransportOptions> {
|
|
81
|
+
options?: MiniappOptions;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* All properties the report dialog supports
|
|
86
|
+
*/
|
|
87
|
+
export interface ReportDialogOptions {
|
|
88
|
+
[key: string]: any;
|
|
89
|
+
eventId?: string;
|
|
90
|
+
dsn?: string;
|
|
91
|
+
user?: {
|
|
92
|
+
email?: string;
|
|
93
|
+
name?: string;
|
|
94
|
+
};
|
|
95
|
+
lang?: string;
|
|
96
|
+
title?: string;
|
|
97
|
+
subtitle?: string;
|
|
98
|
+
subtitle2?: string;
|
|
99
|
+
labelName?: string;
|
|
100
|
+
labelEmail?: string;
|
|
101
|
+
labelComments?: string;
|
|
102
|
+
labelClose?: string;
|
|
103
|
+
labelSubmit?: string;
|
|
104
|
+
errorGeneric?: string;
|
|
105
|
+
errorFormEntry?: string;
|
|
106
|
+
successMessage?: string;
|
|
107
|
+
/** Callback after reportDialog showed up */
|
|
108
|
+
onLoad?(): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parameters for sending user feedback
|
|
115
|
+
*/
|
|
116
|
+
export interface SendFeedbackParams {
|
|
117
|
+
message: string;
|
|
118
|
+
name?: string;
|
|
119
|
+
email?: string;
|
|
120
|
+
url?: string;
|
|
121
|
+
source?: string;
|
|
122
|
+
associatedEventId?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Set an object that will be merged sent as tags data with the event.
|
|
125
|
+
*/
|
|
126
|
+
tags?: {
|
|
127
|
+
[key: string]: string | number | boolean;
|
|
128
|
+
};
|
|
129
|
+
}
|