Skip to content

Commit 42e442f

Browse files
authored
FIS error handling (#911)
1. Mark PersistedInstallationEntry status UNREGISTERED incase of 500 errors 2. Store detailed 4xx server exceptions in the local storage. In the succeeding getId calls,return these exceptions to the clients
1 parent 8e65eae commit 42e442f

File tree

8 files changed

+119
-39
lines changed

8 files changed

+119
-39
lines changed

firebase-installations/api.txt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ package com.google.firebase.installations.local {
4545
method @Nullable public abstract String getAuthToken();
4646
method public abstract long getExpiresInSecs();
4747
method @Nullable public abstract String getFirebaseInstallationId();
48+
method @Nullable public abstract String getFisError();
4849
method @Nullable public abstract String getRefreshToken();
4950
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus getRegistrationStatus();
5051
method public abstract long getTokenCreationEpochInSecs();
@@ -61,6 +62,7 @@ package com.google.firebase.installations.local {
6162
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setAuthToken(@Nullable String);
6263
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setExpiresInSecs(long);
6364
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setFirebaseInstallationId(@NonNull String);
65+
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setFisError(@Nullable String);
6466
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setRefreshToken(@Nullable String);
6567
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setRegistrationStatus(@NonNull com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus);
6668
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setTokenCreationEpochInSecs(long);
@@ -80,10 +82,11 @@ package com.google.firebase.installations.remote {
8082
public abstract class InstallationResponse {
8183
ctor public InstallationResponse();
8284
method @NonNull public static com.google.firebase.installations.remote.InstallationResponse.Builder builder();
83-
method @NonNull public abstract InstallationTokenResult getAuthToken();
84-
method @NonNull public abstract String getFid();
85-
method @NonNull public abstract String getRefreshToken();
86-
method @NonNull public abstract String getUri();
85+
method @Nullable public abstract InstallationTokenResult getAuthToken();
86+
method @Nullable public abstract String getFid();
87+
method @Nullable public abstract String getRefreshToken();
88+
method @Nullable public abstract com.google.firebase.installations.remote.InstallationResponse.ResponseCode getResponseCode();
89+
method @Nullable public abstract String getUri();
8790
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder toBuilder();
8891
}
8992

@@ -93,8 +96,14 @@ package com.google.firebase.installations.remote {
9396
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setAuthToken(@NonNull InstallationTokenResult);
9497
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setFid(@NonNull String);
9598
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setRefreshToken(@NonNull String);
99+
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setResponseCode(@NonNull com.google.firebase.installations.remote.InstallationResponse.ResponseCode);
96100
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setUri(@NonNull String);
97101
}
98102

103+
public enum InstallationResponse.ResponseCode {
104+
enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode OK;
105+
enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode SERVER_ERROR;
106+
}
107+
99108
}
100109

firebase-installations/src/androidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.truth.Truth.assertWithMessage;
1818
import static com.google.firebase.installations.FisAndroidTestConstants.DEFAULT_PERSISTED_INSTALLATION_ENTRY;
1919
import static com.google.firebase.installations.FisAndroidTestConstants.INVALID_TEST_FID;
20+
import static com.google.firebase.installations.FisAndroidTestConstants.SERVER_ERROR_INSTALLATION_RESPONSE;
2021
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_API_KEY;
2122
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_APP_ID_1;
2223
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN;
@@ -291,6 +292,32 @@ public void testGetId_PersistedInstallationOk_BackendError() throws Exception {
291292
assertThat(updatedInstallationEntry).hasRegistrationStatus(RegistrationStatus.REGISTER_ERROR);
292293
}
293294

295+
@Test
296+
public void testGetId_ServerError_UnregisteredFID() throws Exception {
297+
// Mocking server error on FIS createFirebaseInstallation, returns empty InstallationResponse
298+
when(backendClientReturnsOk.createFirebaseInstallation(
299+
anyString(), anyString(), anyString(), anyString()))
300+
.thenReturn(SERVER_ERROR_INSTALLATION_RESPONSE);
301+
302+
FirebaseInstallations firebaseInstallations =
303+
new FirebaseInstallations(
304+
executor, firebaseApp, backendClientReturnsOk, persistedInstallation, mockUtils);
305+
306+
Tasks.await(firebaseInstallations.getId());
307+
308+
PersistedInstallationEntry entryValue =
309+
persistedInstallation.readPersistedInstallationEntryValue();
310+
assertThat(entryValue).hasFid(TEST_FID_1);
311+
312+
// Waiting for Task that registers FID on the FIS Servers
313+
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
314+
315+
PersistedInstallationEntry updatedInstallationEntry =
316+
persistedInstallation.readPersistedInstallationEntryValue();
317+
assertThat(updatedInstallationEntry).hasFid(TEST_FID_1);
318+
assertThat(updatedInstallationEntry).hasRegistrationStatus(RegistrationStatus.UNREGISTERED);
319+
}
320+
294321
@Test
295322
public void testGetId_PersistedInstallationError_BackendOk() throws InterruptedException {
296323
FirebaseInstallations firebaseInstallations =

firebase-installations/src/androidTest/java/com/google/firebase/installations/FisAndroidTestConstants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.firebase.installations.local.PersistedInstallationEntry;
1818
import com.google.firebase.installations.remote.InstallationResponse;
19+
import com.google.firebase.installations.remote.InstallationResponse.ResponseCode;
1920

2021
public final class FisAndroidTestConstants {
2122
public static final String TEST_FID_1 = "cccccccccccccccccccccc";
@@ -55,6 +56,7 @@ public final class FisAndroidTestConstants {
5556
.setTokenExpirationTimestamp(TEST_TOKEN_EXPIRATION_TIMESTAMP)
5657
.setTokenCreationTimestamp(TEST_CREATION_TIMESTAMP_1)
5758
.build())
59+
.setResponseCode(ResponseCode.OK)
5860
.build();
5961

6062
public static final InstallationTokenResult TEST_INSTALLATION_TOKEN_RESULT =
@@ -63,4 +65,7 @@ public final class FisAndroidTestConstants {
6365
.setTokenExpirationTimestamp(TEST_TOKEN_EXPIRATION_TIMESTAMP)
6466
.setTokenCreationTimestamp(TEST_CREATION_TIMESTAMP_1)
6567
.build();
68+
69+
public static final InstallationResponse SERVER_ERROR_INSTALLATION_RESPONSE =
70+
InstallationResponse.builder().setResponseCode(ResponseCode.SERVER_ERROR).build();
6671
}

firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.firebase.installations.local.PersistedInstallationEntry;
3030
import com.google.firebase.installations.remote.FirebaseInstallationServiceClient;
3131
import com.google.firebase.installations.remote.InstallationResponse;
32+
import com.google.firebase.installations.remote.InstallationResponse.ResponseCode;
3233
import java.util.ArrayList;
3334
import java.util.Iterator;
3435
import java.util.List;
@@ -218,12 +219,18 @@ private final void doRegistration() {
218219
persistedInstallation.readPersistedInstallationEntryValue();
219220

220221
// New FID needs to be created
221-
if (persistedInstallationEntry.isErrored() || persistedInstallationEntry.isNotGenerated()) {
222+
if (persistedInstallationEntry.isNotGenerated()) {
222223
String fid = utils.createRandomFid();
223224
persistFid(fid);
224225
persistedInstallationEntry = persistedInstallation.readPersistedInstallationEntryValue();
225226
}
226227

228+
if (persistedInstallationEntry.isErrored()) {
229+
throw new FirebaseInstallationsException(
230+
persistedInstallationEntry.getFisError(),
231+
FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR);
232+
}
233+
227234
triggerOnStateReached(persistedInstallationEntry);
228235

229236
// FID needs to be registered
@@ -262,6 +269,7 @@ private final void doRegistration() {
262269
PersistedInstallationEntry errorInstallationEntry =
263270
persistedInstallationEntry
264271
.toBuilder()
272+
.setFisError(e.getMessage())
265273
.setRegistrationStatus(RegistrationStatus.REGISTER_ERROR)
266274
.build();
267275
persistedInstallation.insertOrUpdatePersistedInstallationEntry(errorInstallationEntry);
@@ -296,15 +304,17 @@ private Void registerAndSaveFid(PersistedInstallationEntry persistedInstallation
296304
/*fid= */ persistedInstallationEntry.getFirebaseInstallationId(),
297305
/*projectID= */ firebaseApp.getOptions().getProjectId(),
298306
/*appId= */ getApplicationId());
299-
persistedInstallation.insertOrUpdatePersistedInstallationEntry(
300-
PersistedInstallationEntry.builder()
301-
.setFirebaseInstallationId(installationResponse.getFid())
302-
.setRegistrationStatus(RegistrationStatus.REGISTERED)
303-
.setAuthToken(installationResponse.getAuthToken().getToken())
304-
.setRefreshToken(installationResponse.getRefreshToken())
305-
.setExpiresInSecs(installationResponse.getAuthToken().getTokenExpirationTimestamp())
306-
.setTokenCreationEpochInSecs(creationTime)
307-
.build());
307+
if (installationResponse.getResponseCode() == ResponseCode.OK) {
308+
persistedInstallation.insertOrUpdatePersistedInstallationEntry(
309+
PersistedInstallationEntry.builder()
310+
.setFirebaseInstallationId(installationResponse.getFid())
311+
.setRegistrationStatus(RegistrationStatus.REGISTERED)
312+
.setAuthToken(installationResponse.getAuthToken().getToken())
313+
.setRefreshToken(installationResponse.getRefreshToken())
314+
.setExpiresInSecs(installationResponse.getAuthToken().getTokenExpirationTimestamp())
315+
.setTokenCreationEpochInSecs(creationTime)
316+
.build());
317+
}
308318

309319
} catch (FirebaseException exception) {
310320
throw new FirebaseInstallationsException(

firebase-installations/src/main/java/com/google/firebase/installations/Utils.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ class Utils {
5353
* #AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS}.
5454
*/
5555
public boolean isAuthTokenExpired(PersistedInstallationEntry persistedInstallationEntry) {
56-
return persistedInstallationEntry.getTokenCreationEpochInSecs()
57-
+ persistedInstallationEntry.getExpiresInSecs()
58-
> currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS;
56+
return persistedInstallationEntry.isRegistered()
57+
&& persistedInstallationEntry.getTokenCreationEpochInSecs()
58+
+ persistedInstallationEntry.getExpiresInSecs()
59+
> currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS;
5960
}
6061

6162
/** Returns current time in seconds. */

firebase-installations/src/main/java/com/google/firebase/installations/local/PersistedInstallationEntry.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public abstract class PersistedInstallationEntry {
4141

4242
public abstract long getTokenCreationEpochInSecs();
4343

44+
@Nullable
45+
public abstract String getFisError();
46+
4447
public boolean isRegistered() {
4548
return getRegistrationStatus() == PersistedInstallation.RegistrationStatus.REGISTERED;
4649
}
@@ -90,6 +93,9 @@ public abstract Builder setRegistrationStatus(
9093
@NonNull
9194
public abstract Builder setTokenCreationEpochInSecs(long value);
9295

96+
@NonNull
97+
public abstract Builder setFisError(@Nullable String value);
98+
9399
@NonNull
94100
public abstract PersistedInstallationEntry build();
95101
}

firebase-installations/src/main/java/com/google/firebase/installations/remote/FirebaseInstallationServiceClient.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.android.gms.common.util.VisibleForTesting;
2828
import com.google.firebase.FirebaseException;
2929
import com.google.firebase.installations.InstallationTokenResult;
30+
import com.google.firebase.installations.remote.InstallationResponse.ResponseCode;
3031
import java.io.BufferedReader;
3132
import java.io.IOException;
3233
import java.io.InputStreamReader;
@@ -118,17 +119,21 @@ public InstallationResponse createFirebaseInstallation(
118119
}
119120

120121
int httpResponseCode = httpsURLConnection.getResponseCode();
121-
switch (httpResponseCode) {
122-
case 200:
123-
return readCreateResponse(httpsURLConnection);
124-
case 500:
125-
retryCount++;
126-
break;
127-
default:
128-
throw new FirebaseException(readErrorResponse(httpsURLConnection));
122+
123+
if (httpResponseCode == 200) {
124+
return readCreateResponse(httpsURLConnection);
125+
}
126+
// Usually the FIS server recovers from errors: retry one time before giving up.
127+
if (httpResponseCode >= 500 && httpResponseCode < 600) {
128+
retryCount++;
129+
continue;
129130
}
131+
132+
// Unrecoverable server response or unknown error
133+
throw new FirebaseException(readErrorResponse(httpsURLConnection));
130134
}
131-
throw new FirebaseException(INTERNAL_SERVER_ERROR_MESSAGE);
135+
// Return empty installation response with SERVER_ERROR response code after max retries
136+
return InstallationResponse.builder().setResponseCode(ResponseCode.SERVER_ERROR).build();
132137
} catch (IOException e) {
133138
throw new FirebaseException(String.format(NETWORK_ERROR_MESSAGE, e.getMessage()));
134139
}
@@ -219,15 +224,18 @@ public InstallationTokenResult generateAuthToken(
219224
httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken);
220225

221226
int httpResponseCode = httpsURLConnection.getResponseCode();
222-
switch (httpResponseCode) {
223-
case 200:
224-
return readGenerateAuthTokenResponse(httpsURLConnection);
225-
case 500:
226-
retryCount++;
227-
break;
228-
default:
229-
throw new FirebaseException(readErrorResponse(httpsURLConnection));
227+
228+
if (httpResponseCode == 200) {
229+
return readGenerateAuthTokenResponse(httpsURLConnection);
230+
}
231+
// Usually the FIS server recovers from errors: retry one time before giving up.
232+
if (httpResponseCode >= 500 && httpResponseCode < 600) {
233+
retryCount++;
234+
continue;
230235
}
236+
237+
// Unrecoverable server response or unknown error
238+
throw new FirebaseException(readErrorResponse(httpsURLConnection));
231239
}
232240
throw new FirebaseException(INTERNAL_SERVER_ERROR_MESSAGE);
233241
} catch (IOException e) {
@@ -283,7 +291,7 @@ private InstallationResponse readCreateResponse(HttpsURLConnection conn) throws
283291
}
284292
reader.endObject();
285293

286-
return builder.build();
294+
return builder.setResponseCode(ResponseCode.OK).build();
287295
}
288296

289297
// Read the response from the generateAuthToken FirebaseInstallation API.

firebase-installations/src/main/java/com/google/firebase/installations/remote/InstallationResponse.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,35 @@
1515
package com.google.firebase.installations.remote;
1616

1717
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
1819
import com.google.auto.value.AutoValue;
1920
import com.google.firebase.installations.InstallationTokenResult;
2021

2122
@AutoValue
2223
public abstract class InstallationResponse {
2324

24-
@NonNull
25+
public enum ResponseCode {
26+
// Returned on success
27+
OK,
28+
// An error occurred on the server while processing this request(temporary)
29+
SERVER_ERROR
30+
}
31+
32+
@Nullable
2533
public abstract String getUri();
2634

27-
@NonNull
35+
@Nullable
2836
public abstract String getFid();
2937

30-
@NonNull
38+
@Nullable
3139
public abstract String getRefreshToken();
3240

33-
@NonNull
41+
@Nullable
3442
public abstract InstallationTokenResult getAuthToken();
3543

44+
@Nullable
45+
public abstract ResponseCode getResponseCode();
46+
3647
@NonNull
3748
public abstract Builder toBuilder();
3849

@@ -56,6 +67,9 @@ public abstract static class Builder {
5667
@NonNull
5768
public abstract Builder setAuthToken(@NonNull InstallationTokenResult value);
5869

70+
@NonNull
71+
public abstract Builder setResponseCode(@NonNull ResponseCode value);
72+
5973
@NonNull
6074
public abstract InstallationResponse build();
6175
}

0 commit comments

Comments
 (0)