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

Sign up to get free protection for your applications and to get access to all the features.
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
  }