Skip to content

Commit bffa8ed

Browse files
qdpham13daymxnmrobervkryachkoyifanyang
authored
Rc realtime dev (#3743)
* Creates Realtime Http client file. Includes ConfigUpdate Listener & Registration. Also exposes methods to be used by public RC file. * Creates Realtime Http client file. Includes ConfigUpdate Listener & Registration. Also exposes methods to be used by public RC file. * Update stream name * Add copyright info to top of file. * Update comments * Format file. * Creates Realtime Http client file. Includes ConfigUpdate Listener & Registration. Also exposes methods to be used by public RC file. * Update stream name * Add copyright info to top of file. * Update comments * Format file. * Moved ConfigUpdateListener and ConfigUpdateListenerRegistration to public level. * Moved ConfigUpdateListener and ConfigUpdateListenerRegistration to public level. * Format and update comments * Uodate api.txt * Add nonnull and nullable to params * Change registration to interface. * Add author * Update api.txt * Dependabot High severity alerts (#3734) * Dependency updates for `smoke-tests` * Do not attempt to parse empty json files in MetaDataStore (#3735) * Do not attempt to parse empty json files in MetaDataStore * Safely delete corrupt files in MetaDataStore * Disable javadoc for undocumented sdks. (#3738) * Creates Realtime Http client file. Includes ConfigUpdate Listener & Registration. Also exposes methods to be used by public RC file. * Reupdate files. * Make changes based on PR comments; Make internal ConfigUpdateListenerRegistration non-static to access private listener removal method from outer class. Add synchonization to listeners set. * Remove synchronized set and add explicit synchonized to methods. * Add http connection logic * Add autofetch logic * Add http stream connection and async autofetching. Also add template version number to cache. * Add message decoding * add unit tests and Realtime based exceptions * Update api.txt * Fix check failures * Format files * Update changes based on PR comments * Add back template version number check * Alter template version number comment to reflect changes * Revert template version number extraction * Update template version number comment * Add thread safe for autofetch listeners * Add thread safe for autofetch listeners * add stream unit tests and thread safe for http connection object * finish autofetch testing * Post release version updates (#3752) * Ignore appdistro-api since it's still in beta. (#3751) * Upgrade Gradle to 6.9 (#3744) * Migrate from deprecated api usage in Coverage script * Fix bugs preventing 6.9 upgrade * oops, that shouldn't have been there still * Fixed old usage of Gradle BOM support (or lack-there-of) * Fixed some minor gradle consistencies, and added firehorn to gitignore * Added distribution versioning back * Explicitly depend on appdistribution-api Co-authored-by: Yifan Yang <[email protected]> * Upgrade kotlin version to 1.6.20 (#3762) * Upgrade kotlin version to 1.6.20 * Upgrade dagger in protoc-gen-firebase-encoders. * Filter out only release component. * Add explanation. * Remove extra log Co-authored-by: Daymon <[email protected]> Co-authored-by: Matthew Robertson <[email protected]> Co-authored-by: Vladimir Kryachko <[email protected]> Co-authored-by: Yifan Yang <[email protected]>
1 parent 5a223a1 commit bffa8ed

File tree

10 files changed

+670
-10
lines changed

10 files changed

+670
-10
lines changed

firebase-config/api.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
package com.google.firebase.remoteconfig {
33

44
public interface ConfigUpdateListener {
5-
method public void onError(Exception);
5+
method public void onError(@NonNull Exception);
66
method public void onEvent();
77
}
88

@@ -65,6 +65,16 @@ package com.google.firebase.remoteconfig {
6565
method public int getLastFetchStatus();
6666
}
6767

68+
public class FirebaseRemoteConfigRealtimeUpdateFetchException extends com.google.firebase.remoteconfig.FirebaseRemoteConfigException {
69+
ctor public FirebaseRemoteConfigRealtimeUpdateFetchException(@NonNull String);
70+
ctor public FirebaseRemoteConfigRealtimeUpdateFetchException(@NonNull String, @Nullable Throwable);
71+
}
72+
73+
public class FirebaseRemoteConfigRealtimeUpdateStreamException extends com.google.firebase.remoteconfig.FirebaseRemoteConfigException {
74+
ctor public FirebaseRemoteConfigRealtimeUpdateStreamException(@NonNull String);
75+
ctor public FirebaseRemoteConfigRealtimeUpdateStreamException(@NonNull String, @Nullable Throwable);
76+
}
77+
6878
public class FirebaseRemoteConfigServerException extends com.google.firebase.remoteconfig.FirebaseRemoteConfigException {
6979
ctor public FirebaseRemoteConfigServerException(int, @NonNull String);
7080
ctor public FirebaseRemoteConfigServerException(int, @NonNull String, @Nullable Throwable);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
//
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.remoteconfig;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
20+
/**
21+
* A Firebase Remote Config exception caused by the auto-fetch component of Realtime config updates.
22+
*
23+
* @author Quan Pham
24+
*/
25+
public class FirebaseRemoteConfigRealtimeUpdateFetchException
26+
extends FirebaseRemoteConfigException {
27+
/** Creates a Firebase Remote Config Realtime fetch exception with the given message. */
28+
public FirebaseRemoteConfigRealtimeUpdateFetchException(@NonNull String detailMessage) {
29+
super(detailMessage);
30+
}
31+
32+
/** Creates a Firebase Remote Config Realtime fetch exception with the given message and cause. */
33+
public FirebaseRemoteConfigRealtimeUpdateFetchException(
34+
@NonNull String detailMessage, @Nullable Throwable cause) {
35+
super(detailMessage, cause);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
//
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.remoteconfig;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
20+
/**
21+
* A Firebase Remote Config exception caused by the stream component of Realtime config updates.
22+
*
23+
* @author Quan Pham
24+
*/
25+
public class FirebaseRemoteConfigRealtimeUpdateStreamException
26+
extends FirebaseRemoteConfigException {
27+
/** Creates a Firebase Remote Config Realtime stream exception with the given message. */
28+
public FirebaseRemoteConfigRealtimeUpdateStreamException(@NonNull String detailMessage) {
29+
super(detailMessage);
30+
}
31+
32+
/**
33+
* Creates a Firebase Remote Config Realtime stream exception with the given message and cause.
34+
*/
35+
public FirebaseRemoteConfigRealtimeUpdateStreamException(
36+
@NonNull String detailMessage, @Nullable Throwable cause) {
37+
super(detailMessage, cause);
38+
}
39+
}

firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigConstants.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
public final class RemoteConfigConstants {
2828
public static final String FETCH_REGEX_URL =
2929
"https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/%s:fetch";
30+
public static final String REALTIME_REGEX_URL =
31+
"https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/%s:streamFetchInvalidations";
3032

3133
/**
3234
* Keys of fields in the Fetch request body that the client sends to the Firebase Remote Config
@@ -73,14 +75,16 @@ public final class RemoteConfigConstants {
7375
ResponseFieldKey.ENTRIES,
7476
ResponseFieldKey.EXPERIMENT_DESCRIPTIONS,
7577
ResponseFieldKey.PERSONALIZATION_METADATA,
76-
ResponseFieldKey.STATE
78+
ResponseFieldKey.STATE,
79+
ResponseFieldKey.TEMPLATE_VERSION_NUMBER
7780
})
7881
@Retention(RetentionPolicy.SOURCE)
7982
public @interface ResponseFieldKey {
8083
String ENTRIES = "entries";
8184
String EXPERIMENT_DESCRIPTIONS = "experimentDescriptions";
8285
String PERSONALIZATION_METADATA = "personalizationMetadata";
8386
String STATE = "state";
87+
String TEMPLATE_VERSION_NUMBER = "templateVersion";
8488
}
8589

8690
/**
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
//
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.remoteconfig.internal;
16+
17+
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG;
18+
19+
import android.util.Log;
20+
import androidx.annotation.GuardedBy;
21+
import androidx.annotation.VisibleForTesting;
22+
import com.google.android.gms.tasks.Task;
23+
import com.google.android.gms.tasks.Tasks;
24+
import com.google.firebase.remoteconfig.ConfigUpdateListener;
25+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException;
26+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigRealtimeUpdateFetchException;
27+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigRealtimeUpdateStreamException;
28+
import java.io.BufferedReader;
29+
import java.io.IOException;
30+
import java.io.InputStream;
31+
import java.io.InputStreamReader;
32+
import java.net.HttpURLConnection;
33+
import java.util.Random;
34+
import java.util.Set;
35+
import java.util.concurrent.Executor;
36+
import java.util.concurrent.ScheduledExecutorService;
37+
import java.util.concurrent.TimeUnit;
38+
import org.json.JSONException;
39+
import org.json.JSONObject;
40+
41+
public class ConfigAutoFetch {
42+
43+
private static final int FETCH_RETRY = 3;
44+
45+
@GuardedBy("this")
46+
private final Set<ConfigUpdateListener> eventListeners;
47+
48+
@GuardedBy("this")
49+
private final HttpURLConnection httpURLConnection;
50+
51+
private final ConfigFetchHandler configFetchHandler;
52+
private final ConfigUpdateListener retryCallback;
53+
private final ScheduledExecutorService scheduledExecutorService;
54+
private final Random random;
55+
private final Executor executor;
56+
57+
public ConfigAutoFetch(
58+
HttpURLConnection httpURLConnection,
59+
ConfigFetchHandler configFetchHandler,
60+
Set<ConfigUpdateListener> eventListeners,
61+
ConfigUpdateListener retryCallback,
62+
Executor executor,
63+
ScheduledExecutorService scheduledExecutorService) {
64+
this.httpURLConnection = httpURLConnection;
65+
this.configFetchHandler = configFetchHandler;
66+
this.eventListeners = eventListeners;
67+
this.retryCallback = retryCallback;
68+
this.scheduledExecutorService = scheduledExecutorService;
69+
this.random = new Random();
70+
this.executor = executor;
71+
}
72+
73+
public void beginAutoFetch() {
74+
executor.execute(
75+
new Runnable() {
76+
@Override
77+
public void run() {
78+
listenForNotifications();
79+
}
80+
});
81+
}
82+
83+
private synchronized void propagateErrors(FirebaseRemoteConfigException exception) {
84+
for (ConfigUpdateListener listener : eventListeners) {
85+
listener.onError(exception);
86+
}
87+
}
88+
89+
private synchronized void executeAllListenerCallbacks() {
90+
for (ConfigUpdateListener listener : eventListeners) {
91+
listener.onEvent();
92+
}
93+
}
94+
95+
// Check connection and establish InputStream
96+
@VisibleForTesting
97+
public synchronized void listenForNotifications() {
98+
if (httpURLConnection != null) {
99+
try {
100+
int responseCode = httpURLConnection.getResponseCode();
101+
if (responseCode == 200) {
102+
InputStream inputStream = httpURLConnection.getInputStream();
103+
handleNotifications(inputStream);
104+
inputStream.close();
105+
} else {
106+
propagateErrors(
107+
new FirebaseRemoteConfigRealtimeUpdateStreamException(
108+
"Http connection responded with error: " + responseCode));
109+
}
110+
} catch (IOException ex) {
111+
propagateErrors(
112+
new FirebaseRemoteConfigRealtimeUpdateFetchException(
113+
"Error handling stream messages while fetching.", ex.getCause()));
114+
}
115+
}
116+
retryCallback.onEvent();
117+
}
118+
119+
// Auto-fetch new config and execute callbacks on each new message
120+
private void handleNotifications(InputStream inputStream) throws IOException {
121+
BufferedReader reader = new BufferedReader((new InputStreamReader(inputStream, "utf-8")));
122+
String message;
123+
while ((message = reader.readLine()) != null) {
124+
long targetTemplateVersion = configFetchHandler.getTemplateVersionNumber();
125+
try {
126+
JSONObject jsonObject = new JSONObject(message);
127+
if (jsonObject.has("latestTemplateVersionNumber")) {
128+
targetTemplateVersion = jsonObject.getLong("latestTemplateVersionNumber");
129+
}
130+
} catch (JSONException ex) {
131+
Log.i(TAG, "Unable to parse latest config update message.");
132+
}
133+
134+
autoFetch(FETCH_RETRY, targetTemplateVersion);
135+
}
136+
reader.close();
137+
}
138+
139+
private void autoFetch(int remainingAttempts, long targetVersion) {
140+
if (remainingAttempts == 0) {
141+
propagateErrors(
142+
new FirebaseRemoteConfigRealtimeUpdateFetchException("Unable to fetch latest version."));
143+
return;
144+
}
145+
146+
// Needs fetch to occur between 2 - 12 seconds. Randomize to not cause ddos alerts in backend
147+
int timeTillFetch = random.nextInt(6) + 1;
148+
scheduledExecutorService.schedule(
149+
new Runnable() {
150+
@Override
151+
public void run() {
152+
fetchLatestConfig(remainingAttempts, targetVersion);
153+
}
154+
},
155+
timeTillFetch,
156+
TimeUnit.SECONDS);
157+
}
158+
159+
@VisibleForTesting
160+
public synchronized void fetchLatestConfig(int remainingAttempts, long targetVersion) {
161+
Task<ConfigFetchHandler.FetchResponse> fetchTask = configFetchHandler.fetch(0L);
162+
fetchTask.onSuccessTask(
163+
(fetchResponse) -> {
164+
long newTemplateVersion = 0;
165+
if (fetchResponse.getFetchedConfigs() != null) {
166+
newTemplateVersion = fetchResponse.getFetchedConfigs().getTemplateVersionNumber();
167+
}
168+
169+
if (newTemplateVersion >= targetVersion) {
170+
executeAllListenerCallbacks();
171+
} else {
172+
Log.i(
173+
TAG,
174+
"Fetched template version is the same as SDK's current version."
175+
+ " Retrying fetch.");
176+
// Continue fetching until template version number if greater then current.
177+
autoFetch(remainingAttempts - 1, targetVersion);
178+
}
179+
return Tasks.forResult(null);
180+
});
181+
}
182+
}

0 commit comments

Comments
 (0)