@firebase/remote-config 0.7.0-canary.ea8512812 → 0.7.0-canary.f06cbf99b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { LogLevel, Logger } from '@firebase/logger';
5
5
  import '@firebase/installations';
6
6
 
7
7
  const name = "@firebase/remote-config";
8
- const version = "0.7.0-canary.ea8512812";
8
+ const version = "0.7.0-canary.f06cbf99b";
9
9
 
10
10
  /**
11
11
  * @license
@@ -105,7 +105,8 @@ const ERROR_DESCRIPTION_MAP = {
105
105
  ["stream-error" /* ErrorCode.CONFIG_UPDATE_STREAM_ERROR */]: 'The stream was not able to connect to the backend: {$originalErrorMessage}.',
106
106
  ["realtime-unavailable" /* ErrorCode.CONFIG_UPDATE_UNAVAILABLE */]: 'The Realtime service is unavailable: {$originalErrorMessage}',
107
107
  ["update-message-invalid" /* ErrorCode.CONFIG_UPDATE_MESSAGE_INVALID */]: 'The stream invalidation message was unparsable: {$originalErrorMessage}',
108
- ["update-not-fetched" /* ErrorCode.CONFIG_UPDATE_NOT_FETCHED */]: 'Unable to fetch the latest config: {$originalErrorMessage}'
108
+ ["update-not-fetched" /* ErrorCode.CONFIG_UPDATE_NOT_FETCHED */]: 'Unable to fetch the latest config: {$originalErrorMessage}',
109
+ ["analytics-unavailable" /* ErrorCode.ANALYTICS_UNAVAILABLE */]: 'Connection to Firebase Analytics failed: {$originalErrorMessage}'
109
110
  };
110
111
  const ERROR_FACTORY = new ErrorFactory('remoteconfig' /* service */, 'Remote Config' /* service name */, ERROR_DESCRIPTION_MAP);
111
112
  // Note how this is like typeof/instanceof, but for ErrorCode.
@@ -162,6 +163,64 @@ class Value {
162
163
  }
163
164
  }
164
165
 
166
+ class Experiment {
167
+ constructor(rc) {
168
+ this.storage = rc._storage;
169
+ this.logger = rc._logger;
170
+ this.analyticsProvider = rc._analyticsProvider;
171
+ }
172
+ async updateActiveExperiments(latestExperiments) {
173
+ const currentActiveExperiments = (await this.storage.getActiveExperiments()) || new Set();
174
+ const experimentInfoMap = this.createExperimentInfoMap(latestExperiments);
175
+ this.addActiveExperiments(experimentInfoMap);
176
+ this.removeInactiveExperiments(currentActiveExperiments, experimentInfoMap);
177
+ return this.storage.setActiveExperiments(new Set(experimentInfoMap.keys()));
178
+ }
179
+ createExperimentInfoMap(latestExperiments) {
180
+ const experimentInfoMap = new Map();
181
+ for (const experiment of latestExperiments) {
182
+ experimentInfoMap.set(experiment.experimentId, experiment);
183
+ }
184
+ return experimentInfoMap;
185
+ }
186
+ addActiveExperiments(experimentInfoMap) {
187
+ const customProperty = {};
188
+ for (const [experimentId, experimentInfo] of experimentInfoMap.entries()) {
189
+ customProperty[`firebase${experimentId}`] = experimentInfo.variantId;
190
+ }
191
+ this.addExperimentToAnalytics(customProperty);
192
+ }
193
+ removeInactiveExperiments(currentActiveExperiments, experimentInfoMap) {
194
+ const customProperty = {};
195
+ for (const experimentId of currentActiveExperiments) {
196
+ if (!experimentInfoMap.has(experimentId)) {
197
+ customProperty[`firebase${experimentId}`] = null;
198
+ }
199
+ }
200
+ this.addExperimentToAnalytics(customProperty);
201
+ }
202
+ addExperimentToAnalytics(customProperty) {
203
+ if (Object.keys(customProperty).length === 0) {
204
+ return;
205
+ }
206
+ try {
207
+ const analytics = this.analyticsProvider.getImmediate({ optional: true });
208
+ if (analytics) {
209
+ analytics.setUserProperties(customProperty);
210
+ analytics.logEvent(`set_firebase_experiment_state`);
211
+ }
212
+ else {
213
+ this.logger.warn(`Analytics import failed. Verify if you have imported Firebase Analytics in your app code.`);
214
+ }
215
+ }
216
+ catch (error) {
217
+ throw ERROR_FACTORY.create("analytics-unavailable" /* ErrorCode.ANALYTICS_UNAVAILABLE */, {
218
+ originalErrorMessage: error?.message
219
+ });
220
+ }
221
+ }
222
+ }
223
+
165
224
  /**
166
225
  * @license
167
226
  * Copyright 2020 Google LLC
@@ -239,10 +298,15 @@ async function activate(remoteConfig) {
239
298
  // config.
240
299
  return false;
241
300
  }
301
+ const experiment = new Experiment(rc);
302
+ const updateActiveExperiments = lastSuccessfulFetchResponse.experiments
303
+ ? experiment.updateActiveExperiments(lastSuccessfulFetchResponse.experiments)
304
+ : Promise.resolve();
242
305
  await Promise.all([
243
306
  rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
244
307
  rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag),
245
- rc._storage.setActiveConfigTemplateVersion(lastSuccessfulFetchResponse.templateVersion)
308
+ rc._storage.setActiveConfigTemplateVersion(lastSuccessfulFetchResponse.templateVersion),
309
+ updateActiveExperiments
246
310
  ]);
247
311
  return true;
248
312
  }
@@ -697,6 +761,7 @@ class RestClient {
697
761
  let config;
698
762
  let state;
699
763
  let templateVersion;
764
+ let experiments;
700
765
  // JSON parsing throws SyntaxError if the response body isn't a JSON string.
701
766
  // Requesting application/json and checking for a 200 ensures there's JSON data.
702
767
  if (response.status === 200) {
@@ -712,6 +777,7 @@ class RestClient {
712
777
  config = responseBody['entries'];
713
778
  state = responseBody['state'];
714
779
  templateVersion = responseBody['templateVersion'];
780
+ experiments = responseBody['experimentDescriptions'];
715
781
  }
716
782
  // Normalizes based on legacy state.
717
783
  if (state === 'INSTANCE_STATE_UNSPECIFIED') {
@@ -723,6 +789,7 @@ class RestClient {
723
789
  else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') {
724
790
  // These cases can be fixed remotely, so normalize to safe value.
725
791
  config = {};
792
+ experiments = [];
726
793
  }
727
794
  // Normalize to exception-based control flow for non-success cases.
728
795
  // Encapsulates HTTP specifics in this class as much as possible. Status is still the best for
@@ -733,7 +800,7 @@ class RestClient {
733
800
  httpStatus: status
734
801
  });
735
802
  }
736
- return { status, eTag: responseEtag, config, templateVersion };
803
+ return { status, eTag: responseEtag, config, templateVersion, experiments };
737
804
  }
738
805
  }
739
806
 
@@ -899,13 +966,18 @@ class RemoteConfig {
899
966
  /**
900
967
  * @internal
901
968
  */
902
- _realtimeHandler) {
969
+ _realtimeHandler,
970
+ /**
971
+ * @internal
972
+ */
973
+ _analyticsProvider) {
903
974
  this.app = app;
904
975
  this._client = _client;
905
976
  this._storageCache = _storageCache;
906
977
  this._storage = _storage;
907
978
  this._logger = _logger;
908
979
  this._realtimeHandler = _realtimeHandler;
980
+ this._analyticsProvider = _analyticsProvider;
909
981
  /**
910
982
  * Tracks completion of initialization promise.
911
983
  * @internal
@@ -1026,6 +1098,12 @@ class Storage {
1026
1098
  setActiveConfigEtag(etag) {
1027
1099
  return this.set('active_config_etag', etag);
1028
1100
  }
1101
+ getActiveExperiments() {
1102
+ return this.get('active_experiments');
1103
+ }
1104
+ setActiveExperiments(experiments) {
1105
+ return this.set('active_experiments', experiments);
1106
+ }
1029
1107
  getThrottleMetadata() {
1030
1108
  return this.get('throttle_metadata');
1031
1109
  }
@@ -2016,6 +2094,7 @@ function registerRemoteConfig() {
2016
2094
  const installations = container
2017
2095
  .getProvider('installations-internal')
2018
2096
  .getImmediate();
2097
+ const analyticsProvider = container.getProvider('analytics-internal');
2019
2098
  // Normalizes optional inputs.
2020
2099
  const { projectId, apiKey, appId } = app.options;
2021
2100
  if (!projectId) {
@@ -2042,7 +2121,7 @@ function registerRemoteConfig() {
2042
2121
  const retryingClient = new RetryingClient(restClient, storage);
2043
2122
  const cachingClient = new CachingClient(retryingClient, storage, storageCache, logger);
2044
2123
  const realtimeHandler = new RealtimeHandler(installations, storage, SDK_VERSION, namespace, projectId, apiKey, appId, logger, storageCache, cachingClient);
2045
- const remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger, realtimeHandler);
2124
+ const remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger, realtimeHandler, analyticsProvider);
2046
2125
  // Starts warming cache.
2047
2126
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
2048
2127
  ensureInitialized(remoteConfigInstance);