@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 +16 -0
- package/android/src/reactnative/java/io/invertase/firebase/config/ReactNativeFirebaseConfigModule.java +105 -1
- package/ios/RNFBConfig/RNFBConfigModule.m +82 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +56 -1
- package/lib/modular/index.js +11 -0
- package/lib/version.js +1 -1
- package/package.json +4 -4
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
|
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:
|
384
|
+
nativeEvents: ['on_config_updated'],
|
330
385
|
hasMultiAppSupport: true,
|
331
386
|
hasCustomUrlOrRegionSupport: false,
|
332
387
|
ModuleClass: FirebaseConfigModule,
|
package/lib/modular/index.js
CHANGED
@@ -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.
|
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.
|
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.
|
28
|
-
"@react-native-firebase/app": "18.
|
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": "
|
33
|
+
"gitHead": "4c666df92028ddc3c0010a7ac102f54b600e6644"
|
34
34
|
}
|