Skip to content

Commit 1ef2044

Browse files
authored
fix(fdc): Don't throw when FirebaseAuth is unable to get an ID Token (#16711)
1 parent c0fa83f commit 1ef2044

File tree

2 files changed

+38
-23
lines changed

2 files changed

+38
-23
lines changed

packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ abstract class OperationRef<Data, Variables> {
4848

4949
Future<OperationResult<Data, Variables>> execute();
5050
Future<bool> _shouldRetry() async {
51-
String? newToken =
52-
await this.dataConnect.auth?.currentUser?.getIdToken(false);
51+
String? newToken;
52+
try {
53+
newToken = await this.dataConnect.auth?.currentUser?.getIdToken(false);
54+
} catch (e) {
55+
// Don't retry if there was an issue getting the ID Token.
56+
log("There was an error attempting to retrieve the ID Token: ${e.toString()}");
57+
}
5358
bool shouldRetry = newToken != null && _lastToken != newToken;
5459
_lastToken = newToken;
5560
return shouldRetry;
@@ -97,12 +102,14 @@ class QueryManager {
97102
return;
98103
}
99104
StreamController stream = trackedQueries[operationName]![key]!;
100-
// TODO(mtewani): Prevent this from getting called multiple times.
101-
stream.onCancel = () => stream.close();
102-
if (error != null) {
103-
stream.addError(error);
104-
} else {
105-
stream.add(QueryResult<Data, Variables>(dataConnect, data as Data, ref));
105+
106+
if (!stream.isClosed) {
107+
if (error != null) {
108+
stream.addError(error);
109+
} else {
110+
stream
111+
.add(QueryResult<Data, Variables>(dataConnect, data as Data, ref));
112+
}
106113
}
107114
}
108115
}

packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.dart

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,7 @@ class MockDataConnectTransport extends Mock implements DataConnectTransport {}
3232

3333
class MockFirebaseDataConnect extends Mock implements FirebaseDataConnect {}
3434

35-
class DCMockUser extends Mock implements User {
36-
int count = 0;
37-
List<String?> tokens = ['invalid-token', 'valid-token'];
38-
@override
39-
Future<String?> getIdToken([bool forceRefresh = false]) {
40-
// First return an invalid token, then return a valid token
41-
return Future.value(tokens[count++]);
42-
}
43-
}
44-
45-
class MockFirebaseAuth extends Mock implements FirebaseAuth {
46-
@override
47-
User? get currentUser => DCMockUser();
48-
}
35+
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
4936

5037
class MockQueryManager extends Mock implements QueryManager {}
5138

@@ -122,10 +109,15 @@ void main() {
122109
late Serializer<String> serializer;
123110
late MockClient mockHttpClient;
124111
late Deserializer<String> deserializer;
112+
late MockFirebaseAuth auth;
113+
late MockUser mockUser;
125114

126115
setUp(() {
127116
mockDataConnect = MockFirebaseDataConnect();
128-
when(mockDataConnect.auth).thenReturn(MockFirebaseAuth());
117+
auth = MockFirebaseAuth();
118+
mockUser = MockUser();
119+
when(mockDataConnect.auth).thenReturn(auth);
120+
when(auth.currentUser).thenReturn(mockUser);
129121
mockHttpClient = MockClient();
130122
transport = RestTransport(
131123
TransportOptions('testhost', 443, true),
@@ -142,15 +134,31 @@ void main() {
142134
transport.setHttp(mockHttpClient);
143135
mockDataConnect.transport = transport;
144136
});
137+
test('executeQuery should gracefully handle getIdToken failures', () async {
138+
final deserializer = (String data) => 'Deserialized Data';
139+
final mockResponseSuccess = http.Response('{"success": true}', 200);
140+
when(mockUser.getIdToken()).thenThrow(Exception('Auth error'));
141+
QueryRef ref = QueryRef(mockDataConnect, 'operation', transport,
142+
deserializer, QueryManager(mockDataConnect), emptySerializer, null);
143+
when(mockHttpClient.post(any,
144+
headers: anyNamed('headers'), body: anyNamed('body')))
145+
.thenAnswer((_) async => mockResponseSuccess);
146+
await ref.execute();
147+
});
145148
test(
146149
'query should forceRefresh on ID token if the first request is unauthorized',
147150
() async {
148151
final mockResponse = http.Response('{"error": "Unauthorized"}', 401);
149152
final mockResponseSuccess = http.Response('{"success": true}', 200);
150153
final deserializer = (String data) => 'Deserialized Data';
151154
int count = 0;
155+
int idTokenCount = 0;
152156
QueryRef ref = QueryRef(mockDataConnect, 'operation', transport,
153157
deserializer, QueryManager(mockDataConnect), emptySerializer, null);
158+
when(mockUser.getIdToken()).thenAnswer((invocation) => [
159+
Future.value('invalid-token'),
160+
Future.value('valid-token')
161+
][idTokenCount++]);
154162

155163
when(mockHttpClient.post(any,
156164
headers: anyNamed('headers'), body: anyNamed('body')))

0 commit comments

Comments
 (0)