@callstack/react-native-brownfield 6.1.0 → 6.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 +17 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +12 -12
- package/android/src/main/java/com/callstack/reactnativebrownfield/RetryableOnce.kt +27 -0
- package/android/src/test/java/com/callstack/reactnativebrownfield/RetryableOnceTest.kt +37 -0
- package/ios/ReactNativeBrownfield.swift +3 -1
- package/ios/ReactNativeBrownfieldModule.mm +35 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @callstack/react-native-brownfield
|
|
2
2
|
|
|
3
|
+
## 6.2.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`6517ec5`](https://github.com/callstack/react-native-brownfield/commit/6517ec5666269007f10c28efb7a748beba476998)]:
|
|
8
|
+
- @callstack/brownfield-cli@6.2.0
|
|
9
|
+
|
|
10
|
+
## 6.1.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#516](https://github.com/callstack/react-native-brownfield/pull/516) [`66ac701`](https://github.com/callstack/react-native-brownfield/commit/66ac701d71cb3990732e701a0ab1abd40a7bd271) Thanks [@malopezr7](https://github.com/malopezr7)! - Android: `ReactNativeBrownfield.initialize` can now be called again after it throws. Before, a failure while loading native libraries or creating the `ReactHost` left it marked as initialized, so the next call returned without error and `ReactNativeBrownfield.shared` was never set.
|
|
15
|
+
|
|
16
|
+
- [#517](https://github.com/callstack/react-native-brownfield/pull/517) [`5884ce9`](https://github.com/callstack/react-native-brownfield/commit/5884ce96bcccf73de33835e7db2785aa3ab8c2a9) Thanks [@tlq5l](https://github.com/tlq5l)! - Fix an iOS crash (`std::bad_function_call`) when native code posts a message to JS while the Brownfield module's JS emitter is still being set up. If the Brownfield module exists but its JS emitter is not ready when delivery happens, the message is dropped and a log line is written. If the module does not exist yet, the message is dropped without a log line.
|
|
17
|
+
- Updated dependencies [[`a9726c1`](https://github.com/callstack/react-native-brownfield/commit/a9726c137c61fe2e59c039555a955632a5d86aec)]:
|
|
18
|
+
- @callstack/brownfield-cli@6.1.1
|
|
19
|
+
|
|
3
20
|
## 6.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -18,7 +18,6 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
|
18
18
|
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
19
19
|
import com.facebook.soloader.SoLoader
|
|
20
20
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
21
|
-
import java.util.concurrent.atomic.AtomicBoolean
|
|
22
21
|
|
|
23
22
|
fun interface OnJSBundleLoaded {
|
|
24
23
|
operator fun invoke(timings: JSBundleTimings)
|
|
@@ -33,17 +32,15 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
33
32
|
|
|
34
33
|
companion object {
|
|
35
34
|
private lateinit var instance: ReactNativeBrownfield
|
|
36
|
-
private val
|
|
37
|
-
private val
|
|
35
|
+
private val initialization = RetryableOnce()
|
|
36
|
+
private val nativeLibsLoading = RetryableOnce()
|
|
38
37
|
private lateinit var applicationContext: Application
|
|
39
38
|
|
|
40
39
|
@JvmStatic
|
|
41
40
|
val shared: ReactNativeBrownfield get() = instance
|
|
42
41
|
|
|
43
42
|
private fun loadNativeLibs(application: Application) {
|
|
44
|
-
|
|
45
|
-
loadNativeLibsInternal(application)
|
|
46
|
-
}
|
|
43
|
+
nativeLibsLoading.run { loadNativeLibsInternal(application) }
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
private fun loadNativeLibsInternal(application: Application) {
|
|
@@ -65,11 +62,12 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
65
62
|
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
66
63
|
) {
|
|
67
64
|
applicationContext = application
|
|
68
|
-
|
|
65
|
+
val ranNow = initialization.run {
|
|
69
66
|
AndroidJSBundleTimingObserver.install()
|
|
70
67
|
loadNativeLibs(application)
|
|
71
68
|
installAndPreload(reactHost, onJSBundleLoaded)
|
|
72
|
-
}
|
|
69
|
+
}
|
|
70
|
+
if (!ranNow) {
|
|
73
71
|
invokeWhenJSBundleLoaded(onJSBundleLoaded)
|
|
74
72
|
}
|
|
75
73
|
}
|
|
@@ -81,11 +79,12 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
81
79
|
reactHostFactory: () -> ReactHost
|
|
82
80
|
) {
|
|
83
81
|
applicationContext = application
|
|
84
|
-
|
|
82
|
+
val ranNow = initialization.run {
|
|
85
83
|
AndroidJSBundleTimingObserver.install()
|
|
86
84
|
loadNativeLibs(application)
|
|
87
85
|
installAndPreload(reactHostFactory(), onJSBundleLoaded)
|
|
88
|
-
}
|
|
86
|
+
}
|
|
87
|
+
if (!ranNow) {
|
|
89
88
|
invokeWhenJSBundleLoaded(onJSBundleLoaded)
|
|
90
89
|
}
|
|
91
90
|
}
|
|
@@ -98,7 +97,7 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
98
97
|
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
99
98
|
) {
|
|
100
99
|
applicationContext = application
|
|
101
|
-
|
|
100
|
+
val ranNow = initialization.run {
|
|
102
101
|
AndroidJSBundleTimingObserver.install()
|
|
103
102
|
loadNativeLibs(application)
|
|
104
103
|
val reactHost = getDefaultReactHost(
|
|
@@ -114,7 +113,8 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
114
113
|
jsRuntimeFactory = null
|
|
115
114
|
)
|
|
116
115
|
installAndPreload(reactHost, onJSBundleLoaded)
|
|
117
|
-
}
|
|
116
|
+
}
|
|
117
|
+
if (!ranNow) {
|
|
118
118
|
invokeWhenJSBundleLoaded(onJSBundleLoaded)
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
|
+
|
|
3
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runs a block at most once, counting only a run that completes.
|
|
7
|
+
*
|
|
8
|
+
* If the block throws, the next [run] call runs it again instead of treating
|
|
9
|
+
* the work as done. Calls made while a run is in progress are skipped.
|
|
10
|
+
*/
|
|
11
|
+
internal class RetryableOnce {
|
|
12
|
+
private val claimed = AtomicBoolean()
|
|
13
|
+
|
|
14
|
+
/** Returns `true` when [block] ran and completed during this call. */
|
|
15
|
+
fun run(block: () -> Unit): Boolean {
|
|
16
|
+
if (claimed.getAndSet(true)) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
block()
|
|
21
|
+
} catch (error: Throwable) {
|
|
22
|
+
claimed.set(false)
|
|
23
|
+
throw error
|
|
24
|
+
}
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertFalse
|
|
5
|
+
import org.junit.Assert.assertTrue
|
|
6
|
+
import org.junit.Assert.fail
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
|
|
9
|
+
class RetryableOnceTest {
|
|
10
|
+
@Test fun `runs the block once after it completes`() {
|
|
11
|
+
val once = RetryableOnce()
|
|
12
|
+
var runs = 0
|
|
13
|
+
|
|
14
|
+
assertTrue(once.run { runs++ })
|
|
15
|
+
assertFalse(once.run { runs++ })
|
|
16
|
+
assertEquals(1, runs)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Test fun `runs the block again after it throws`() {
|
|
20
|
+
val once = RetryableOnce()
|
|
21
|
+
var runs = 0
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
once.run {
|
|
25
|
+
runs++
|
|
26
|
+
throw UnsatisfiedLinkError("dlopen failed")
|
|
27
|
+
}
|
|
28
|
+
fail("expected the error to propagate")
|
|
29
|
+
} catch (expected: UnsatisfiedLinkError) {
|
|
30
|
+
assertEquals("dlopen failed", expected.message)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
assertTrue(once.run { runs++ })
|
|
34
|
+
assertFalse(once.run { runs++ })
|
|
35
|
+
assertEquals(2, runs)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -307,7 +307,9 @@ internal import Expo
|
|
|
307
307
|
|
|
308
308
|
/**
|
|
309
309
|
* Send a serialized JSON message to the React Native JS application.
|
|
310
|
-
*
|
|
310
|
+
* Messages can be received using `ReactNativeBrownfield.onMessage()`.
|
|
311
|
+
* If the Brownfield module exists but its JS emitter is not ready when delivery happens, the message is dropped and a log line is written.
|
|
312
|
+
* If the module does not exist yet, the message is dropped without a log line.
|
|
311
313
|
*
|
|
312
314
|
* @param message - The serialized JSON message to send to the React Native JS application.
|
|
313
315
|
* @example
|
|
@@ -15,7 +15,9 @@ static ReactNativeBrownfieldModule *_sharedInstance = nil;
|
|
|
15
15
|
- (instancetype)init {
|
|
16
16
|
self = [super init];
|
|
17
17
|
if (self) {
|
|
18
|
-
|
|
18
|
+
@synchronized (ReactNativeBrownfieldModule.class) {
|
|
19
|
+
_sharedInstance = self;
|
|
20
|
+
}
|
|
19
21
|
|
|
20
22
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
21
23
|
selector:@selector(handleNativeToJSMessage:)
|
|
@@ -28,8 +30,18 @@ static ReactNativeBrownfieldModule *_sharedInstance = nil;
|
|
|
28
30
|
- (void)dealloc {
|
|
29
31
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
_sharedInstance
|
|
33
|
+
@synchronized (ReactNativeBrownfieldModule.class) {
|
|
34
|
+
if (_sharedInstance == self) {
|
|
35
|
+
_sharedInstance = nil;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// React Native calls invalidate during teardown; clearing the callback sends
|
|
41
|
+
// later messages down the not-ready drop path.
|
|
42
|
+
- (void)invalidate {
|
|
43
|
+
@synchronized (ReactNativeBrownfieldModule.class) {
|
|
44
|
+
_eventEmitterCallback = nullptr;
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
|
|
@@ -41,10 +53,22 @@ static ReactNativeBrownfieldModule *_sharedInstance = nil;
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
+ (void)emitMessageFromNative:(NSString *)message {
|
|
44
|
-
|
|
56
|
+
// Keep singleton and callback access under one lock because getTurboModule:
|
|
57
|
+
// installs the callback when JS first requires the module.
|
|
58
|
+
@synchronized (ReactNativeBrownfieldModule.class) {
|
|
59
|
+
if (!_sharedInstance) {
|
|
60
|
+
NSLog(@"ReactNativeBrownfieldModule is not initialized, dropping message");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The codegen base class exposes this callback as a protected std::function;
|
|
65
|
+
// invoking it while empty would throw std::bad_function_call.
|
|
66
|
+
if (!_sharedInstance->_eventEmitterCallback) {
|
|
67
|
+
NSLog(@"ReactNativeBrownfieldModule is not ready for JS yet, dropping message");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
[_sharedInstance emitOnBrownfieldMessage:@{ @"text": message }];
|
|
46
|
-
} else {
|
|
47
|
-
NSLog(@"ReactNativeBrownfieldModule is not initialized, dropping message");
|
|
48
72
|
}
|
|
49
73
|
}
|
|
50
74
|
|
|
@@ -65,7 +89,11 @@ RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {}
|
|
|
65
89
|
RCT_EXPORT_METHOD(removeListeners:(double)count) {}
|
|
66
90
|
|
|
67
91
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
68
|
-
|
|
92
|
+
// Building the spec installs _eventEmitterCallback; use the same lock as
|
|
93
|
+
// emitMessageFromNative: to prevent the callback read and write from racing.
|
|
94
|
+
@synchronized (ReactNativeBrownfieldModule.class) {
|
|
95
|
+
return std::make_shared<facebook::react::NativeReactNativeBrownfieldModuleSpecJSI>(params);
|
|
96
|
+
}
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callstack/react-native-brownfield",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Michal Chudziak <mike.chudziak@callstack.com>",
|
|
6
6
|
"bin": {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"@expo/config-plugins": ">=54 <100"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
|
-
"@callstack/brownfield-cli": "^6.
|
|
103
|
+
"@callstack/brownfield-cli": "^6.2.0",
|
|
104
104
|
"@react-native-community/cli-config": "^20.0.0",
|
|
105
105
|
"@react-native-community/cli-types": "^20.0.0",
|
|
106
106
|
"xcode": "^3.0.1"
|