tunneled_transport.dart 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import 'dart:convert';
  2. import 'package:http/http.dart';
  3. import 'package:sentry/sentry.dart';
  4. /// A transport is in charge of sending the event to the Sentry server.
  5. class TunneledTransport implements Transport {
  6. final Uri _tunnel;
  7. final SentryOptions _options;
  8. final Dsn? _dsn;
  9. _CredentialBuilder? _credentialBuilder;
  10. final Map<String, String> _headers;
  11. factory TunneledTransport(Uri tunnel, SentryOptions options) {
  12. return TunneledTransport._(tunnel, options);
  13. }
  14. TunneledTransport._(this._tunnel, this._options)
  15. : _dsn = _options.dsn != null ? Dsn.parse(_options.dsn!) : null,
  16. _headers = _buildHeaders(
  17. _options.platformChecker.isWeb,
  18. _options.sdk.identifier,
  19. ) {
  20. _credentialBuilder = _CredentialBuilder(
  21. _dsn,
  22. _options.sdk.identifier,
  23. _options.clock,
  24. );
  25. }
  26. @override
  27. Future<SentryId?> send(SentryEnvelope envelope) async {
  28. final streamedRequest = await _createStreamedRequest(envelope);
  29. final response = await _options.httpClient
  30. .send(streamedRequest)
  31. .then(Response.fromStream);
  32. if (response.statusCode != 200) {
  33. // body guard to not log the error as it has performance impact to allocate
  34. // the body String.
  35. if (_options.debug) {
  36. _options.logger(
  37. SentryLevel.error,
  38. 'API returned an error, statusCode = ${response.statusCode}, '
  39. 'body = ${response.body}',
  40. );
  41. }
  42. return const SentryId.empty();
  43. } else {
  44. _options.logger(
  45. SentryLevel.debug,
  46. 'Envelope ${envelope.header.eventId ?? "--"} was sent successfully.',
  47. );
  48. }
  49. final eventId = json.decode(response.body)['id'];
  50. if (eventId == null) {
  51. return null;
  52. }
  53. return SentryId.fromId(eventId);
  54. }
  55. Future<StreamedRequest> _createStreamedRequest(
  56. SentryEnvelope envelope,
  57. ) async {
  58. final streamedRequest = StreamedRequest('POST', _tunnel);
  59. envelope
  60. .envelopeStream(_options)
  61. .listen(streamedRequest.sink.add)
  62. .onDone(streamedRequest.sink.close);
  63. streamedRequest.headers.addAll(_credentialBuilder!.configure(_headers));
  64. return streamedRequest;
  65. }
  66. }
  67. class _CredentialBuilder {
  68. final String _authHeader;
  69. final ClockProvider _clock;
  70. int get timestamp => _clock().millisecondsSinceEpoch;
  71. _CredentialBuilder._(String authHeader, ClockProvider clock)
  72. : _authHeader = authHeader,
  73. _clock = clock;
  74. factory _CredentialBuilder(
  75. Dsn? dsn,
  76. String sdkIdentifier,
  77. ClockProvider clock,
  78. ) {
  79. final authHeader = _buildAuthHeader(
  80. publicKey: dsn?.publicKey,
  81. secretKey: dsn?.secretKey,
  82. sdkIdentifier: sdkIdentifier,
  83. );
  84. return _CredentialBuilder._(authHeader, clock);
  85. }
  86. static String _buildAuthHeader({
  87. String? publicKey,
  88. String? secretKey,
  89. String? sdkIdentifier,
  90. }) {
  91. var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, '
  92. 'sentry_key=$publicKey';
  93. if (secretKey != null) {
  94. header += ', sentry_secret=$secretKey';
  95. }
  96. return header;
  97. }
  98. Map<String, String> configure(Map<String, String> headers) {
  99. return headers
  100. ..addAll(
  101. <String, String>{
  102. 'X-Sentry-Auth': '$_authHeader, sentry_timestamp=$timestamp'
  103. },
  104. );
  105. }
  106. }
  107. Map<String, String> _buildHeaders(bool isWeb, String sdkIdentifier) {
  108. final headers = {'Content-Type': 'application/x-sentry-envelope'};
  109. // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why
  110. // for web it use browser user agent
  111. if (!isWeb) {
  112. headers['User-Agent'] = sdkIdentifier;
  113. }
  114. return headers;
  115. }