react-native-nitro-modules 0.16.1 → 0.17.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.
@@ -31,6 +31,7 @@ Pod::Spec.new do |s|
31
31
  "cpp/core/AnyMap.hpp",
32
32
  "cpp/core/ArrayBuffer.hpp",
33
33
  "cpp/core/HybridObject.hpp",
34
+ "cpp/core/Promise.hpp",
34
35
  "cpp/entrypoint/HybridNitroModulesProxy.hpp",
35
36
  "cpp/entrypoint/InstallNitro.hpp",
36
37
  "cpp/registry/HybridObjectRegistry.hpp",
@@ -40,9 +41,9 @@ Pod::Spec.new do |s|
40
41
  "cpp/utils/NitroDefines.hpp",
41
42
  # Public iOS-specific headers that will be exposed in modulemap (for Swift)
42
43
  "ios/core/ArrayBufferHolder.hpp",
43
- "ios/core/PromiseHolder.hpp",
44
44
  "ios/core/AnyMapHolder.hpp",
45
45
  "ios/core/HybridContext.hpp",
46
+ "ios/utils/RuntimeError.hpp",
46
47
  "ios/utils/SwiftClosure.hpp",
47
48
  ]
48
49
 
package/README.md CHANGED
@@ -169,12 +169,6 @@ The following C++ / JS types are supported out of the box:
169
169
  <td><code>T?</code></td>
170
170
  <td><code>T?</code></td>
171
171
  </tr>
172
- <tr>
173
- <td><code>Promise&lt;T&gt;</code></td>
174
- <td><code>std::future&lt;T&gt;</code></td>
175
- <td><code><a href="./ios/core/Promise.swift">Promise&lt;T&gt;</a></code></td>
176
- <td><code><a href="./android/src/main/java/com/margelo/nitro/core/Promise.kt">Promise&lt;T&gt;</a></code></td>
177
- </tr>
178
172
  <tr>
179
173
  <td><code>(T...) =&gt; void</code></td>
180
174
  <td><code>std::function&lt;void (T...)&gt;</code></td>
@@ -187,6 +181,12 @@ The following C++ / JS types are supported out of the box:
187
181
  <td>❌</td>
188
182
  <td>❌</td>
189
183
  </tr>
184
+ <tr>
185
+ <td><code>Promise&lt;T&gt;</code></td>
186
+ <td><code>std::shared_ptr&lt;<a href="./cpp/core/Promise.hpp">Promise&lt;T&gt;</a>&gt;</code></td>
187
+ <td><code><a href="./ios/core/Promise.swift">Promise&lt;T&gt;</a></code></td>
188
+ <td><code><a href="./android/src/main/java/com/margelo/nitro/core/Promise.kt">Promise&lt;T&gt;</a></code></td>
189
+ </tr>
190
190
  <tr>
191
191
  <td><code>{ ... }</code></td>
192
192
  <td><code>std::shared_ptr&lt;<a href="./cpp/core/AnyMap.hpp">AnyMap</a>&gt;</code></td>
@@ -48,9 +48,18 @@ android {
48
48
 
49
49
  externalNativeBuild {
50
50
  cmake {
51
- cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
51
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
52
52
  arguments "-DANDROID_STL=c++_shared"
53
53
  abiFilters (*reactNativeArchitectures())
54
+
55
+ buildTypes {
56
+ debug {
57
+ cppFlags "-O1 -g"
58
+ }
59
+ release {
60
+ cppFlags "-O2"
61
+ }
62
+ }
54
63
  }
55
64
  }
56
65
  }
@@ -7,12 +7,30 @@
7
7
 
8
8
  #pragma once
9
9
 
10
+ #include "Promise.hpp"
10
11
  #include <fbjni/fbjni.h>
12
+ #include <mutex>
11
13
 
12
14
  namespace margelo::nitro {
13
15
 
14
16
  using namespace facebook;
15
17
 
18
+ struct JOnResolvedCallback : public jni::JavaClass<JOnResolvedCallback> {
19
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/core/Promise$OnResolvedCallback;";
20
+ void onResolved(const jni::alias_ref<jni::JObject>& result) const {
21
+ static const auto method = javaClassLocal()->getMethod<void(jni::alias_ref<jni::JObject>)>("onResolved");
22
+ method(self(), result);
23
+ }
24
+ };
25
+
26
+ struct JOnRejectedCallback : public jni::JavaClass<JOnRejectedCallback> {
27
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/core/Promise$OnRejectedCallback;";
28
+ void onRejected(const jni::alias_ref<jni::JThrowable>& error) const {
29
+ static const auto method = javaClassLocal()->getMethod<void(jni::alias_ref<jni::JThrowable>)>("onRejected");
30
+ method(self(), error);
31
+ }
32
+ };
33
+
16
34
  /**
17
35
  * Represents a Promise implemented in Java.
18
36
  */
@@ -20,9 +38,9 @@ class JPromise final : public jni::HybridClass<JPromise> {
20
38
  public:
21
39
  static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/core/Promise;";
22
40
  using OnResolvedFunc = std::function<void(jni::alias_ref<jni::JObject>)>;
23
- using OnRejectedFunc = std::function<void(jni::alias_ref<jni::JString>)>;
41
+ using OnRejectedFunc = std::function<void(jni::alias_ref<jni::JThrowable>)>;
24
42
 
25
- public:
43
+ private:
26
44
  /**
27
45
  * Create a new, still unresolved `JPromise` from Java.
28
46
  */
@@ -30,14 +48,24 @@ public:
30
48
  return makeCxxInstance();
31
49
  }
32
50
 
51
+ public:
52
+ /**
53
+ * Create a new, still unresolved `JPromise` from C++.
54
+ */
55
+ static jni::local_ref<JPromise::javaobject> create() {
56
+ return newObjectCxxArgs();
57
+ }
58
+
33
59
  public:
34
60
  void resolve(jni::alias_ref<jni::JObject> result) {
61
+ std::unique_lock lock(_mutex);
35
62
  _result = jni::make_global(result);
36
63
  for (const auto& onResolved : _onResolvedListeners) {
37
64
  onResolved(_result);
38
65
  }
39
66
  }
40
- void reject(jni::alias_ref<jni::JString> error) {
67
+ void reject(jni::alias_ref<jni::JThrowable> error) {
68
+ std::unique_lock lock(_mutex);
41
69
  _error = jni::make_global(error);
42
70
  for (const auto& onRejected : _onRejectedListeners) {
43
71
  onRejected(_error);
@@ -46,6 +74,7 @@ public:
46
74
 
47
75
  public:
48
76
  void addOnResolvedListener(OnResolvedFunc&& onResolved) {
77
+ std::unique_lock lock(_mutex);
49
78
  if (_result != nullptr) {
50
79
  // Promise is already resolved! Call the callback immediately
51
80
  onResolved(_result);
@@ -55,6 +84,7 @@ public:
55
84
  }
56
85
  }
57
86
  void addOnRejectedListener(OnRejectedFunc&& onRejected) {
87
+ std::unique_lock lock(_mutex);
58
88
  if (_error != nullptr) {
59
89
  // Promise is already rejected! Call the callback immediately
60
90
  onRejected(_error);
@@ -65,15 +95,40 @@ public:
65
95
  }
66
96
 
67
97
  private:
68
- JPromise() {}
98
+ void addOnResolvedListenerJava(jni::alias_ref<JOnResolvedCallback> callback) {
99
+ std::unique_lock lock(_mutex);
100
+ if (_result != nullptr) {
101
+ // Promise is already resolved! Call the callback immediately
102
+ callback->onResolved(_result);
103
+ } else {
104
+ // Promise is not yet resolved, put the listener in our queue.
105
+ auto sharedCallback = jni::make_global(callback);
106
+ _onResolvedListeners.emplace_back([=](const auto& result) { sharedCallback->onResolved(result); });
107
+ }
108
+ }
109
+ void addOnRejectedListenerJava(jni::alias_ref<JOnRejectedCallback> callback) {
110
+ std::unique_lock lock(_mutex);
111
+ if (_error != nullptr) {
112
+ // Promise is already rejected! Call the callback immediately
113
+ callback->onRejected(_error);
114
+ } else {
115
+ // Promise is not yet rejected, put the listener in our queue.
116
+ auto sharedCallback = jni::make_global(callback);
117
+ _onRejectedListeners.emplace_back([=](const auto& error) { sharedCallback->onRejected(error); });
118
+ }
119
+ }
120
+
121
+ private:
122
+ JPromise() = default;
69
123
 
70
124
  private:
71
125
  friend HybridBase;
72
126
  using HybridBase::HybridBase;
73
127
  jni::global_ref<jni::JObject> _result;
74
- jni::global_ref<jni::JString> _error;
128
+ jni::global_ref<jni::JThrowable> _error;
75
129
  std::vector<OnResolvedFunc> _onResolvedListeners;
76
130
  std::vector<OnRejectedFunc> _onRejectedListeners;
131
+ std::mutex _mutex;
77
132
 
78
133
  public:
79
134
  static void registerNatives() {
@@ -81,6 +136,8 @@ public:
81
136
  makeNativeMethod("initHybrid", JPromise::initHybrid),
82
137
  makeNativeMethod("nativeResolve", JPromise::resolve),
83
138
  makeNativeMethod("nativeReject", JPromise::reject),
139
+ makeNativeMethod("addOnResolvedListener", JPromise::addOnResolvedListenerJava),
140
+ makeNativeMethod("addOnRejectedListener", JPromise::addOnRejectedListenerJava),
84
141
  });
85
142
  }
86
143
  };
@@ -0,0 +1,38 @@
1
+ //
2
+ // JUnit.hpp
3
+ // react-native-nitro
4
+ //
5
+ // Created by Marc Rousavy on 19.11.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "NitroLogger.hpp"
11
+ #include <fbjni/fbjni.h>
12
+
13
+ namespace margelo::nitro {
14
+
15
+ using namespace facebook;
16
+
17
+ /**
18
+ * Represents a `Unit` from Kotlin.
19
+ * This is similar to `void` for Java, but is actually an `Object`.
20
+ */
21
+ class JUnit final {
22
+ public:
23
+ /**
24
+ * Gets the shared instance to `Unit`. This is always a static global.
25
+ */
26
+ static jni::alias_ref<jni::JObject> instance() {
27
+ static jni::global_ref<jni::JObject> sharedInstance = nullptr;
28
+ if (sharedInstance == nullptr) {
29
+ jni::alias_ref<jni::JClass> clazz = jni::findClassStatic("java/lang/Object");
30
+ jni::JConstructor<jobject()> constructor = clazz->getConstructor<jobject()>();
31
+ jni::local_ref<jobject> instance = clazz->newObject(constructor);
32
+ sharedInstance = jni::make_global(instance);
33
+ }
34
+ return sharedInstance;
35
+ }
36
+ };
37
+
38
+ } // namespace margelo::nitro
@@ -5,9 +5,11 @@ import com.facebook.jni.HybridData
5
5
  import com.facebook.proguard.annotations.DoNotStrip
6
6
  import kotlinx.coroutines.CoroutineScope
7
7
  import kotlinx.coroutines.Dispatchers
8
- import kotlinx.coroutines.async
9
8
  import kotlinx.coroutines.launch
10
9
  import kotlin.concurrent.thread
10
+ import kotlin.coroutines.resume
11
+ import kotlin.coroutines.resumeWithException
12
+ import kotlin.coroutines.suspendCoroutine
11
13
 
12
14
  /**
13
15
  * Represents a Promise that can be passed to JS.
@@ -23,17 +25,26 @@ import kotlin.concurrent.thread
23
25
  @Keep
24
26
  @DoNotStrip
25
27
  class Promise<T> {
28
+ @Keep
29
+ @DoNotStrip
26
30
  private val mHybridData: HybridData
27
31
 
28
32
  /**
29
33
  * Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions
30
34
  */
31
- init {
35
+ constructor() {
32
36
  mHybridData = initHybrid()
33
37
  }
34
38
 
39
+ @Suppress("unused")
40
+ @Keep
41
+ @DoNotStrip
42
+ private constructor(hybridData: HybridData) {
43
+ mHybridData = hybridData
44
+ }
45
+
35
46
  /**
36
- * Resolves the Promise with the given result.
47
+ * Resolves the Promise with the given [result].
37
48
  * Any `onResolved` listeners will be invoked.
38
49
  */
39
50
  fun resolve(result: T) {
@@ -41,18 +52,73 @@ class Promise<T> {
41
52
  }
42
53
 
43
54
  /**
44
- * Rejects the Promise with the given error.
55
+ * Rejects the Promise with the given [error].
45
56
  * Any `onRejected` listeners will be invoked.
46
57
  */
47
- fun reject(error: Error) {
48
- nativeReject(error.toString())
58
+ fun reject(error: Throwable) {
59
+ nativeReject(error)
60
+ }
61
+
62
+ /**
63
+ * Add a continuation listener to this `Promise<T>`.
64
+ * Once the `Promise<T>` resolves, the [listener] will be called.
65
+ */
66
+ fun then(listener: (result: T) -> Unit): Promise<T> {
67
+ addOnResolvedListener { boxedResult ->
68
+ @Suppress("UNCHECKED_CAST")
69
+ val result = boxedResult as? T ?: throw Error("Failed to cast Object to T!")
70
+ listener(result)
71
+ }
72
+ return this
73
+ }
74
+
75
+ /**
76
+ * Add an error continuation listener to this `Promise<T>`.
77
+ * Once the `Promise<T>` rejects, the [listener] will be called with the error.
78
+ */
79
+ fun catch(listener: (throwable: Throwable) -> Unit): Promise<T> {
80
+ addOnRejectedListener(listener)
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Asynchronously await the result of the Promise.
86
+ * If the Promise is already resolved/rejected, this will continue immediately,
87
+ * otherwise it will asynchronously wait for a result or throw on a rejection.
88
+ * This function can only be used from a coroutine context.
89
+ */
90
+ suspend fun await(): T {
91
+ return suspendCoroutine { continuation ->
92
+ then { result -> continuation.resume(result) }
93
+ catch { error -> continuation.resumeWithException(error) }
94
+ }
49
95
  }
50
96
 
51
97
  // C++ functions
52
98
  private external fun nativeResolve(result: Any)
53
- private external fun nativeReject(error: String)
99
+ private external fun nativeReject(error: Throwable)
100
+ private external fun addOnResolvedListener(callback: OnResolvedCallback)
101
+ private external fun addOnRejectedListener(callback: OnRejectedCallback)
54
102
  private external fun initHybrid(): HybridData
55
103
 
104
+ // Nested callbacks - need to be JavaClasses so we can access them with JNI
105
+ @Keep
106
+ @DoNotStrip
107
+ private fun interface OnResolvedCallback {
108
+ @Suppress("unused")
109
+ @Keep
110
+ @DoNotStrip
111
+ fun onResolved(result: Any)
112
+ }
113
+ @Keep
114
+ @DoNotStrip
115
+ private fun interface OnRejectedCallback {
116
+ @Suppress("unused")
117
+ @Keep
118
+ @DoNotStrip
119
+ fun onRejected(error: Throwable)
120
+ }
121
+
56
122
  companion object {
57
123
  private val defaultScope = CoroutineScope(Dispatchers.Default)
58
124
 
@@ -71,7 +137,7 @@ class Promise<T> {
71
137
  try {
72
138
  val result = run()
73
139
  promise.resolve(result)
74
- } catch (e: Error) {
140
+ } catch (e: Throwable) {
75
141
  promise.reject(e)
76
142
  }
77
143
  }
@@ -91,7 +157,7 @@ class Promise<T> {
91
157
  try {
92
158
  val result = run()
93
159
  promise.resolve(result)
94
- } catch (e: Error) {
160
+ } catch (e: Throwable) {
95
161
  promise.reject(e)
96
162
  }
97
163
  }
@@ -108,7 +174,7 @@ class Promise<T> {
108
174
  /**
109
175
  * Creates a new Promise that is already rejected with the given error.
110
176
  */
111
- fun <T> rejected(error: Error): Promise<T> {
177
+ fun <T> rejected(error: Throwable): Promise<T> {
112
178
  return Promise<T>().apply { reject(error) }
113
179
  }
114
180
  }
@@ -16,6 +16,7 @@ struct JSIConverter;
16
16
  #include "JSIConverter.hpp"
17
17
  #include "NitroDefines.hpp"
18
18
  #include "TypeInfo.hpp"
19
+ #include <exception>
19
20
  #include <functional>
20
21
  #include <jsi/jsi.h>
21
22
  #include <memory>
@@ -0,0 +1,10 @@
1
+ //
2
+ // Promise.cpp
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 18.11.24.
6
+ //
7
+
8
+ #include "Promise.hpp"
9
+
10
+ namespace margelo::nitro {} // namespace margelo::nitro