Refresh credentials once (#73)
* Made sure refreshCredentials is only running once when multiple calls are made
* Added unit test
* Added line to changelog
* Updated version in pubspec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f2809d..21b211c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.6.1
+
+* Added fix to make sure that credentials are only refreshed once when multiple calls are made.
+
# 1.6.0
* Added PKCE support to `AuthorizationCodeGrant`.
diff --git a/lib/src/client.dart b/lib/src/client.dart
index d33aaa0..7a78aa3 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -135,6 +135,9 @@
params['error_uri'] == null ? null : Uri.parse(params['error_uri']));
}
+ /// A [Future] used to track whether [refreshCredentials] is running.
+ Future<Credentials> _refreshingFuture;
+
/// Explicitly refreshes this client's credentials. Returns this client.
///
/// This will throw a [StateError] if the [Credentials] can't be refreshed, an
@@ -151,14 +154,26 @@
throw StateError("$prefix can't be refreshed.");
}
- _credentials = await credentials.refresh(
- identifier: identifier,
- secret: secret,
- newScopes: newScopes,
- basicAuth: _basicAuth,
- httpClient: _httpClient);
-
- if (_onCredentialsRefreshed != null) _onCredentialsRefreshed(_credentials);
+ // To make sure that only one refresh happens when credentials are expired
+ // we track it using the [_refreshingFuture]. And also make sure that the
+ // _onCredentialsRefreshed callback is only called once.
+ if (_refreshingFuture == null) {
+ try {
+ _refreshingFuture = credentials.refresh(
+ identifier: identifier,
+ secret: secret,
+ newScopes: newScopes,
+ basicAuth: _basicAuth,
+ httpClient: _httpClient,
+ );
+ _credentials = await _refreshingFuture;
+ _onCredentialsRefreshed?.call(_credentials);
+ } finally {
+ _refreshingFuture = null;
+ }
+ } else {
+ await _refreshingFuture;
+ }
return this;
}
diff --git a/pubspec.yaml b/pubspec.yaml
index db168e3..4648628 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: oauth2
-version: 1.6.0
+version: 1.6.1
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/oauth2
description: >-
diff --git a/test/client_test.dart b/test/client_test.dart
index 11b725c..5960cbd 100644
--- a/test/client_test.dart
+++ b/test/client_test.dart
@@ -65,6 +65,46 @@
expect(client.credentials.accessToken, equals('new access token'));
});
+ test(
+ 'that can be refreshed refreshes only once if multiple requests are made',
+ () async {
+ var expiration = DateTime.now().subtract(Duration(hours: 1));
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ expiration: expiration);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ final numCalls = 2;
+
+ for (var i = 0; i < numCalls; i++) {
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'],
+ equals('Bearer new access token'));
+
+ return Future.value(http.Response('good job', 200));
+ });
+ }
+
+ await Future.wait(
+ List<Future>.generate(numCalls, (_) => client.read(requestUri)));
+
+ expect(client.credentials.accessToken, equals('new access token'));
+ });
+
test('that onCredentialsRefreshed is called', () async {
var callbackCalled = false;