-
-
Notifications
You must be signed in to change notification settings - Fork 359
Expand file tree
/
Copy pathclient.ts
More file actions
261 lines (233 loc) · 8.11 KB
/
client.ts
File metadata and controls
261 lines (233 loc) · 8.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
import { eventFromException, eventFromMessage } from '@sentry/browser';
import type {
ClientReportEnvelope,
ClientReportItem,
Envelope,
Event,
EventHint,
Outcome,
SeverityLevel,
TransportMakeRequestResponse,
UserFeedback,
} from '@sentry/core';
import {
_INTERNAL_flushLogsBuffer,
addAutoIpAddressToSession,
Client,
dateTimestampInSeconds,
debug,
SentryError,
} from '@sentry/core';
import { Alert } from 'react-native';
import { getDevServer } from './integrations/debugsymbolicatorutils';
import { defaultSdkInfo } from './integrations/sdkinfo';
import { getDefaultSidecarUrl } from './integrations/spotlight';
import type { ReactNativeClientOptions } from './options';
import type { mobileReplayIntegration } from './replay/mobilereplay';
import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay';
import { createUserFeedbackEnvelope, items } from './utils/envelope';
import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
import { mergeOutcomes } from './utils/outcome';
import { ReactNativeLibraries } from './utils/rnlibraries';
import { NATIVE } from './wrapper';
const DEFAULT_FLUSH_INTERVAL = 5000;
/**
* The Sentry React Native SDK Client.
*
* @see ReactNativeClientOptions for documentation on configuration options.
* @see SentryClient for usage documentation.
*/
export class ReactNativeClient extends Client<ReactNativeClientOptions> {
private _outcomesBuffer: Outcome[];
private _logFlushIdleTimeout: ReturnType<typeof setTimeout> | undefined;
/**
* Creates a new React Native SDK instance.
* @param options Configuration options for this SDK.
*/
public constructor(options: ReactNativeClientOptions) {
ignoreRequireCycleLogs(ReactNativeLibraries.ReactNativeVersion?.version);
options._metadata = options._metadata || {};
options._metadata.sdk = options._metadata.sdk || defaultSdkInfo;
// Only allow IP inferral by Relay if sendDefaultPii is true
if (options._metadata?.sdk) {
options._metadata.sdk.settings = {
infer_ip: options.sendDefaultPii ? 'auto' : 'never',
...options._metadata.sdk.settings,
};
}
// We default this to true, as it is the safer scenario
options.parentSpanIsAlwaysRootSpan =
options.parentSpanIsAlwaysRootSpan === undefined ? true : options.parentSpanIsAlwaysRootSpan;
super(options);
this._outcomesBuffer = [];
if (options.sendDefaultPii === true) {
this.on('beforeSendSession', addAutoIpAddressToSession);
}
if (options.enableLogs) {
this.on('flush', () => {
_INTERNAL_flushLogsBuffer(this);
});
this.on('afterCaptureLog', () => {
if (this._logFlushIdleTimeout) {
clearTimeout(this._logFlushIdleTimeout);
}
this._logFlushIdleTimeout = setTimeout(() => {
_INTERNAL_flushLogsBuffer(this);
}, DEFAULT_FLUSH_INTERVAL);
});
}
}
/**
* @inheritDoc
*/
public eventFromException(exception: unknown, hint: EventHint = {}): PromiseLike<Event> {
return eventFromException(this._options.stackParser, exception, hint, this._options.attachStacktrace);
}
/**
* @inheritDoc
*/
public eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike<Event> {
return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace);
}
/**
* If native client is available it will trigger a native crash.
* Use this only for testing purposes.
*/
public nativeCrash(): void {
NATIVE.nativeCrash();
}
/**
* @inheritDoc
*/
public close(): PromiseLike<boolean> {
// As super.close() flushes queued events, we wait for that to finish before closing the native SDK.
return super.close().then((result: boolean) => {
return NATIVE.closeNativeSdk().then(() => result) as PromiseLike<boolean>;
});
}
/**
* Sends user feedback to Sentry.
* @deprecated Use `Sentry.captureFeedback` instead.
*/
public captureUserFeedback(feedback: UserFeedback): void {
const envelope = createUserFeedbackEnvelope(feedback, {
metadata: this._options._metadata,
dsn: this.getDsn(),
tunnel: undefined,
});
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.sendEnvelope(envelope);
}
/**
* @inheritdoc
*/
public sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse> {
const outcomes = this._clearOutcomes();
this._outcomesBuffer = mergeOutcomes(this._outcomesBuffer, outcomes);
if (this._options.sendClientReports) {
this._attachClientReportTo(this._outcomesBuffer, envelope as ClientReportEnvelope);
}
let shouldClearOutcomesBuffer = true;
if (this._isEnabled() && this._transport && this._dsn) {
this.emit('beforeEnvelope', envelope);
this._transport.send(envelope).then(null, reason => {
if (reason instanceof SentryError) {
// SentryError is thrown by SyncPromise
shouldClearOutcomesBuffer = false;
// If this is called asynchronously we want the _outcomesBuffer to be cleared
debug.error('SentryError while sending event, keeping outcomes buffer:', reason);
} else {
debug.error('Error while sending event:', reason);
}
});
} else {
debug.error('Transport disabled');
}
if (shouldClearOutcomesBuffer) {
this._outcomesBuffer = []; // if send fails synchronously the _outcomesBuffer will stay intact
}
return Promise.resolve({});
}
/**
* @inheritDoc
*/
public init(): void {
super.init();
this._initNativeSdk();
}
/**
* Register a hook on this client.
*
* (Generic method signature to allow for custom React Native Client events.)
*/
public on(hook: string, callback: unknown): () => void {
// @ts-expect-error on from the base class doesn't support generic types
return super.on(hook, callback);
}
/**
* Emit a hook that was previously registered via `on()`.
*
* (Generic method signature to allow for custom React Native Client events.)
*/
public emit(hook: string, ...rest: unknown[]): void {
// @ts-expect-error emit from the base class doesn't support generic types
super.emit(hook, ...rest);
}
/**
* Starts native client with dsn and options
*/
private _initNativeSdk(): void {
NATIVE.initNativeSdk({
...this._options,
defaultSidecarUrl: getDefaultSidecarUrl(),
devServerUrl: getDevServer()?.url || '',
mobileReplayOptions:
this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] &&
'options' in this._integrations[MOBILE_REPLAY_INTEGRATION_NAME]
? (this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] as ReturnType<typeof mobileReplayIntegration>).options
: undefined,
})
.then(
(result: boolean) => {
return result;
},
() => {
this._showCannotConnectDialog();
return false;
},
)
.then((didCallNativeInit: boolean) => {
this._options.onReady?.({ didCallNativeInit });
this.emit('afterInit');
})
.then(undefined, error => {
debug.error('The OnReady callback threw an error: ', error);
});
}
/**
* If the user is in development mode, and the native nagger is enabled then it will show an alert.
*/
private _showCannotConnectDialog(): void {
if (__DEV__ && this._options.enableNativeNagger) {
Alert.alert(
'Sentry',
'Warning, could not connect to Sentry native SDK.\nIf you do not want to use the native component please pass `enableNative: false` in the options.\nVisit: https://docs.sentry.io/platforms/react-native/ for more details.',
);
}
}
/**
* Attaches a client report from outcomes to the envelope.
*/
private _attachClientReportTo(outcomes: Outcome[], envelope: ClientReportEnvelope): void {
if (outcomes.length > 0) {
const clientReportItem: ClientReportItem = [
{ type: 'client_report' },
{
timestamp: dateTimestampInSeconds(),
discarded_events: outcomes,
},
];
envelope[items].push(clientReportItem);
}
}
}