ente/lib/core/logging/tunneled_transport.dart

140 lines
3.6 KiB
Dart
Raw Normal View History

2022-11-14 08:28:09 +00:00
import 'dart:convert';
import 'package:http/http.dart';
import 'package:sentry/sentry.dart';
/// A transport is in charge of sending the event to the Sentry server.
class TunneledTransport implements Transport {
final Uri _tunnel;
final SentryOptions _options;
2023-04-08 11:56:28 +00:00
final Dsn? _dsn;
2022-11-14 08:28:09 +00:00
2023-04-08 11:56:28 +00:00
_CredentialBuilder? _credentialBuilder;
2022-11-14 08:28:09 +00:00
final Map<String, String> _headers;
factory TunneledTransport(Uri tunnel, SentryOptions options) {
return TunneledTransport._(tunnel, options);
}
TunneledTransport._(this._tunnel, this._options)
2023-04-08 11:56:28 +00:00
: _dsn = _options.dsn != null ? Dsn.parse(_options.dsn!) : null,
2022-11-14 08:28:09 +00:00
_headers = _buildHeaders(
_options.platformChecker.isWeb,
2023-08-18 14:33:33 +00:00
_options.sentryClientName,
2022-11-14 08:28:09 +00:00
) {
_credentialBuilder = _CredentialBuilder(
_dsn,
2023-08-18 14:33:33 +00:00
_options.sentryClientName,
2022-11-14 08:28:09 +00:00
_options.clock,
);
}
@override
2023-04-08 11:56:28 +00:00
Future<SentryId?> send(SentryEnvelope envelope) async {
2022-11-14 08:28:09 +00:00
final streamedRequest = await _createStreamedRequest(envelope);
final response = await _options.httpClient
.send(streamedRequest)
.then(Response.fromStream);
if (response.statusCode != 200) {
// body guard to not log the error as it has performance impact to allocate
// the body String.
if (_options.debug) {
_options.logger(
SentryLevel.error,
'API returned an error, statusCode = ${response.statusCode}, '
2023-04-08 11:56:28 +00:00
'body = ${response.body}',
2022-11-14 08:28:09 +00:00
);
}
return const SentryId.empty();
} else {
_options.logger(
SentryLevel.debug,
'Envelope ${envelope.header.eventId ?? "--"} was sent successfully.',
);
}
final eventId = json.decode(response.body)['id'];
if (eventId == null) {
return null;
}
return SentryId.fromId(eventId);
}
Future<StreamedRequest> _createStreamedRequest(
2023-04-08 11:56:28 +00:00
SentryEnvelope envelope,
) async {
2022-11-14 08:28:09 +00:00
final streamedRequest = StreamedRequest('POST', _tunnel);
envelope
.envelopeStream(_options)
.listen(streamedRequest.sink.add)
.onDone(streamedRequest.sink.close);
2023-04-08 11:56:28 +00:00
streamedRequest.headers.addAll(_credentialBuilder!.configure(_headers));
2022-11-14 08:28:09 +00:00
return streamedRequest;
}
}
class _CredentialBuilder {
final String _authHeader;
final ClockProvider _clock;
int get timestamp => _clock().millisecondsSinceEpoch;
_CredentialBuilder._(String authHeader, ClockProvider clock)
: _authHeader = authHeader,
_clock = clock;
factory _CredentialBuilder(
2023-04-08 11:56:28 +00:00
Dsn? dsn,
String sdkIdentifier,
ClockProvider clock,
) {
2022-11-14 08:28:09 +00:00
final authHeader = _buildAuthHeader(
2023-04-08 11:56:28 +00:00
publicKey: dsn?.publicKey,
secretKey: dsn?.secretKey,
2022-11-14 08:28:09 +00:00
sdkIdentifier: sdkIdentifier,
);
return _CredentialBuilder._(authHeader, clock);
}
static String _buildAuthHeader({
2023-04-08 11:56:28 +00:00
String? publicKey,
String? secretKey,
String? sdkIdentifier,
2022-11-14 08:28:09 +00:00
}) {
var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, '
'sentry_key=$publicKey';
if (secretKey != null) {
header += ', sentry_secret=$secretKey';
}
return header;
}
Map<String, String> configure(Map<String, String> headers) {
return headers
..addAll(
<String, String>{
'X-Sentry-Auth': '$_authHeader, sentry_timestamp=$timestamp'
},
);
}
}
Map<String, String> _buildHeaders(bool isWeb, String sdkIdentifier) {
final headers = {'Content-Type': 'application/x-sentry-envelope'};
// NOTE(lejard_h) overriding user agent on VM and Flutter not sure why
// for web it use browser user agent
if (!isWeb) {
headers['User-Agent'] = sdkIdentifier;
}
return headers;
}