Skip to content

Commit 1b9b7fb

Browse files
slightfootyjbanov
authored andcommitted
Support for new DSN format (without secretKey) and remove the quiver dependency. (#20)
* Make the secretKey optional for the new DSN format. * Remove the requirement for quiver. * Fixed test issue. Fixed dartfmt issues. * Code review modifications. * dartfmt
1 parent 5715d15 commit 1b9b7fb

File tree

4 files changed

+103
-32
lines changed

4 files changed

+103
-32
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
# Name/Organization <email address>
55

66
Google Inc.
7+
Simon Lightfoot <[email protected]>

lib/sentry.dart

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import 'dart:io';
1111

1212
import 'package:http/http.dart';
1313
import 'package:meta/meta.dart';
14-
import 'package:quiver/time.dart';
1514
import 'package:usage/uuid/uuid.dart';
1615

1716
import 'src/stack_trace.dart';
@@ -20,6 +19,9 @@ import 'src/version.dart';
2019

2120
export 'src/version.dart';
2221

22+
/// Used to provide timestamp for logging.
23+
typedef ClockProvider = DateTime Function();
24+
2325
/// Logs crash reports and events to the Sentry.io service.
2426
class SentryClient {
2527
/// Sentry.io client identifier for _this_ client.
@@ -46,7 +48,9 @@ class SentryClient {
4648
/// make HTTP calls to Sentry.io. This is useful in tests.
4749
///
4850
/// If [clock] is provided, it is used to get time instead of the system
49-
/// clock. This is useful in tests.
51+
/// clock. This is useful in tests. Should be an implementation of ClockProvider.
52+
/// This parameter is dynamic to maintain backwards compatibility with
53+
/// previous use of Clock from the Quiver library.
5054
///
5155
/// If [uuidGenerator] is provided, it is used to generate the "event_id"
5256
/// field instead of the built-in random UUID v4 generator. This is useful in
@@ -56,36 +60,35 @@ class SentryClient {
5660
Event environmentAttributes,
5761
bool compressPayload,
5862
Client httpClient,
59-
Clock clock,
63+
dynamic clock,
6064
UuidGenerator uuidGenerator,
6165
}) {
6266
httpClient ??= new Client();
63-
clock ??= const Clock(_getUtcDateTime);
67+
clock ??= _getUtcDateTime;
6468
uuidGenerator ??= _generateUuidV4WithoutDashes;
6569
compressPayload ??= true;
6670

71+
final ClockProvider clockProvider =
72+
clock is ClockProvider ? clock : clock.get;
73+
6774
final Uri uri = Uri.parse(dsn);
6875
final List<String> userInfo = uri.userInfo.split(':');
6976

7077
assert(() {
71-
if (userInfo.length != 2)
72-
throw new ArgumentError(
73-
'Colon-separated publicKey:secretKey pair not found in the user info field of the DSN URI: $dsn');
74-
7578
if (uri.pathSegments.isEmpty)
7679
throw new ArgumentError(
7780
'Project ID not found in the URI path of the DSN URI: $dsn');
7881

7982
return true;
8083
}());
8184

82-
final String publicKey = userInfo.first;
83-
final String secretKey = userInfo.last;
85+
final String publicKey = userInfo[0];
86+
final String secretKey = userInfo.length >= 2 ? userInfo[1] : null;
8487
final String projectId = uri.pathSegments.last;
8588

8689
return new SentryClient._(
8790
httpClient: httpClient,
88-
clock: clock,
91+
clock: clockProvider,
8992
uuidGenerator: uuidGenerator,
9093
environmentAttributes: environmentAttributes,
9194
dsnUri: uri,
@@ -98,20 +101,20 @@ class SentryClient {
98101

99102
SentryClient._({
100103
@required Client httpClient,
101-
@required Clock clock,
104+
@required ClockProvider clock,
102105
@required UuidGenerator uuidGenerator,
103106
@required this.environmentAttributes,
104107
@required this.dsnUri,
105108
@required this.publicKey,
106-
@required this.secretKey,
109+
this.secretKey,
107110
@required this.compressPayload,
108111
@required this.projectId,
109112
}) : _httpClient = httpClient,
110113
_clock = clock,
111114
_uuidGenerator = uuidGenerator;
112115

113116
final Client _httpClient;
114-
final Clock _clock;
117+
final ClockProvider _clock;
115118
final UuidGenerator _uuidGenerator;
116119

117120
/// Contains [Event] attributes that are automatically mixed into all events
@@ -161,21 +164,23 @@ class SentryClient {
161164

162165
/// Reports an [event] to Sentry.io.
163166
Future<SentryResponse> capture({@required Event event}) async {
164-
final DateTime now = _clock.now();
167+
final DateTime now = _clock();
168+
String authHeader = 'Sentry sentry_version=6, sentry_client=$sentryClient, '
169+
'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey';
170+
if (secretKey != null) {
171+
authHeader += ', sentry_secret=$secretKey';
172+
}
173+
165174
final Map<String, String> headers = <String, String>{
166175
'User-Agent': '$sentryClient',
167176
'Content-Type': 'application/json',
168-
'X-Sentry-Auth': 'Sentry sentry_version=6, '
169-
'sentry_client=$sentryClient, '
170-
'sentry_timestamp=${now.millisecondsSinceEpoch}, '
171-
'sentry_key=$publicKey, '
172-
'sentry_secret=$secretKey',
177+
'X-Sentry-Auth': authHeader,
173178
};
174179

175180
final Map<String, dynamic> data = <String, dynamic>{
176181
'project': projectId,
177182
'event_id': _uuidGenerator(),
178-
'timestamp': formatDateAsIso8601WithSecondPrecision(_clock.now()),
183+
'timestamp': formatDateAsIso8601WithSecondPrecision(now),
179184
'logger': defaultLoggerName,
180185
};
181186

pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ environment:
1010
dependencies:
1111
http: ">=0.11.0 <2.0.0"
1212
meta: ">=1.0.0 <2.0.0"
13-
quiver: ">=0.25.0 <2.0.0"
1413
stack_trace: ">=1.0.0 <2.0.0"
1514
usage: ">=3.0.0 <4.0.0"
1615

test/sentry_test.dart

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import 'dart:convert';
66
import 'dart:io';
77

88
import 'package:http/http.dart';
9-
import 'package:quiver/time.dart';
109
import 'package:sentry/sentry.dart';
1110
import 'package:test/test.dart';
1211

1312
const String _testDsn = 'https://public:[email protected]/1';
13+
const String _testDsnWithoutSecret = 'https://[email protected]/1';
1414

1515
void main() {
1616
group('$SentryClient', () {
@@ -24,9 +24,75 @@ void main() {
2424
await client.close();
2525
});
2626

27+
test('can parse DSN without secret', () async {
28+
final SentryClient client = new SentryClient(dsn: _testDsnWithoutSecret);
29+
expect(client.dsnUri, Uri.parse(_testDsnWithoutSecret));
30+
expect(client.postUri, 'https://sentry.example.com/api/1/store/');
31+
expect(client.publicKey, 'public');
32+
expect(client.secretKey, null);
33+
expect(client.projectId, '1');
34+
await client.close();
35+
});
36+
37+
test('sends client auth header without secret', () async {
38+
final MockClient httpMock = new MockClient();
39+
final ClockProvider fakeClockProvider =
40+
() => new DateTime.utc(2017, 1, 2);
41+
42+
Map<String, String> headers;
43+
44+
httpMock.answerWith((Invocation invocation) async {
45+
if (invocation.memberName == #close) {
46+
return null;
47+
}
48+
if (invocation.memberName == #post) {
49+
headers = invocation.namedArguments[#headers];
50+
return new Response('{"id": "test-event-id"}', 200);
51+
}
52+
fail('Unexpected invocation of ${invocation.memberName} in HttpMock');
53+
});
54+
55+
final SentryClient client = new SentryClient(
56+
dsn: _testDsnWithoutSecret,
57+
httpClient: httpMock,
58+
clock: fakeClockProvider,
59+
compressPayload: false,
60+
uuidGenerator: () => 'X' * 32,
61+
environmentAttributes: const Event(
62+
serverName: 'test.server.com',
63+
release: '1.2.3',
64+
environment: 'staging',
65+
),
66+
);
67+
68+
try {
69+
throw new ArgumentError('Test error');
70+
} catch (error, stackTrace) {
71+
final SentryResponse response = await client.captureException(
72+
exception: error, stackTrace: stackTrace);
73+
expect(response.isSuccessful, true);
74+
expect(response.eventId, 'test-event-id');
75+
expect(response.error, null);
76+
}
77+
78+
final Map<String, String> expectedHeaders = <String, String>{
79+
'User-Agent': '$sdkName/$sdkVersion',
80+
'Content-Type': 'application/json',
81+
'X-Sentry-Auth': 'Sentry sentry_version=6, '
82+
'sentry_client=${SentryClient.sentryClient}, '
83+
'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}, '
84+
'sentry_key=public',
85+
};
86+
87+
expect(headers, expectedHeaders);
88+
89+
await client.close();
90+
});
91+
2792
testCaptureException(bool compressPayload) async {
2893
final MockClient httpMock = new MockClient();
29-
final Clock fakeClock = new Clock.fixed(new DateTime.utc(2017, 1, 2));
94+
final ClockProvider fakeClockProvider =
95+
() => new DateTime.utc(2017, 1, 2);
3096

3197
String postUri;
3298
Map<String, String> headers;
@@ -47,7 +113,7 @@ void main() {
47113
final SentryClient client = new SentryClient(
48114
dsn: _testDsn,
49115
httpClient: httpMock,
50-
clock: fakeClock,
116+
clock: fakeClockProvider,
51117
uuidGenerator: () => 'X' * 32,
52118
compressPayload: compressPayload,
53119
environmentAttributes: const Event(
@@ -74,9 +140,7 @@ void main() {
74140
'Content-Type': 'application/json',
75141
'X-Sentry-Auth': 'Sentry sentry_version=6, '
76142
'sentry_client=${SentryClient.sentryClient}, '
77-
'sentry_timestamp=${fakeClock
78-
.now()
79-
.millisecondsSinceEpoch}, '
143+
'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}, '
80144
'sentry_key=public, '
81145
'sentry_secret=secret',
82146
};
@@ -133,7 +197,8 @@ void main() {
133197

134198
test('reads error message from the x-sentry-error header', () async {
135199
final MockClient httpMock = new MockClient();
136-
final Clock fakeClock = new Clock.fixed(new DateTime(2017, 1, 2));
200+
final ClockProvider fakeClockProvider =
201+
() => new DateTime.utc(2017, 1, 2);
137202

138203
httpMock.answerWith((Invocation invocation) async {
139204
if (invocation.memberName == #close) {
@@ -150,7 +215,7 @@ void main() {
150215
final SentryClient client = new SentryClient(
151216
dsn: _testDsn,
152217
httpClient: httpMock,
153-
clock: fakeClock,
218+
clock: fakeClockProvider,
154219
uuidGenerator: () => 'X' * 32,
155220
compressPayload: false,
156221
environmentAttributes: const Event(
@@ -176,7 +241,8 @@ void main() {
176241

177242
test('$Event userContext overrides client', () async {
178243
final MockClient httpMock = new MockClient();
179-
final Clock fakeClock = new Clock.fixed(new DateTime(2017, 1, 2));
244+
final ClockProvider fakeClockProvider =
245+
() => new DateTime.utc(2017, 1, 2);
180246

181247
String loggedUserId; // used to find out what user context was sent
182248
httpMock.answerWith((Invocation invocation) async {
@@ -211,7 +277,7 @@ void main() {
211277
final SentryClient client = new SentryClient(
212278
dsn: _testDsn,
213279
httpClient: httpMock,
214-
clock: fakeClock,
280+
clock: fakeClockProvider,
215281
uuidGenerator: () => 'X' * 32,
216282
compressPayload: false,
217283
environmentAttributes: const Event(

0 commit comments

Comments
 (0)