expo-modules-core 0.9.0 → 0.10.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/android/CMakeLists.txt +154 -0
  3. package/android/build.gradle +293 -5
  4. package/android/src/main/cpp/Exceptions.cpp +22 -0
  5. package/android/src/main/cpp/Exceptions.h +38 -0
  6. package/android/src/main/cpp/ExpoModulesHostObject.cpp +47 -0
  7. package/android/src/main/cpp/ExpoModulesHostObject.h +32 -0
  8. package/android/src/main/cpp/JNIFunctionBody.cpp +29 -0
  9. package/android/src/main/cpp/JNIFunctionBody.h +50 -0
  10. package/android/src/main/cpp/JNIInjector.cpp +19 -0
  11. package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +122 -0
  12. package/android/src/main/cpp/JSIInteropModuleRegistry.h +96 -0
  13. package/android/src/main/cpp/JSIObjectWrapper.h +33 -0
  14. package/android/src/main/cpp/JSITypeConverter.h +84 -0
  15. package/android/src/main/cpp/JavaScriptModuleObject.cpp +138 -0
  16. package/android/src/main/cpp/JavaScriptModuleObject.h +122 -0
  17. package/android/src/main/cpp/JavaScriptObject.cpp +125 -0
  18. package/android/src/main/cpp/JavaScriptObject.h +131 -0
  19. package/android/src/main/cpp/JavaScriptRuntime.cpp +127 -0
  20. package/android/src/main/cpp/JavaScriptRuntime.h +87 -0
  21. package/android/src/main/cpp/JavaScriptValue.cpp +172 -0
  22. package/android/src/main/cpp/JavaScriptValue.h +78 -0
  23. package/android/src/main/cpp/MethodMetadata.cpp +230 -0
  24. package/android/src/main/cpp/MethodMetadata.h +92 -0
  25. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +2 -0
  26. package/android/src/main/java/expo/modules/core/errors/ContextDestroyedException.kt +7 -0
  27. package/android/src/main/java/expo/modules/interfaces/permissions/Permissions.java +30 -0
  28. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +49 -1
  29. package/android/src/main/java/expo/modules/kotlin/ConcatIterator.kt +18 -0
  30. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +15 -12
  31. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +39 -3
  32. package/android/src/main/java/expo/modules/kotlin/defaultmodules/ErrorManagerModule.kt +2 -2
  33. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +13 -0
  34. package/android/src/main/java/expo/modules/kotlin/exception/ExceptionDecorator.kt +2 -0
  35. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +19 -14
  36. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +29 -7
  37. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionBuilder.kt +13 -13
  38. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionComponent.kt +18 -0
  39. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromiseComponent.kt +18 -0
  40. package/android/src/main/java/expo/modules/kotlin/functions/SuspendFunctionComponent.kt +56 -0
  41. package/android/src/main/java/expo/modules/kotlin/functions/SyncFunctionComponent.kt +28 -0
  42. package/android/src/main/java/expo/modules/kotlin/jni/CppType.kt +18 -0
  43. package/android/src/main/java/expo/modules/kotlin/jni/JNIFunctionBody.kt +39 -0
  44. package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +89 -0
  45. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +44 -0
  46. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptObject.kt +113 -0
  47. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptValue.kt +35 -0
  48. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +15 -5
  49. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +65 -111
  50. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +35 -2
  51. package/android/src/main/java/expo/modules/kotlin/providers/AppContextProvider.kt +14 -0
  52. package/android/src/main/java/expo/modules/kotlin/providers/CurrentActivityProvider.kt +22 -0
  53. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +19 -2
  54. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +3 -2
  55. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +7 -2
  56. package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +68 -20
  57. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +50 -22
  58. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +18 -2
  59. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +18 -2
  60. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +17 -2
  61. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +43 -3
  62. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +5 -0
  63. package/build/NativeModulesProxy.native.d.ts.map +1 -1
  64. package/build/NativeModulesProxy.native.js +9 -3
  65. package/build/NativeModulesProxy.native.js.map +1 -1
  66. package/ios/AppDelegates/EXAppDelegatesLoader.m +1 -2
  67. package/ios/ExpoModulesCore.podspec +1 -1
  68. package/ios/JSI/EXJSIConversions.mm +6 -0
  69. package/ios/JSI/EXJSIInstaller.h +15 -21
  70. package/ios/JSI/EXJSIInstaller.mm +39 -3
  71. package/ios/JSI/EXJSIUtils.h +47 -3
  72. package/ios/JSI/EXJSIUtils.mm +88 -4
  73. package/ios/JSI/EXJavaScriptObject.h +11 -18
  74. package/ios/JSI/EXJavaScriptObject.mm +37 -18
  75. package/ios/JSI/EXJavaScriptRuntime.h +43 -9
  76. package/ios/JSI/EXJavaScriptRuntime.mm +70 -27
  77. package/ios/JSI/EXJavaScriptTypedArray.h +30 -0
  78. package/ios/JSI/EXJavaScriptTypedArray.mm +29 -0
  79. package/ios/JSI/EXJavaScriptValue.h +3 -2
  80. package/ios/JSI/EXJavaScriptValue.mm +17 -20
  81. package/ios/JSI/EXJavaScriptWeakObject.h +23 -0
  82. package/ios/JSI/EXJavaScriptWeakObject.mm +53 -0
  83. package/ios/JSI/EXObjectDeallocator.h +27 -0
  84. package/ios/JSI/ExpoModulesHostObject.h +3 -3
  85. package/ios/JSI/ExpoModulesHostObject.mm +4 -4
  86. package/ios/JSI/JavaScriptRuntime.swift +38 -1
  87. package/ios/JSI/JavaScriptValue.swift +7 -0
  88. package/ios/JSI/TypedArray.cpp +67 -0
  89. package/ios/JSI/TypedArray.h +46 -0
  90. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +0 -11
  91. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +17 -10
  92. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +88 -77
  93. package/ios/NativeModulesProxy/NativeModulesProxyModule.swift +17 -0
  94. package/ios/Services/EXReactNativeEventEmitter.h +2 -2
  95. package/ios/Services/EXReactNativeEventEmitter.m +11 -6
  96. package/ios/Swift/AppContext.swift +208 -28
  97. package/ios/Swift/Arguments/AnyArgument.swift +18 -0
  98. package/ios/Swift/Arguments/{Types/EnumArgumentType.swift → EnumArgument.swift} +2 -17
  99. package/ios/Swift/Classes/ClassComponent.swift +95 -0
  100. package/ios/Swift/Classes/ClassComponentElement.swift +33 -0
  101. package/ios/Swift/Classes/ClassComponentElementsBuilder.swift +34 -0
  102. package/ios/Swift/Classes/ClassComponentFactories.swift +96 -0
  103. package/ios/Swift/DynamicTypes/AnyDynamicType.swift +44 -0
  104. package/ios/Swift/DynamicTypes/DynamicArrayType.swift +56 -0
  105. package/ios/Swift/DynamicTypes/DynamicConvertibleType.swift +27 -0
  106. package/ios/Swift/DynamicTypes/DynamicEnumType.swift +27 -0
  107. package/ios/Swift/DynamicTypes/DynamicOptionalType.swift +63 -0
  108. package/ios/Swift/DynamicTypes/DynamicRawType.swift +33 -0
  109. package/ios/Swift/DynamicTypes/DynamicSharedObjectType.swift +37 -0
  110. package/ios/Swift/DynamicTypes/DynamicType.swift +39 -0
  111. package/ios/Swift/DynamicTypes/DynamicTypedArrayType.swift +46 -0
  112. package/ios/Swift/Exceptions/CodedError.swift +1 -1
  113. package/ios/Swift/Exceptions/Exception.swift +8 -6
  114. package/ios/Swift/Exceptions/UnexpectedException.swift +2 -1
  115. package/ios/Swift/ExpoBridgeModule.m +5 -0
  116. package/ios/Swift/ExpoBridgeModule.swift +65 -0
  117. package/ios/Swift/Functions/AnyFunction.swift +33 -31
  118. package/ios/Swift/Functions/AsyncFunctionComponent.swift +196 -59
  119. package/ios/Swift/Functions/SyncFunctionComponent.swift +142 -58
  120. package/ios/Swift/JavaScriptUtils.swift +32 -57
  121. package/ios/Swift/Logging/LogHandlers.swift +39 -0
  122. package/ios/Swift/Logging/LogType.swift +62 -0
  123. package/ios/Swift/Logging/Logger.swift +198 -0
  124. package/ios/Swift/ModuleHolder.swift +19 -54
  125. package/ios/Swift/ModuleRegistry.swift +7 -1
  126. package/ios/Swift/Modules/AnyModule.swift +3 -3
  127. package/ios/Swift/ModulesProvider.swift +2 -0
  128. package/ios/Swift/Objects/JavaScriptObjectBuilder.swift +37 -0
  129. package/ios/Swift/Objects/ObjectDefinition.swift +74 -1
  130. package/ios/Swift/Objects/ObjectDefinitionComponents.swift +77 -68
  131. package/ios/Swift/Objects/PropertyComponent.swift +147 -0
  132. package/ios/Swift/Promise.swift +12 -3
  133. package/ios/Swift/Records/Field.swift +2 -2
  134. package/ios/Swift/SharedObjects/SharedObject.swift +20 -0
  135. package/ios/Swift/SharedObjects/SharedObjectRegistry.swift +129 -0
  136. package/ios/Swift/TypedArrays/AnyTypedArray.swift +11 -0
  137. package/ios/Swift/TypedArrays/ConcreteTypedArrays.swift +56 -0
  138. package/ios/Swift/TypedArrays/GenericTypedArray.swift +49 -0
  139. package/ios/Swift/TypedArrays/TypedArray.swift +80 -0
  140. package/ios/Swift/Utilities.swift +28 -0
  141. package/ios/Swift/Views/ConcreteViewProp.swift +3 -3
  142. package/ios/Swift/Views/ViewManagerDefinitionComponents.swift +2 -2
  143. package/ios/Tests/ClassComponentSpec.swift +210 -0
  144. package/ios/Tests/DynamicTypeSpec.swift +336 -0
  145. package/ios/Tests/EnumArgumentSpec.swift +48 -0
  146. package/ios/Tests/ExpoModulesSpec.swift +17 -3
  147. package/ios/Tests/FunctionSpec.swift +167 -118
  148. package/ios/Tests/Mocks/ModuleMocks.swift +1 -1
  149. package/ios/Tests/PropertyComponentSpec.swift +95 -0
  150. package/ios/Tests/SharedObjectRegistrySpec.swift +109 -0
  151. package/ios/Tests/TypedArraysSpec.swift +136 -0
  152. package/package.json +2 -2
  153. package/src/NativeModulesProxy.native.ts +13 -3
  154. package/src/ts-declarations/ExpoModules.d.ts +7 -0
  155. package/tsconfig.json +1 -1
  156. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromise.kt +0 -15
  157. package/android/src/main/java/expo/modules/kotlin/functions/AsyncSuspendFunction.kt +0 -36
  158. package/ios/Swift/Arguments/AnyArgumentType.swift +0 -13
  159. package/ios/Swift/Arguments/ArgumentType.swift +0 -28
  160. package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +0 -42
  161. package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +0 -16
  162. package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +0 -49
  163. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +0 -15
  164. package/ios/Swift/Arguments/Types/RawArgumentType.swift +0 -25
  165. package/ios/Swift/Functions/ConcreteFunction.swift +0 -103
  166. package/ios/Swift/SwiftInteropBridge.swift +0 -155
  167. package/ios/Tests/ArgumentTypeSpec.swift +0 -143
@@ -0,0 +1,78 @@
1
+ // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
+
3
+ #pragma once
4
+
5
+ #include "JSIObjectWrapper.h"
6
+
7
+ #include <fbjni/fbjni.h>
8
+ #include <jsi/jsi.h>
9
+
10
+ #include <memory>
11
+
12
+ namespace jni = facebook::jni;
13
+ namespace jsi = facebook::jsi;
14
+
15
+ namespace expo {
16
+ class JavaScriptRuntime;
17
+
18
+ class JavaScriptObject;
19
+
20
+ /**
21
+ * Represents any JavaScript value. Its purpose is to expose the `jsi::Value` API back to Kotlin.
22
+ */
23
+ class JavaScriptValue : public jni::HybridClass<JavaScriptValue>, JSIValueWrapper {
24
+ public:
25
+ static auto constexpr
26
+ kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptValue;";
27
+ static auto constexpr TAG = "JavaScriptValue";
28
+
29
+ static void registerNatives();
30
+
31
+ JavaScriptValue(
32
+ std::weak_ptr<JavaScriptRuntime> runtime,
33
+ std::shared_ptr<jsi::Value> jsValue
34
+ );
35
+
36
+ std::shared_ptr<jsi::Value> get() override;
37
+
38
+ std::string kind();
39
+
40
+ bool isNull();
41
+
42
+ bool isUndefined();
43
+
44
+ bool isBool();
45
+
46
+ bool isNumber();
47
+
48
+ bool isString();
49
+
50
+ bool isSymbol();
51
+
52
+ bool isFunction();
53
+
54
+ bool isArray();
55
+
56
+ bool isObject();
57
+
58
+ bool getBool();
59
+
60
+ double getDouble();
61
+
62
+ std::string getString();
63
+
64
+ jni::local_ref<jni::HybridClass<JavaScriptObject>::javaobject> getObject();
65
+
66
+ jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> getArray();
67
+
68
+ private:
69
+ friend HybridBase;
70
+
71
+ std::weak_ptr<JavaScriptRuntime> runtimeHolder;
72
+ std::shared_ptr<jsi::Value> jsValue;
73
+
74
+ jni::local_ref<jstring> jniKind();
75
+
76
+ jni::local_ref<jstring> jniGetString();
77
+ };
78
+ } // namespace expo
@@ -0,0 +1,230 @@
1
+ #include "MethodMetadata.h"
2
+
3
+ #include "JSIInteropModuleRegistry.h"
4
+
5
+ namespace jni = facebook::jni;
6
+ namespace jsi = facebook::jsi;
7
+ namespace react = facebook::react;
8
+
9
+ namespace expo {
10
+
11
+ // Modified version of the RN implementation
12
+ // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
13
+ jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
14
+ jsi::Function &&function,
15
+ jsi::Runtime &rt,
16
+ std::shared_ptr<react::CallInvoker> jsInvoker
17
+ ) {
18
+ auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
19
+ std::move(jsInvoker));
20
+
21
+ // This needs to be a shared_ptr because:
22
+ // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
23
+ // not.
24
+ // 2. It cannot be weak_ptr since we need this object to live on.
25
+ // 3. It cannot be a value, because that would be deleted as soon as this
26
+ // function returns.
27
+ auto callbackWrapperOwner =
28
+ std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
29
+
30
+ std::function<void(folly::dynamic)> fn =
31
+ [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
32
+ folly::dynamic responses) mutable {
33
+ if (wrapperWasCalled) {
34
+ throw std::runtime_error(
35
+ "callback 2 arg cannot be called more than once");
36
+ }
37
+
38
+ auto strongWrapper = weakWrapper.lock();
39
+ if (!strongWrapper) {
40
+ return;
41
+ }
42
+
43
+ strongWrapper->jsInvoker().invokeAsync(
44
+ [weakWrapper, callbackWrapperOwner, responses]() mutable {
45
+ auto strongWrapper2 = weakWrapper.lock();
46
+ if (!strongWrapper2) {
47
+ return;
48
+ }
49
+
50
+ jsi::Value args =
51
+ jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
52
+ auto argsArray = args.getObject(strongWrapper2->runtime())
53
+ .asArray(strongWrapper2->runtime());
54
+ jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
55
+
56
+ strongWrapper2->callback().call(
57
+ strongWrapper2->runtime(),
58
+ (const jsi::Value *) &arg,
59
+ (size_t) 1
60
+ );
61
+
62
+ callbackWrapperOwner.reset();
63
+ });
64
+
65
+ wrapperWasCalled = true;
66
+ };
67
+
68
+ return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
69
+ }
70
+
71
+ MethodMetadata::MethodMetadata(
72
+ std::string name,
73
+ int args,
74
+ bool isAsync,
75
+ jni::global_ref<jobject> &&jBodyReference
76
+ ) : name(name),
77
+ args(args),
78
+ isAsync(isAsync),
79
+ jBodyReference(jBodyReference) {}
80
+
81
+ std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
82
+ jsi::Runtime &runtime,
83
+ JSIInteropModuleRegistry *moduleRegistry
84
+ ) {
85
+ if (body == nullptr) {
86
+ if (isAsync) {
87
+ body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
88
+ } else {
89
+ body = std::make_shared<jsi::Function>(toSyncFunction(runtime));
90
+ }
91
+ }
92
+
93
+ return body;
94
+ }
95
+
96
+ jsi::Function MethodMetadata::toSyncFunction(jsi::Runtime &runtime) {
97
+ return jsi::Function::createFromHostFunction(
98
+ runtime,
99
+ jsi::PropNameID::forAscii(runtime, name),
100
+ args,
101
+ [this](
102
+ jsi::Runtime &rt,
103
+ const jsi::Value &thisValue,
104
+ const jsi::Value *args,
105
+ size_t count
106
+ ) -> jsi::Value {
107
+ auto dynamicArray = folly::dynamic::array();
108
+ for (int i = 0; i < count; i++) {
109
+ auto &arg = args[i];
110
+ dynamicArray.push_back(jsi::dynamicFromValue(rt, arg));
111
+ }
112
+
113
+ // Cast in this place is safe, cause we know that this function is promise-less.
114
+ auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
115
+ auto result = syncFunction->invoke(
116
+ react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamicArray)).get()
117
+ );
118
+
119
+ if (result == nullptr) {
120
+ return jsi::Value::undefined();
121
+ }
122
+
123
+ return jsi::valueFromDynamic(rt, result->cthis()->consume())
124
+ .asObject(rt)
125
+ .asArray(rt)
126
+ .getValueAtIndex(rt, 0);
127
+ });
128
+ }
129
+
130
+ jsi::Function MethodMetadata::toAsyncFunction(
131
+ jsi::Runtime &runtime,
132
+ JSIInteropModuleRegistry *moduleRegistry
133
+ ) {
134
+ return jsi::Function::createFromHostFunction(
135
+ runtime,
136
+ jsi::PropNameID::forAscii(runtime, name),
137
+ args,
138
+ [this, moduleRegistry](
139
+ jsi::Runtime &rt,
140
+ const jsi::Value &thisValue,
141
+ const jsi::Value *args,
142
+ size_t count
143
+ ) -> jsi::Value {
144
+ auto dynamicArray = folly::dynamic::array();
145
+ for (int i = 0; i < count; i++) {
146
+ auto &arg = args[i];
147
+ dynamicArray.push_back(jsi::dynamicFromValue(rt, arg));
148
+ }
149
+
150
+ auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
151
+ // Creates a JSI promise
152
+ jsi::Value promise = Promise.callAsConstructor(
153
+ rt,
154
+ createPromiseBody(rt, moduleRegistry, std::move(dynamicArray))
155
+ );
156
+ return promise;
157
+ }
158
+ );
159
+ }
160
+
161
+ jsi::Function MethodMetadata::createPromiseBody(
162
+ jsi::Runtime &runtime,
163
+ JSIInteropModuleRegistry *moduleRegistry,
164
+ folly::dynamic &&args
165
+ ) {
166
+ return jsi::Function::createFromHostFunction(
167
+ runtime,
168
+ jsi::PropNameID::forAscii(runtime, "promiseFn"),
169
+ 2,
170
+ [this, args = std::move(args), moduleRegistry](
171
+ jsi::Runtime &rt,
172
+ const jsi::Value &thisVal,
173
+ const jsi::Value *promiseConstructorArgs,
174
+ size_t promiseConstructorArgCount
175
+ ) {
176
+ if (promiseConstructorArgCount != 2) {
177
+ throw std::invalid_argument("Promise fn arg count must be 2");
178
+ }
179
+
180
+ jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
181
+ jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
182
+
183
+ auto &runtimeHolder = moduleRegistry->runtimeHolder;
184
+ jobject resolve = createJavaCallbackFromJSIFunction(
185
+ std::move(resolveJSIFn),
186
+ rt,
187
+ runtimeHolder->jsInvoker
188
+ ).release();
189
+
190
+ jobject reject = createJavaCallbackFromJSIFunction(
191
+ std::move(rejectJSIFn),
192
+ rt,
193
+ runtimeHolder->jsInvoker
194
+ ).release();
195
+
196
+ JNIEnv *env = jni::Environment::current();
197
+
198
+ jclass jPromiseImpl =
199
+ env->FindClass("com/facebook/react/bridge/PromiseImpl");
200
+ jmethodID jPromiseImplConstructor = env->GetMethodID(
201
+ jPromiseImpl,
202
+ "<init>",
203
+ "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V");
204
+
205
+ // Creates a promise object
206
+ jobject promise = env->NewObject(
207
+ jPromiseImpl,
208
+ jPromiseImplConstructor,
209
+ resolve,
210
+ reject
211
+ );
212
+
213
+ // Cast in this place is safe, cause we know that this function expects promise.
214
+ auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
215
+ asyncFunction->invoke(
216
+ react::ReadableNativeArray::newObjectCxxArgs(args).get(),
217
+ promise
218
+ );
219
+
220
+ // We have to remove the local reference to the promise object.
221
+ // It doesn't mean that the promise will be deallocated, but rather that we move
222
+ // the ownership to the `JNIAsyncFunctionBody`.
223
+ env->DeleteLocalRef(promise);
224
+
225
+ return jsi::Value::undefined();
226
+ }
227
+ );
228
+ }
229
+
230
+ } // namespace expo
@@ -0,0 +1,92 @@
1
+ // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
+
3
+ #pragma once
4
+
5
+ #include <jsi/jsi.h>
6
+ #include <fbjni/fbjni.h>
7
+ #include <ReactCommon/TurboModuleUtils.h>
8
+ #include <react/jni/ReadableNativeArray.h>
9
+ #include <memory>
10
+ #include <folly/dynamic.h>
11
+ #include <jsi/JSIDynamic.h>
12
+
13
+ namespace jni = facebook::jni;
14
+ namespace jsi = facebook::jsi;
15
+ namespace react = facebook::react;
16
+
17
+ namespace expo {
18
+ class JSIInteropModuleRegistry;
19
+
20
+ /**
21
+ * A class that holds information about the exported function.
22
+ */
23
+ class MethodMetadata {
24
+ public:
25
+ /**
26
+ * Function name
27
+ */
28
+ std::string name;
29
+ /**
30
+ * Number of arguments
31
+ */
32
+ int args;
33
+ /*
34
+ * Whether this function is async
35
+ */
36
+ bool isAsync;
37
+
38
+ MethodMetadata(
39
+ std::string name,
40
+ int args,
41
+ bool isAsync,
42
+ jni::global_ref<jobject> &&jBodyReference
43
+ );
44
+
45
+ // We deleted the copy contractor to not deal with transforming the ownership of the `jBodyReference`.
46
+ MethodMetadata(const MethodMetadata &) = delete;
47
+
48
+ /**
49
+ * MethodMetadata owns the only reference to the Kotlin function.
50
+ * We have to clean that, cause it's a `global_ref`.
51
+ */
52
+ ~MethodMetadata() {
53
+ jBodyReference.release();
54
+ }
55
+
56
+ /**
57
+ * Transforms metadata to a jsi::Function.
58
+ *
59
+ * @param runtime
60
+ * @param moduleRegistry
61
+ * @return shared ptr to the jsi::Function that wrapped the underlying Kotlin's function.
62
+ */
63
+ std::shared_ptr<jsi::Function> toJSFunction(
64
+ jsi::Runtime &runtime,
65
+ JSIInteropModuleRegistry *moduleRegistry
66
+ );
67
+
68
+ private:
69
+ /**
70
+ * Reference to one of two java objects - `JNIFunctionBody` or `JNIAsyncFunctionBody`.
71
+ *
72
+ * In case when `isAsync` is `true`, this variable will point to `JNIAsyncFunctionBody`.
73
+ * Otherwise to `JNIFunctionBody`
74
+ */
75
+ jni::global_ref<jobject> jBodyReference;
76
+
77
+ /**
78
+ * To not create a jsi::Function always when we need it, we cached that value.
79
+ */
80
+ std::shared_ptr<jsi::Function> body = nullptr;
81
+
82
+ jsi::Function toSyncFunction(jsi::Runtime &runtime);
83
+
84
+ jsi::Function toAsyncFunction(jsi::Runtime &runtime, JSIInteropModuleRegistry *moduleRegistry);
85
+
86
+ jsi::Function createPromiseBody(
87
+ jsi::Runtime &runtime,
88
+ JSIInteropModuleRegistry *moduleRegistry,
89
+ folly::dynamic &&args
90
+ );
91
+ };
92
+ } // namespace expo
@@ -92,6 +92,8 @@ public class NativeModulesProxy extends ReactContextBaseJavaModule {
92
92
  @Override
93
93
  public Map<String, Object> getConstants() {
94
94
  mModuleRegistry.ensureIsInitialized();
95
+ getKotlinInteropModuleRegistry().installJSIInterop();
96
+
95
97
  Collection<ExportedModule> exportedModules = mModuleRegistry.getAllExportedModules();
96
98
  Collection<ViewManager> viewManagers = mModuleRegistry.getAllViewManagers();
97
99
 
@@ -0,0 +1,7 @@
1
+ package expo.modules.core.errors
2
+
3
+ import kotlinx.coroutines.CancellationException
4
+
5
+ private const val DEFAULT_MESSAGE = "App context destroyed. All coroutines are cancelled."
6
+
7
+ class ContextDestroyedException(message: String = DEFAULT_MESSAGE) : CancellationException(message)
@@ -1,5 +1,7 @@
1
1
  package expo.modules.interfaces.permissions;
2
2
 
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
3
5
  import expo.modules.core.Promise;
4
6
 
5
7
  public interface Permissions {
@@ -12,6 +14,20 @@ public interface Permissions {
12
14
  permissionsManager.getPermissionsWithPromise(promise, permissions);
13
15
  }
14
16
 
17
+ /**
18
+ * Compatibility method that accepts expo.modules.kotlin.Promise, but forward the logic to the other method
19
+ */
20
+ static void getPermissionsWithPermissionsManager(
21
+ @Nullable Permissions permissionsManager,
22
+ @NonNull final expo.modules.kotlin.Promise promise,
23
+ @NonNull String... permissions
24
+ ) {
25
+ getPermissionsWithPermissionsManager(permissionsManager, new Promise() {
26
+ @Override public void resolve(Object value) { promise.resolve(value); }
27
+ @Override public void reject(String c, String m, Throwable e) { promise.reject(c, m, e); }
28
+ }, permissions);
29
+ }
30
+
15
31
  static void askForPermissionsWithPermissionsManager(Permissions permissionsManager, final Promise promise, String... permissions) {
16
32
  if (permissionsManager == null) {
17
33
  promise.reject("E_NO_PERMISSIONS", "Permissions module is null. Are you sure all the installed Expo modules are properly linked?");
@@ -20,6 +36,20 @@ public interface Permissions {
20
36
  permissionsManager.askForPermissionsWithPromise(promise, permissions);
21
37
  }
22
38
 
39
+ /**
40
+ * Compatibility method that accepts expo.modules.kotlin.Promise, but forward the logic to the other method
41
+ */
42
+ static void askForPermissionsWithPermissionsManager(
43
+ @Nullable Permissions permissionsManager,
44
+ @NonNull final expo.modules.kotlin.Promise promise,
45
+ @NonNull String... permissions
46
+ ) {
47
+ askForPermissionsWithPermissionsManager(permissionsManager, new Promise() {
48
+ @Override public void resolve(Object value) { promise.resolve(value); }
49
+ @Override public void reject(String c, String m, Throwable e) { promise.reject(c, m, e); }
50
+ }, permissions);
51
+ }
52
+
23
53
  void getPermissionsWithPromise(final Promise promise, String... permissions);
24
54
 
25
55
  void getPermissions(final PermissionsResponseListener response, String... permissions);
@@ -1,9 +1,14 @@
1
+ @file:OptIn(DelicateCoroutinesApi::class)
2
+
1
3
  package expo.modules.kotlin
2
4
 
3
5
  import android.app.Activity
4
6
  import android.content.Context
5
7
  import android.content.Intent
8
+ import androidx.appcompat.app.AppCompatActivity
6
9
  import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
11
+ import expo.modules.core.errors.ContextDestroyedException
7
12
  import expo.modules.core.interfaces.ActivityProvider
8
13
  import expo.modules.interfaces.barcodescanner.BarCodeScannerInterface
9
14
  import expo.modules.interfaces.camera.CameraViewInterface
@@ -20,19 +25,34 @@ import expo.modules.kotlin.events.EventName
20
25
  import expo.modules.kotlin.events.KEventEmitterWrapper
21
26
  import expo.modules.kotlin.events.KModuleEventEmitterWrapper
22
27
  import expo.modules.kotlin.events.OnActivityResultPayload
28
+ import expo.modules.kotlin.jni.JSIInteropModuleRegistry
23
29
  import expo.modules.kotlin.modules.Module
30
+ import expo.modules.kotlin.providers.CurrentActivityProvider
31
+ import kotlinx.coroutines.CoroutineName
32
+ import kotlinx.coroutines.CoroutineScope
33
+ import kotlinx.coroutines.DelicateCoroutinesApi
34
+ import kotlinx.coroutines.SupervisorJob
35
+ import kotlinx.coroutines.cancel
36
+ import kotlinx.coroutines.newSingleThreadContext
24
37
  import java.lang.ref.WeakReference
25
38
 
26
39
  class AppContext(
27
40
  modulesProvider: ModulesProvider,
28
41
  val legacyModuleRegistry: expo.modules.core.ModuleRegistry,
29
42
  private val reactContextHolder: WeakReference<ReactApplicationContext>
30
- ) {
43
+ ) : CurrentActivityProvider {
31
44
  val registry = ModuleRegistry(WeakReference(this)).apply {
32
45
  register(ErrorManagerModule())
33
46
  register(modulesProvider)
34
47
  }
35
48
  private val reactLifecycleDelegate = ReactLifecycleDelegate(this)
49
+ // We postpone creating the `JSIInteropModuleRegistry` to not load so files in unit tests.
50
+ private lateinit var jsiInterop: JSIInteropModuleRegistry
51
+ internal val modulesQueue = CoroutineScope(
52
+ newSingleThreadContext("ExpoModulesCoreQueue") +
53
+ SupervisorJob() +
54
+ CoroutineName("ExpoModulesCoreCoroutineQueue")
55
+ )
36
56
 
37
57
  init {
38
58
  requireNotNull(reactContextHolder.get()) {
@@ -43,6 +63,18 @@ class AppContext(
43
63
  }
44
64
  }
45
65
 
66
+ fun installJSIInterop() {
67
+ jsiInterop = JSIInteropModuleRegistry(this)
68
+ val reactContext = reactContextHolder.get() ?: return
69
+ reactContext.javaScriptContextHolder?.get()?.let {
70
+ jsiInterop.installJSI(
71
+ it,
72
+ reactContext.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl,
73
+ reactContext.catalystInstance.nativeCallInvokerHolder as CallInvokerHolderImpl
74
+ )
75
+ }
76
+ }
77
+
46
78
  /**
47
79
  * Returns a legacy module implementing given interface.
48
80
  */
@@ -149,6 +181,7 @@ class AppContext(
149
181
  reactContextHolder.get()?.removeLifecycleEventListener(reactLifecycleDelegate)
150
182
  registry.post(EventName.MODULE_DESTROY)
151
183
  registry.cleanUp()
184
+ modulesQueue.cancel(ContextDestroyedException())
152
185
  }
153
186
 
154
187
  fun onHostResume() {
@@ -181,4 +214,19 @@ class AppContext(
181
214
  intent
182
215
  )
183
216
  }
217
+
218
+ // region CurrentActivityProvider
219
+
220
+ override val currentActivity: AppCompatActivity?
221
+ get() {
222
+ val currentActivity = this.activityProvider?.currentActivity ?: return null
223
+
224
+ check(currentActivity is AppCompatActivity) {
225
+ "Current Activity is of incorrect class, expected AppCompatActivity, received ${currentActivity.localClassName}"
226
+ }
227
+
228
+ return currentActivity
229
+ }
230
+
231
+ // endregion
184
232
  }
@@ -0,0 +1,18 @@
1
+ package expo.modules.kotlin
2
+
3
+ /**
4
+ * Simple iterator that will merge two other iterators.
5
+ */
6
+ class ConcatIterator<T>(
7
+ private val first: Iterator<T>,
8
+ private val second: Iterator<T>
9
+ ) : Iterator<T> {
10
+ override fun hasNext(): Boolean = first.hasNext() || second.hasNext()
11
+
12
+ override fun next(): T =
13
+ if (first.hasNext()) {
14
+ first.next()
15
+ } else {
16
+ second.next()
17
+ }
18
+ }
@@ -41,26 +41,25 @@ class KotlinInteropModuleRegistry(
41
41
  }
42
42
 
43
43
  fun exportedModulesConstants(): Map<ModuleName, ModuleConstants> {
44
- return registry
45
- .map { holder ->
46
- holder.name to holder.definition.constantsProvider()
47
- }
48
- .toMap()
44
+ return registry.associate { holder ->
45
+ holder.name to holder.definition.constantsProvider()
46
+ }
49
47
  }
50
48
 
51
49
  fun exportMethods(exportKey: (String, List<ModuleMethodInfo>) -> Unit = { _, _ -> }): Map<ModuleName, List<ModuleMethodInfo>> {
52
- return registry
53
- .map { holder ->
54
- val methodsInfo = holder.definition.methods.map { (name, method) ->
50
+ return registry.associate { holder ->
51
+ val methodsInfo = holder
52
+ .definition
53
+ .asyncFunctions
54
+ .map { (name, method) ->
55
55
  mapOf(
56
56
  "name" to name,
57
57
  "argumentsCount" to method.argsCount
58
58
  )
59
59
  }
60
- exportKey(holder.name, methodsInfo)
61
- holder.name to methodsInfo
62
- }
63
- .toMap()
60
+ exportKey(holder.name, methodsInfo)
61
+ holder.name to methodsInfo
62
+ }
64
63
  }
65
64
 
66
65
  fun exportViewManagers(): List<ViewManager<*, *>> {
@@ -110,4 +109,8 @@ class KotlinInteropModuleRegistry(
110
109
  fun onDestroy() {
111
110
  appContext.onDestroy()
112
111
  }
112
+
113
+ fun installJSIInterop() {
114
+ appContext.installJSIInterop()
115
+ }
113
116
  }
@@ -1,5 +1,6 @@
1
1
  package expo.modules.kotlin
2
2
 
3
+ import com.facebook.react.bridge.Arguments
3
4
  import com.facebook.react.bridge.ReadableArray
4
5
  import expo.modules.kotlin.events.BasicEventListener
5
6
  import expo.modules.kotlin.events.EventListenerWithPayload
@@ -8,19 +9,54 @@ import expo.modules.kotlin.events.EventName
8
9
  import expo.modules.kotlin.exception.FunctionCallException
9
10
  import expo.modules.kotlin.exception.MethodNotFoundException
10
11
  import expo.modules.kotlin.exception.exceptionDecorator
12
+ import expo.modules.kotlin.jni.JavaScriptModuleObject
11
13
  import expo.modules.kotlin.modules.Module
14
+ import expo.modules.kotlin.modules.ProcessedModuleDefinition
12
15
 
13
16
  class ModuleHolder(val module: Module) {
14
- val definition = module.definition()
17
+ val definition = ProcessedModuleDefinition(module.definition(), this)
18
+
15
19
  val name get() = definition.name
16
20
 
21
+ /**
22
+ * Cached instance of HybridObject used by CPP to interact with underlying [expo.modules.kotlin.modules.Module] object.
23
+ */
24
+ val jsObject by lazy {
25
+ JavaScriptModuleObject()
26
+ .apply {
27
+ val constants = definition.constantsProvider()
28
+ val convertedConstants = Arguments.makeNativeMap(constants)
29
+ exportConstants(convertedConstants)
30
+
31
+ definition
32
+ .functions
33
+ .forEach { function ->
34
+ function.attachToJSObject(module.appContext, this)
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Invokes a function with promise. Is used in the bridge implementation of the Sweet API.
41
+ */
17
42
  fun call(methodName: String, args: ReadableArray, promise: Promise) = exceptionDecorator({
18
43
  FunctionCallException(methodName, definition.name, it)
19
44
  }) {
20
- val method = definition.methods[methodName]
45
+ val method = definition.asyncFunctions[methodName]
46
+ ?: throw MethodNotFoundException()
47
+
48
+ method.call(args, promise)
49
+ }
50
+
51
+ /**
52
+ * Invokes a function without promise.
53
+ * `callSync` was added only for test purpose and shouldn't be used anywhere else.
54
+ */
55
+ fun callSync(methodName: String, args: ReadableArray): Any? {
56
+ val method = definition.syncFunctions[methodName]
21
57
  ?: throw MethodNotFoundException()
22
58
 
23
- method.call(this, args, promise)
59
+ return method.call(args)
24
60
  }
25
61
 
26
62
  fun post(eventName: EventName) {