@react-native-firebase/remote-config 18.0.0 → 18.2.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [18.2.0](https://github.com/invertase/react-native-firebase/compare/v18.1.0...v18.2.0) (2023-07-13)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **app, ios:** incorporate firebase-ios-sdk 10.12.0 ([#7231](https://github.com/invertase/react-native-firebase/issues/7231)) ([ee66459](https://github.com/invertase/react-native-firebase/commit/ee66459cd214ffb84ce2d4e15eef79d047f075ab))
11
+
12
+ ## [18.1.0](https://github.com/invertase/react-native-firebase/compare/v18.0.0...v18.1.0) (2023-06-22)
13
+
14
+ ### Features
15
+
16
+ - **remote-config:** realtime config updates ([9ded619](https://github.com/invertase/react-native-firebase/commit/9ded619e81c1523d7fa0cdbda8fc94929450a967))
17
+
18
+ ### Bug Fixes
19
+
20
+ - **remote-config, ios:** workaround firebase-ios-sdk[#11458](https://github.com/invertase/react-native-firebase/issues/11458) until SDK v10.12.0 ([8c75849](https://github.com/invertase/react-native-firebase/commit/8c758496c2fadc506805f81d1619ebce21a413df))
21
+
6
22
  ## [18.0.0](https://github.com/invertase/react-native-firebase/compare/v17.5.0...v18.0.0) (2023-06-05)
7
23
 
8
24
  **Note:** Version bump only for package @react-native-firebase/remote-config
@@ -19,21 +19,46 @@ package io.invertase.firebase.config;
19
19
 
20
20
  import com.facebook.react.bridge.*;
21
21
  import com.google.firebase.FirebaseApp;
22
- import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
22
+ import com.google.firebase.remoteconfig.*;
23
+ import io.invertase.firebase.common.ReactNativeFirebaseEvent;
24
+ import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
23
25
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
26
+ import java.util.ArrayList;
24
27
  import java.util.HashMap;
28
+ import java.util.Iterator;
29
+ import java.util.List;
25
30
  import java.util.Map;
31
+ import java.util.Set;
26
32
  import javax.annotation.Nullable;
33
+ import org.jetbrains.annotations.NotNull;
27
34
 
28
35
  public class ReactNativeFirebaseConfigModule extends ReactNativeFirebaseModule {
29
36
  private static final String SERVICE_NAME = "Config";
30
37
  private final UniversalFirebaseConfigModule module;
31
38
 
39
+ private static HashMap<String, ConfigUpdateListenerRegistration> mConfigUpdateRegistrations =
40
+ new HashMap<>();
41
+
32
42
  ReactNativeFirebaseConfigModule(ReactApplicationContext reactContext) {
33
43
  super(reactContext, SERVICE_NAME);
34
44
  module = new UniversalFirebaseConfigModule(reactContext, SERVICE_NAME);
35
45
  }
36
46
 
47
+ @Override
48
+ public void onCatalystInstanceDestroy() {
49
+ super.onCatalystInstanceDestroy();
50
+
51
+ Iterator<Map.Entry<String, ConfigUpdateListenerRegistration>> configRegistrationsIterator =
52
+ mConfigUpdateRegistrations.entrySet().iterator();
53
+
54
+ while (configRegistrationsIterator.hasNext()) {
55
+ Map.Entry<String, ConfigUpdateListenerRegistration> pair = configRegistrationsIterator.next();
56
+ ConfigUpdateListenerRegistration mConfigRegistration = pair.getValue();
57
+ mConfigRegistration.remove();
58
+ configRegistrationsIterator.remove();
59
+ }
60
+ }
61
+
37
62
  @ReactMethod
38
63
  public void activate(String appName, Promise promise) {
39
64
  module
@@ -151,6 +176,85 @@ public class ReactNativeFirebaseConfigModule extends ReactNativeFirebaseModule {
151
176
  });
152
177
  }
153
178
 
179
+ @ReactMethod
180
+ public void onConfigUpdated(String appName) {
181
+ if (mConfigUpdateRegistrations.get(appName) == null) {
182
+ FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
183
+ ConfigUpdateListenerRegistration registration =
184
+ FirebaseRemoteConfig.getInstance(firebaseApp)
185
+ .addOnConfigUpdateListener(
186
+ new ConfigUpdateListener() {
187
+ @Override
188
+ public void onUpdate(@NotNull ConfigUpdate configUpdate) {
189
+ ReactNativeFirebaseEventEmitter emitter =
190
+ ReactNativeFirebaseEventEmitter.getSharedInstance();
191
+
192
+ Set<String> updatedKeys = configUpdate.getUpdatedKeys();
193
+ List<String> updatedKeysList = new ArrayList<>(updatedKeys);
194
+
195
+ Map<String, Object> results = new HashMap<>();
196
+ results.put("appName", appName);
197
+ results.put("resultType", "success");
198
+ results.put("updatedKeys", updatedKeysList);
199
+ ReactNativeFirebaseEvent event =
200
+ new ReactNativeFirebaseEvent(
201
+ "on_config_updated", Arguments.makeNativeMap(results), appName);
202
+ emitter.sendEvent(event);
203
+ }
204
+
205
+ @Override
206
+ public void onError(@NotNull FirebaseRemoteConfigException error) {
207
+ ReactNativeFirebaseEventEmitter emitter =
208
+ ReactNativeFirebaseEventEmitter.getSharedInstance();
209
+
210
+ WritableMap userInfoMap = Arguments.createMap();
211
+ userInfoMap.putString("resultType", "error");
212
+ userInfoMap.putString("appName", appName);
213
+
214
+ FirebaseRemoteConfigException.Code code = error.getCode();
215
+ switch (code) {
216
+ case CONFIG_UPDATE_STREAM_ERROR:
217
+ userInfoMap.putString("code", "config_update_stream_error");
218
+ break;
219
+ case CONFIG_UPDATE_MESSAGE_INVALID:
220
+ userInfoMap.putString("code", "config_update_message_invalid");
221
+ break;
222
+ case CONFIG_UPDATE_NOT_FETCHED:
223
+ userInfoMap.putString("code", "config_update_not_fetched");
224
+ break;
225
+ case CONFIG_UPDATE_UNAVAILABLE:
226
+ userInfoMap.putString("code", "config_update_unavailable");
227
+ break;
228
+ case UNKNOWN:
229
+ userInfoMap.putString("code", "unknown");
230
+ break;
231
+ default:
232
+ // contract violation internal to the RNFB / SDK boundary
233
+ userInfoMap.putString("code", "internal");
234
+ }
235
+
236
+ userInfoMap.putString("message", error.getMessage());
237
+ userInfoMap.putString("nativeErrorMessage", error.getMessage());
238
+ ReactNativeFirebaseEvent event =
239
+ new ReactNativeFirebaseEvent("on_config_updated", userInfoMap, appName);
240
+ emitter.sendEvent(event);
241
+ }
242
+ });
243
+
244
+ mConfigUpdateRegistrations.put(appName, registration);
245
+ }
246
+ }
247
+
248
+ @ReactMethod
249
+ public void removeConfigUpdateRegistration(String appName) {
250
+ ConfigUpdateListenerRegistration mConfigRegistration = mConfigUpdateRegistrations.get(appName);
251
+
252
+ if (mConfigRegistration != null) {
253
+ mConfigRegistration.remove();
254
+ mConfigUpdateRegistrations.remove(appName);
255
+ }
256
+ }
257
+
154
258
  private WritableMap resultWithConstants(Object result) {
155
259
  Map<String, Object> responseMap = new HashMap<>(2);
156
260
  responseMap.put("result", result);
@@ -22,6 +22,10 @@
22
22
  #import "RNFBConfigModule.h"
23
23
  #import "RNFBSharedUtils.h"
24
24
 
25
+ static NSString *const ON_CONFIG_UPDATED_EVENT = @"on_config_updated";
26
+
27
+ static __strong NSMutableDictionary *configUpdateHandlers;
28
+
25
29
  @implementation RNFBConfigModule
26
30
  #pragma mark -
27
31
  #pragma mark Converters
@@ -64,6 +68,21 @@ NSString *convertFIRRemoteConfigSourceToNSString(FIRRemoteConfigSource value) {
64
68
  }
65
69
  }
66
70
 
71
+ NSString *convertFIRRemoteConfigUpdateErrorToNSString(FIRRemoteConfigUpdateError value) {
72
+ switch (value) {
73
+ case FIRRemoteConfigUpdateErrorStreamError:
74
+ return @"config_update_stream_error";
75
+ case FIRRemoteConfigUpdateErrorMessageInvalid:
76
+ return @"config_update_message_invalid";
77
+ case FIRRemoteConfigUpdateErrorNotFetched:
78
+ return @"config_update_not_fetched";
79
+ case FIRRemoteConfigUpdateErrorUnavailable:
80
+ return @"config_update_unavailable";
81
+ default:
82
+ return @"internal";
83
+ }
84
+ }
85
+
67
86
  NSDictionary *convertFIRRemoteConfigValueToNSDictionary(FIRRemoteConfigValue *value) {
68
87
  return @{
69
88
  @"value" : (id)value.stringValue ?: [NSNull null],
@@ -84,6 +103,28 @@ RCT_EXPORT_MODULE();
84
103
  return YES;
85
104
  }
86
105
 
106
+ - (id)init {
107
+ self = [super init];
108
+ static dispatch_once_t onceToken;
109
+ dispatch_once(&onceToken, ^{
110
+ configUpdateHandlers = [[NSMutableDictionary alloc] init];
111
+ });
112
+ return self;
113
+ }
114
+
115
+ - (void)dealloc {
116
+ [self invalidate];
117
+ }
118
+
119
+ - (void)invalidate {
120
+ for (NSString *key in configUpdateHandlers) {
121
+ FIRConfigUpdateListenerRegistration *registration = [configUpdateHandlers objectForKey:key];
122
+ [registration remove];
123
+ }
124
+
125
+ [configUpdateHandlers removeAllObjects];
126
+ }
127
+
87
128
  #pragma mark -
88
129
  #pragma mark Firebase Config Methods
89
130
 
@@ -223,6 +264,47 @@ RCT_EXPORT_METHOD(setDefaultsFromResource
223
264
  }
224
265
  }
225
266
 
267
+ RCT_EXPORT_METHOD(onConfigUpdated : (FIRApp *)firebaseApp) {
268
+ if (![configUpdateHandlers valueForKey:firebaseApp.name]) {
269
+ FIRConfigUpdateListenerRegistration *newRegistration =
270
+ [[FIRRemoteConfig remoteConfigWithApp:firebaseApp]
271
+ addOnConfigUpdateListener:^(FIRRemoteConfigUpdate *_Nonnull configUpdate,
272
+ NSError *_Nullable error) {
273
+ if (error != nil) {
274
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
275
+
276
+ [userInfo setValue:@"error" forKey:@"resultType"];
277
+ [userInfo setValue:convertFIRRemoteConfigUpdateErrorToNSString(error.code)
278
+ forKey:@"code"];
279
+ [userInfo setValue:error.localizedDescription forKey:@"message"];
280
+ [userInfo setValue:error.localizedDescription forKey:@"nativeErrorMessage"];
281
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp
282
+ name:ON_CONFIG_UPDATED_EVENT
283
+ body:userInfo];
284
+ return;
285
+ }
286
+
287
+ NSMutableDictionary *results = [NSMutableDictionary dictionary];
288
+
289
+ [results setValue:@"success" forKey:@"resultType"];
290
+ [results setValue:[configUpdate.updatedKeys allObjects] forKey:@"updatedKeys"];
291
+
292
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp
293
+ name:ON_CONFIG_UPDATED_EVENT
294
+ body:results];
295
+ }];
296
+
297
+ configUpdateHandlers[firebaseApp.name] = newRegistration;
298
+ }
299
+ }
300
+
301
+ RCT_EXPORT_METHOD(removeConfigUpdateRegistration : (FIRApp *)firebaseApp) {
302
+ if ([configUpdateHandlers valueForKey:firebaseApp.name]) {
303
+ [[configUpdateHandlers objectForKey:firebaseApp.name] remove];
304
+ [configUpdateHandlers removeObjectForKey:firebaseApp.name];
305
+ }
306
+ }
307
+
226
308
  #pragma mark -
227
309
  #pragma mark Internal Helper Methods
228
310
 
package/lib/index.d.ts CHANGED
@@ -376,6 +376,17 @@ export namespace FirebaseRemoteConfigTypes {
376
376
  */
377
377
  setDefaultsFromResource(resourceName: string): Promise<null>;
378
378
 
379
+ /**
380
+ * Start listening for real-time config updates from the Remote Config backend and
381
+ * automatically fetch updates when they’re available. Note that the list of updated keys
382
+ * passed to the callback will include all keys not currently active, and the config update
383
+ * process fetches the new config but does not automatically activate for you. Typically
384
+ * you will want to activate the config in your callback so the new values are in force.
385
+ *
386
+ * @param listener called with either array of updated keys or error arg when config changes
387
+ */
388
+ onConfigUpdated(listener: CallbackOrObserver<OnConfigUpdatedListenerCallback>): () => void;
389
+
379
390
  /**
380
391
  * Moves fetched data to the apps active config.
381
392
  * Resolves with a boolean value true if new local values were activated
@@ -543,6 +554,17 @@ export const firebase: ReactNativeFirebase.Module & {
543
554
  ): ReactNativeFirebase.FirebaseApp & { remoteConfig(): FirebaseRemoteConfigTypes.Module };
544
555
  };
545
556
 
557
+ type CallbackOrObserver<T extends (...args: any[]) => any> = T | { next: T };
558
+
559
+ type OnConfigUpdatedListenerCallback = (
560
+ event?: { updatedKeys: string[] },
561
+ error?: {
562
+ code: string;
563
+ message: string;
564
+ nativeErrorMessage: string;
565
+ },
566
+ ) => void;
567
+
546
568
  export default defaultExport;
547
569
 
548
570
  /**
package/lib/index.js CHANGED
@@ -53,6 +53,7 @@ export {
53
53
  fetch,
54
54
  setDefaults,
55
55
  setDefaultsFromResource,
56
+ onConfigUpdated,
56
57
  } from './modular/index';
57
58
 
58
59
  const statics = {
@@ -84,6 +85,7 @@ class FirebaseConfigModule extends FirebaseModule {
84
85
  this._lastFetchTime = -1;
85
86
  this._values = {};
86
87
  this._isWeb = Platform.OS !== 'ios' && Platform.OS !== 'android';
88
+ this._configUpdateListenerCount = 0;
87
89
  }
88
90
 
89
91
  get defaultConfig() {
@@ -282,6 +284,59 @@ class FirebaseConfigModule extends FirebaseModule {
282
284
  return this._promiseWithConstants(this.native.setDefaultsFromResource(resourceName));
283
285
  }
284
286
 
287
+ /**
288
+ * Registers a listener to changes in the configuration.
289
+ *
290
+ * @param listenerOrObserver - function called on config change
291
+ * @returns {function} unsubscribe listener
292
+ */
293
+ onConfigUpdated(listenerOrObserver) {
294
+ const listener = this._parseListener(listenerOrObserver);
295
+ let unsubscribed = false;
296
+ const subscription = this.emitter.addListener(
297
+ this.eventNameForApp('on_config_updated'),
298
+ event => {
299
+ const { resultType } = event;
300
+ if (resultType === 'success') {
301
+ listener({ updatedKeys: event.updatedKeys }, undefined);
302
+ return;
303
+ }
304
+
305
+ listener(undefined, {
306
+ code: event.code,
307
+ message: event.message,
308
+ nativeErrorMessage: event.nativeErrorMessage,
309
+ });
310
+ },
311
+ );
312
+ if (this._configUpdateListenerCount === 0) {
313
+ this.native.onConfigUpdated();
314
+ }
315
+
316
+ this._configUpdateListenerCount++;
317
+
318
+ return () => {
319
+ if (unsubscribed) {
320
+ // there is no harm in calling this multiple times to unsubscribe,
321
+ // but anything after the first call is a no-op
322
+ return;
323
+ } else {
324
+ unsubscribed = true;
325
+ }
326
+ subscription.remove();
327
+ this._configUpdateListenerCount--;
328
+ if (this._configUpdateListenerCount === 0) {
329
+ this.native.removeConfigUpdateRegistration();
330
+ }
331
+ };
332
+ }
333
+
334
+ _parseListener(listenerOrObserver) {
335
+ return typeof listenerOrObserver === 'object'
336
+ ? listenerOrObserver.next.bind(listenerOrObserver)
337
+ : listenerOrObserver;
338
+ }
339
+
285
340
  _updateFromConstants(constants) {
286
341
  // Wrapped this as we update using sync getters initially for `defaultConfig` & `settings`
287
342
  if (constants.lastFetchTime) {
@@ -326,7 +381,7 @@ export default createModuleNamespace({
326
381
  version,
327
382
  namespace,
328
383
  nativeModuleName,
329
- nativeEvents: false,
384
+ nativeEvents: ['on_config_updated'],
330
385
  hasMultiAppSupport: true,
331
386
  hasCustomUrlOrRegionSupport: false,
332
387
  ModuleClass: FirebaseConfigModule,
@@ -221,3 +221,14 @@ export function setDefaults(remoteConfig, defaults) {
221
221
  export function setDefaultsFromResource(remoteConfig, resourceName) {
222
222
  return remoteConfig.setDefaultsFromResource(resourceName);
223
223
  }
224
+
225
+ /**
226
+ * Registers a listener to changes in the configuration.
227
+ *
228
+ * @param remoteConfig - RemoteConfig instance
229
+ * @param callback - function called on config change
230
+ * @returns {function} unsubscribe listener
231
+ */
232
+ export function onConfigUpdated(remoteConfig, callback) {
233
+ return remoteConfig.onConfigUpdated(callback);
234
+ }
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '18.0.0';
2
+ module.exports = '18.2.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/remote-config",
3
- "version": "18.0.0",
3
+ "version": "18.2.0",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - React Native Firebase provides native integration with Remote Config, allowing you to change the appearance and/or functionality of your app without requiring an app update.",
6
6
  "main": "lib/index.js",
@@ -24,11 +24,11 @@
24
24
  "remote-config"
25
25
  ],
26
26
  "peerDependencies": {
27
- "@react-native-firebase/analytics": "18.0.0",
28
- "@react-native-firebase/app": "18.0.0"
27
+ "@react-native-firebase/analytics": "18.2.0",
28
+ "@react-native-firebase/app": "18.2.0"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
32
32
  },
33
- "gitHead": "db49dfeab62fa0c52530ccf2bfdfe9e27947bdbd"
33
+ "gitHead": "4c666df92028ddc3c0010a7ac102f54b600e6644"
34
34
  }