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.
Files changed (46) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +88 -0
  3. package/dist/sentry-miniapp.cjs.js +142 -39
  4. package/dist/sentry-miniapp.cjs.js.map +1 -1
  5. package/dist/sentry-miniapp.esm.js +142 -39
  6. package/dist/sentry-miniapp.esm.js.map +1 -1
  7. package/dist/sentry-miniapp.umd.js +142 -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 +1 -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/sdk.d.ts +6 -4
  16. package/dist/types/sdk.d.ts.map +1 -1
  17. package/dist/types/transports/offlineStore.d.ts +4 -1
  18. package/dist/types/transports/offlineStore.d.ts.map +1 -1
  19. package/dist/types/types.d.ts +4 -0
  20. package/dist/types/types.d.ts.map +1 -1
  21. package/dist/types/version.d.ts +1 -1
  22. package/package.json +94 -16
  23. package/src/.keep +0 -0
  24. package/src/client.ts +203 -0
  25. package/src/crossPlatform.ts +407 -0
  26. package/src/eventbuilder.ts +291 -0
  27. package/src/helpers.ts +214 -0
  28. package/src/index.ts +86 -0
  29. package/src/integrations/dedupe.ts +215 -0
  30. package/src/integrations/globalhandlers.ts +209 -0
  31. package/src/integrations/httpcontext.ts +140 -0
  32. package/src/integrations/index.ts +10 -0
  33. package/src/integrations/linkederrors.ts +107 -0
  34. package/src/integrations/networkbreadcrumbs.ts +155 -0
  35. package/src/integrations/performance.ts +622 -0
  36. package/src/integrations/rewriteframes.ts +77 -0
  37. package/src/integrations/router.ts +180 -0
  38. package/src/integrations/system.ts +135 -0
  39. package/src/integrations/trycatch.ts +233 -0
  40. package/src/polyfills.ts +242 -0
  41. package/src/sdk.ts +182 -0
  42. package/src/transports/index.ts +3 -0
  43. package/src/transports/offlineStore.ts +85 -0
  44. package/src/transports/xhr.ts +68 -0
  45. package/src/types.ts +129 -0
  46. package/src/version.ts +3 -0
@@ -0,0 +1,180 @@
1
+ import { addBreadcrumb, getCurrentScope } from '@sentry/core';
2
+ import type { Integration, IntegrationFn } from '@sentry/core';
3
+
4
+ /** Router integration for miniapp navigation */
5
+ export class Router implements Integration {
6
+ /**
7
+ * @inheritDoc
8
+ */
9
+ public static id: string = 'Router';
10
+
11
+ /**
12
+ * @inheritDoc
13
+ */
14
+ public name: string = Router.id;
15
+
16
+ /**
17
+ * @inheritDoc
18
+ */
19
+ private _lastRoute: string = '';
20
+
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ public setupOnce(): void {
25
+ this._instrumentNavigation();
26
+ this._startRouteMonitoring();
27
+ }
28
+
29
+ /**
30
+ * Instrument navigation functions
31
+ */
32
+ private _instrumentNavigation(): void {
33
+ const global = globalThis as any;
34
+
35
+ // Instrument wx.navigateTo
36
+ if (global.wx && global.wx.navigateTo) {
37
+ const originalNavigateTo = global.wx.navigateTo;
38
+ global.wx.navigateTo = (options: any) => {
39
+ this._recordNavigation('navigateTo', options.url, this._getCurrentRoute());
40
+ return originalNavigateTo.call(global.wx, options);
41
+ };
42
+ }
43
+
44
+ // Instrument wx.redirectTo
45
+ if (global.wx && global.wx.redirectTo) {
46
+ const originalRedirectTo = global.wx.redirectTo;
47
+ global.wx.redirectTo = (options: any) => {
48
+ this._recordNavigation('redirectTo', options.url, this._getCurrentRoute());
49
+ return originalRedirectTo.call(global.wx, options);
50
+ };
51
+ }
52
+
53
+ // Instrument wx.switchTab
54
+ if (global.wx && global.wx.switchTab) {
55
+ const originalSwitchTab = global.wx.switchTab;
56
+ global.wx.switchTab = (options: any) => {
57
+ this._recordNavigation('switchTab', options.url, this._getCurrentRoute());
58
+ return originalSwitchTab.call(global.wx, options);
59
+ };
60
+ }
61
+
62
+ // Instrument wx.navigateBack
63
+ if (global.wx && global.wx.navigateBack) {
64
+ const originalNavigateBack = global.wx.navigateBack;
65
+ global.wx.navigateBack = (options: any = {}) => {
66
+ this._recordNavigation('navigateBack', 'back', this._getCurrentRoute(), options.delta);
67
+ return originalNavigateBack.call(global.wx, options);
68
+ };
69
+ }
70
+
71
+ // Instrument wx.reLaunch
72
+ if (global.wx && global.wx.reLaunch) {
73
+ const originalReLaunch = global.wx.reLaunch;
74
+ global.wx.reLaunch = (options: any) => {
75
+ this._recordNavigation('reLaunch', options.url, this._getCurrentRoute());
76
+ return originalReLaunch.call(global.wx, options);
77
+ };
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Start monitoring route changes
83
+ */
84
+ private _startRouteMonitoring(): void {
85
+ // Monitor route changes by checking current pages periodically
86
+ setInterval(() => {
87
+ const currentRoute = this._getCurrentRoute();
88
+ if (currentRoute && currentRoute !== this._lastRoute) {
89
+ this._recordRouteChange(this._lastRoute, currentRoute);
90
+ this._lastRoute = currentRoute;
91
+ }
92
+ }, 1000);
93
+ }
94
+
95
+ /**
96
+ * Get current route
97
+ */
98
+ private _getCurrentRoute(): string {
99
+ try {
100
+ const global = globalThis as any;
101
+ if (global.getCurrentPages) {
102
+ const pages = global.getCurrentPages();
103
+ if (pages && pages.length > 0) {
104
+ const currentPage = pages[pages.length - 1];
105
+ return currentPage.route || currentPage.__route__ || '';
106
+ }
107
+ }
108
+ } catch (e) {
109
+ // Ignore errors
110
+ }
111
+ return '';
112
+ }
113
+
114
+ /**
115
+ * Record navigation action
116
+ */
117
+ private _recordNavigation(action: string, to: string, from: string, delta?: number): void {
118
+ const scope = getCurrentScope();
119
+
120
+ // Add breadcrumb
121
+ addBreadcrumb({
122
+ category: 'navigation',
123
+ data: {
124
+ action,
125
+ from,
126
+ to,
127
+ delta,
128
+ },
129
+ message: `Navigation ${action}: ${from} -> ${to}`,
130
+ type: 'navigation',
131
+ });
132
+
133
+ // Set current route tag
134
+ scope.setTag('route', to === 'back' ? from : to);
135
+
136
+ // Set navigation context
137
+ scope.setContext('navigation', {
138
+ action,
139
+ from,
140
+ to,
141
+ delta,
142
+ timestamp: Date.now(),
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Record route change
148
+ */
149
+ private _recordRouteChange(from: string, to: string): void {
150
+ const scope = getCurrentScope();
151
+
152
+ // Add breadcrumb
153
+ addBreadcrumb({
154
+ category: 'navigation',
155
+ data: {
156
+ from,
157
+ to,
158
+ },
159
+ message: `Route changed: ${from} -> ${to}`,
160
+ type: 'navigation',
161
+ });
162
+
163
+ // Update route tag
164
+ scope.setTag('route', to);
165
+
166
+ // Update route context
167
+ scope.setContext('route', {
168
+ current: to,
169
+ previous: from,
170
+ timestamp: Date.now(),
171
+ });
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Router integration
177
+ */
178
+ export const routerIntegration: IntegrationFn = () => {
179
+ return new Router();
180
+ };
@@ -0,0 +1,135 @@
1
+ import { getCurrentScope } from '@sentry/core';
2
+ import type { Integration, IntegrationFn } from '@sentry/core';
3
+
4
+ import { getSystemInfo, sdk } from '../crossPlatform';
5
+
6
+ /** System information integration */
7
+ export class System implements Integration {
8
+ /**
9
+ * @inheritDoc
10
+ */
11
+ public static id: string = 'System';
12
+
13
+ /**
14
+ * @inheritDoc
15
+ */
16
+ public name: string = System.id;
17
+
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ public setupOnce(): void {
22
+ this._addSystemContext();
23
+ this._addNetworkContext();
24
+ this._addLocationContext();
25
+ }
26
+
27
+ /**
28
+ * Add system information to context
29
+ */
30
+ private _addSystemContext(): void {
31
+ try {
32
+ const systemInfo = getSystemInfo();
33
+ if (systemInfo) {
34
+ const scope = getCurrentScope();
35
+
36
+ // Set device context
37
+ scope.setContext('device', {
38
+ name: systemInfo.model,
39
+ model: systemInfo.model,
40
+ brand: systemInfo.brand,
41
+ family: systemInfo.platform,
42
+ arch: systemInfo.platform,
43
+ });
44
+
45
+ // Set OS context
46
+ scope.setContext('os', {
47
+ name: systemInfo.system?.split(' ')[0] || 'unknown',
48
+ version: systemInfo.system?.split(' ')[1] || 'unknown',
49
+ kernel_version: systemInfo.version,
50
+ });
51
+
52
+ // Set app context
53
+ scope.setContext('app', {
54
+ app_name: (systemInfo as any).appName || 'unknown',
55
+ app_version: systemInfo.version,
56
+ });
57
+
58
+ // Set screen context
59
+ scope.setContext('screen', {
60
+ screen_width: systemInfo.screenWidth,
61
+ screen_height: systemInfo.screenHeight,
62
+ screen_density: systemInfo.pixelRatio,
63
+ });
64
+
65
+ // Set tags
66
+ scope.setTag('device.model', systemInfo.model);
67
+ scope.setTag('device.brand', systemInfo.brand);
68
+ scope.setTag('os.name', systemInfo.system?.split(' ')[0] || 'unknown');
69
+ scope.setTag('os.version', systemInfo.system?.split(' ')[1] || 'unknown');
70
+ scope.setTag('app.version', systemInfo.version);
71
+ scope.setTag('language', systemInfo.language);
72
+ }
73
+ } catch (e) {
74
+ // Ignore errors when getting system info
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Add network information to context
80
+ */
81
+ private _addNetworkContext(): void {
82
+ try {
83
+ if ((sdk() as any).getNetworkType) {
84
+ (sdk() as any).getNetworkType({
85
+ success: (res: { networkType: string; isConnected?: boolean }) => {
86
+ const scope = getCurrentScope();
87
+ scope.setContext('network', {
88
+ type: res.networkType,
89
+ connected: res.isConnected !== false,
90
+ });
91
+ scope.setTag('network.type', res.networkType);
92
+ },
93
+ fail: () => {
94
+ // Ignore network type fetch errors
95
+ },
96
+ });
97
+ }
98
+ } catch (e) {
99
+ // Ignore errors when getting network info
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Add location information to context (if available)
105
+ */
106
+ private _addLocationContext(): void {
107
+ try {
108
+ if ((sdk() as any).getLocation) {
109
+ (sdk() as any).getLocation({
110
+ type: 'gcj02',
111
+ success: (res: { latitude: number; longitude: number; accuracy: number }) => {
112
+ const scope = getCurrentScope();
113
+ scope.setContext('location', {
114
+ latitude: res.latitude,
115
+ longitude: res.longitude,
116
+ accuracy: res.accuracy,
117
+ });
118
+ },
119
+ fail: () => {
120
+ // Ignore location fetch errors (user might not grant permission)
121
+ },
122
+ });
123
+ }
124
+ } catch (e) {
125
+ // Ignore errors when getting location info
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * System integration
132
+ */
133
+ export const systemIntegration: IntegrationFn = () => {
134
+ return new System();
135
+ };
@@ -0,0 +1,233 @@
1
+ import { captureException, getCurrentScope } from '@sentry/core';
2
+ import type { Integration, IntegrationFn, WrappedFunction } from '@sentry/core';
3
+
4
+ /** Wrap timer functions and event targets to catch errors and provide better meta data */
5
+ export class TryCatch implements Integration {
6
+ /**
7
+ * @inheritDoc
8
+ */
9
+ public static id: string = 'TryCatch';
10
+
11
+ /**
12
+ * @inheritDoc
13
+ */
14
+ public name: string = TryCatch.id;
15
+
16
+ /** JSDoc */
17
+ private _wrapTimeFunction(original: (...args: any[]) => any): (...args: any[]) => any {
18
+ return function (this: any, ...args: any[]): any {
19
+ const originalCallback = args[0];
20
+ args[0] = wrap(originalCallback, {
21
+ mechanism: {
22
+ data: { function: getFunctionName(original) },
23
+ handled: true,
24
+ type: 'instrument',
25
+ },
26
+ });
27
+ return original.apply(this, args);
28
+ };
29
+ }
30
+
31
+ /** JSDoc */
32
+ private _wrapRAF(original: any): (callback: () => void) => any {
33
+ return function (this: any, callback: () => void): any {
34
+ return original(
35
+ wrap(callback, {
36
+ mechanism: {
37
+ data: {
38
+ function: 'requestAnimationFrame',
39
+ handler: getFunctionName(original),
40
+ },
41
+ handled: true,
42
+ type: 'instrument',
43
+ },
44
+ }),
45
+ );
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Wrap timer functions and event targets to catch errors
51
+ * and provide better metadata.
52
+ */
53
+ public setupOnce(): void {
54
+
55
+ // In miniapp environment, we mainly focus on wrapping common async functions
56
+ const global = globalThis as any;
57
+
58
+ if (global.setTimeout) {
59
+ fill(global, 'setTimeout', this._wrapTimeFunction.bind(this));
60
+ }
61
+ if (global.setInterval) {
62
+ fill(global, 'setInterval', this._wrapTimeFunction.bind(this));
63
+ }
64
+ if (global.requestAnimationFrame) {
65
+ fill(global, 'requestAnimationFrame', this._wrapRAF.bind(this));
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Wrap a function to capture exceptions
72
+ */
73
+ export function wrap(
74
+ fn: WrappedFunction,
75
+ options: {
76
+ mechanism?: {
77
+ data?: Record<string, any>;
78
+ handled?: boolean;
79
+ type?: string;
80
+ };
81
+ capture?: boolean;
82
+ } = {},
83
+ before?: WrappedFunction,
84
+ ): any {
85
+ // tslint:disable-next-line:strict-type-predicates
86
+ if (typeof fn !== 'function') {
87
+ return fn;
88
+ }
89
+
90
+ try {
91
+ // We don't wanna wrap it twice
92
+ if ((fn as any).__sentry__) {
93
+ return fn;
94
+ }
95
+
96
+ // If this has already been wrapped in the past, return that wrapped function
97
+ if (fn.__sentry_wrapped__) {
98
+ return fn.__sentry_wrapped__;
99
+ }
100
+ } catch (e) {
101
+ // Just accessing custom props in some environments
102
+ // can cause a "Permission denied" exception.
103
+ // Bail on wrapping and return the function as-is.
104
+ return fn;
105
+ }
106
+
107
+ const sentryWrapped: WrappedFunction = function (this: any, ...args: any[]): any {
108
+ // tslint:disable-next-line:strict-type-predicates
109
+ if (before && typeof before === 'function') {
110
+ before.apply(this, args);
111
+ }
112
+
113
+ try {
114
+ const wrappedArguments = args.map((arg: any) => wrap(arg, options));
115
+
116
+ if ((fn as any).handleEvent) {
117
+ return (fn as any).handleEvent.apply(this, wrappedArguments);
118
+ }
119
+
120
+ return fn.apply(this, wrappedArguments);
121
+ } catch (ex) {
122
+ const scope = getCurrentScope();
123
+
124
+ scope.addEventProcessor((event) => {
125
+ const processedEvent = { ...event };
126
+
127
+ if (options.mechanism) {
128
+ processedEvent.exception = processedEvent.exception || {};
129
+ (processedEvent.exception as any).mechanism = options.mechanism;
130
+ }
131
+
132
+ processedEvent.extra = {
133
+ ...processedEvent.extra,
134
+ arguments: args,
135
+ };
136
+
137
+ return processedEvent;
138
+ });
139
+
140
+ captureException(ex);
141
+ throw ex;
142
+ }
143
+ };
144
+
145
+ // Accessing some objects may throw
146
+ try {
147
+ // tslint:disable-next-line: no-for-in
148
+ for (const property in fn) {
149
+ if (Object.prototype.hasOwnProperty.call(fn, property)) {
150
+ (sentryWrapped as any)[property] = (fn as any)[property];
151
+ }
152
+ }
153
+ } catch (_oO) {
154
+ // no-empty
155
+ }
156
+
157
+ fn.prototype = fn.prototype || {};
158
+ sentryWrapped.prototype = fn.prototype;
159
+
160
+ Object.defineProperty(fn, '__sentry_wrapped__', {
161
+ enumerable: false,
162
+ value: sentryWrapped,
163
+ });
164
+
165
+ // Signal that this function has been wrapped/filled already
166
+ Object.defineProperties(sentryWrapped, {
167
+ __sentry__: {
168
+ enumerable: false,
169
+ value: true,
170
+ },
171
+ __sentry_original__: {
172
+ enumerable: false,
173
+ value: fn,
174
+ },
175
+ });
176
+
177
+ // Restore original function name
178
+ try {
179
+ const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') as PropertyDescriptor;
180
+ if (descriptor.configurable) {
181
+ Object.defineProperty(sentryWrapped, 'name', {
182
+ get(): string {
183
+ return fn.name;
184
+ },
185
+ });
186
+ }
187
+ } catch (_oO) {
188
+ // no-empty
189
+ }
190
+
191
+ return sentryWrapped;
192
+ }
193
+
194
+ /**
195
+ * Fill an object with a new value, keeping a reference to the original
196
+ */
197
+ function fill(source: { [key: string]: any }, name: string, replacementFactory: (...args: any[]) => any): void {
198
+ if (!(name in source)) {
199
+ return;
200
+ }
201
+
202
+ const original = source[name] as () => any;
203
+ const wrapped = replacementFactory(original);
204
+
205
+ if (typeof wrapped === 'function') {
206
+ try {
207
+ wrapped.prototype = wrapped.prototype || {};
208
+ wrapped.prototype.constructor = wrapped;
209
+ } catch (_Oo) {
210
+ // This can throw in some funky environments
211
+ }
212
+ }
213
+
214
+ source[name] = wrapped;
215
+ }
216
+
217
+ /**
218
+ * Safely extract function name from itself
219
+ */
220
+ function getFunctionName(fn: any): string {
221
+ try {
222
+ return (fn && fn.name) || '<anonymous>';
223
+ } catch (e) {
224
+ return '<anonymous>';
225
+ }
226
+ }
227
+
228
+ /**
229
+ * TryCatch integration
230
+ */
231
+ export const tryCatchIntegration: IntegrationFn = () => {
232
+ return new TryCatch();
233
+ };