expo-modules-core 0.9.2 → 0.11.1

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 (193) hide show
  1. package/CHANGELOG.md +33 -3
  2. package/README.md +3 -3
  3. package/android/CMakeLists.txt +163 -0
  4. package/android/build.gradle +343 -5
  5. package/android/src/main/cpp/CachedReferencesRegistry.cpp +65 -0
  6. package/android/src/main/cpp/CachedReferencesRegistry.h +80 -0
  7. package/android/src/main/cpp/Exceptions.cpp +22 -0
  8. package/android/src/main/cpp/Exceptions.h +38 -0
  9. package/android/src/main/cpp/ExpoModulesHostObject.cpp +47 -0
  10. package/android/src/main/cpp/ExpoModulesHostObject.h +32 -0
  11. package/android/src/main/cpp/JNIFunctionBody.cpp +44 -0
  12. package/android/src/main/cpp/JNIFunctionBody.h +50 -0
  13. package/android/src/main/cpp/JNIInjector.cpp +23 -0
  14. package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +122 -0
  15. package/android/src/main/cpp/JSIInteropModuleRegistry.h +96 -0
  16. package/android/src/main/cpp/JSIObjectWrapper.h +33 -0
  17. package/android/src/main/cpp/JSITypeConverter.h +84 -0
  18. package/android/src/main/cpp/JavaScriptModuleObject.cpp +219 -0
  19. package/android/src/main/cpp/JavaScriptModuleObject.h +144 -0
  20. package/android/src/main/cpp/JavaScriptObject.cpp +125 -0
  21. package/android/src/main/cpp/JavaScriptObject.h +131 -0
  22. package/android/src/main/cpp/JavaScriptRuntime.cpp +127 -0
  23. package/android/src/main/cpp/JavaScriptRuntime.h +87 -0
  24. package/android/src/main/cpp/JavaScriptValue.cpp +172 -0
  25. package/android/src/main/cpp/JavaScriptValue.h +78 -0
  26. package/android/src/main/cpp/MethodMetadata.cpp +340 -0
  27. package/android/src/main/cpp/MethodMetadata.h +131 -0
  28. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +2 -0
  29. package/android/src/main/java/expo/modules/core/errors/ContextDestroyedException.kt +7 -0
  30. package/android/src/main/java/expo/modules/interfaces/permissions/Permissions.java +30 -0
  31. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +98 -3
  32. package/android/src/main/java/expo/modules/kotlin/ConcatIterator.kt +18 -0
  33. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +15 -12
  34. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +45 -3
  35. package/android/src/main/java/expo/modules/kotlin/activityaware/AppCompatActivityAware.kt +49 -0
  36. package/android/src/main/java/expo/modules/kotlin/activityaware/AppCompatActivityAwareHelper.kt +43 -0
  37. package/android/src/main/java/expo/modules/kotlin/activityaware/OnActivityAvailableListener.kt +18 -0
  38. package/android/src/main/java/expo/modules/kotlin/activityresult/ActivityResultsManager.kt +99 -0
  39. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultCaller.kt +25 -0
  40. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultContract.kt +27 -0
  41. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultFallbackCallback.kt +17 -0
  42. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt +30 -0
  43. package/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultRegistry.kt +358 -0
  44. package/android/src/main/java/expo/modules/kotlin/activityresult/DataPersistor.kt +135 -0
  45. package/android/src/main/java/expo/modules/kotlin/defaultmodules/ErrorManagerModule.kt +2 -2
  46. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +13 -0
  47. package/android/src/main/java/expo/modules/kotlin/exception/ExceptionDecorator.kt +2 -0
  48. package/android/src/main/java/expo/modules/kotlin/functions/AnyFunction.kt +53 -15
  49. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +35 -7
  50. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionBuilder.kt +13 -121
  51. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionComponent.kt +21 -0
  52. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromiseComponent.kt +21 -0
  53. package/android/src/main/java/expo/modules/kotlin/functions/SuspendFunctionComponent.kt +63 -0
  54. package/android/src/main/java/expo/modules/kotlin/functions/SyncFunctionComponent.kt +36 -0
  55. package/android/src/main/java/expo/modules/kotlin/jni/CppType.kt +19 -0
  56. package/android/src/main/java/expo/modules/kotlin/jni/JNIFunctionBody.kt +39 -0
  57. package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +89 -0
  58. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +46 -0
  59. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptObject.kt +113 -0
  60. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptValue.kt +35 -0
  61. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +16 -6
  62. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +5 -500
  63. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +30 -5
  64. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +271 -0
  65. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionData.kt +21 -0
  66. package/android/src/main/java/expo/modules/kotlin/objects/PropertyComponent.kt +54 -0
  67. package/android/src/main/java/expo/modules/kotlin/objects/PropertyComponentBuilder.kt +32 -0
  68. package/android/src/main/java/expo/modules/kotlin/providers/AppContextProvider.kt +14 -0
  69. package/android/src/main/java/expo/modules/kotlin/providers/CurrentActivityProvider.kt +22 -0
  70. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +19 -2
  71. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +3 -2
  72. package/android/src/main/java/expo/modules/kotlin/types/AnyTypeConverter.kt +36 -0
  73. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +7 -2
  74. package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +68 -20
  75. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +50 -22
  76. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +18 -2
  77. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +18 -2
  78. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +17 -2
  79. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +43 -3
  80. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +12 -0
  81. package/android/src/main/java/expo/modules/kotlin/views/ViewGroupDefinitionBuilder.kt +0 -41
  82. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +0 -33
  83. package/build/NativeModulesProxy.native.d.ts.map +1 -1
  84. package/build/NativeModulesProxy.native.js +9 -3
  85. package/build/NativeModulesProxy.native.js.map +1 -1
  86. package/build/PermissionsInterface.d.ts +29 -0
  87. package/build/PermissionsInterface.d.ts.map +1 -1
  88. package/build/PermissionsInterface.js +9 -0
  89. package/build/PermissionsInterface.js.map +1 -1
  90. package/ios/AppDelegates/EXAppDelegatesLoader.m +1 -2
  91. package/ios/ExpoModulesCore.podspec +3 -2
  92. package/ios/JSI/EXJSIConversions.mm +6 -0
  93. package/ios/JSI/EXJSIInstaller.h +15 -21
  94. package/ios/JSI/EXJSIInstaller.mm +39 -3
  95. package/ios/JSI/EXJSIUtils.h +48 -3
  96. package/ios/JSI/EXJSIUtils.mm +88 -4
  97. package/ios/JSI/EXJavaScriptObject.h +11 -18
  98. package/ios/JSI/EXJavaScriptObject.mm +37 -18
  99. package/ios/JSI/EXJavaScriptRuntime.h +43 -9
  100. package/ios/JSI/EXJavaScriptRuntime.mm +70 -27
  101. package/ios/JSI/EXJavaScriptTypedArray.h +30 -0
  102. package/ios/JSI/EXJavaScriptTypedArray.mm +29 -0
  103. package/ios/JSI/EXJavaScriptValue.h +3 -2
  104. package/ios/JSI/EXJavaScriptValue.mm +17 -20
  105. package/ios/JSI/EXJavaScriptWeakObject.h +23 -0
  106. package/ios/JSI/EXJavaScriptWeakObject.mm +53 -0
  107. package/ios/JSI/EXObjectDeallocator.h +27 -0
  108. package/ios/JSI/ExpoModulesHostObject.h +3 -3
  109. package/ios/JSI/ExpoModulesHostObject.mm +4 -4
  110. package/ios/JSI/JavaScriptRuntime.swift +38 -1
  111. package/ios/JSI/JavaScriptValue.swift +7 -0
  112. package/ios/JSI/TypedArray.cpp +67 -0
  113. package/ios/JSI/TypedArray.h +46 -0
  114. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +0 -11
  115. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +17 -10
  116. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +86 -77
  117. package/ios/NativeModulesProxy/NativeModulesProxyModule.swift +17 -0
  118. package/ios/Services/EXReactNativeEventEmitter.h +2 -2
  119. package/ios/Services/EXReactNativeEventEmitter.m +11 -6
  120. package/ios/Swift/AppContext.swift +206 -28
  121. package/ios/Swift/Arguments/AnyArgument.swift +18 -0
  122. package/ios/Swift/Arguments/{Types/EnumArgumentType.swift → EnumArgument.swift} +2 -17
  123. package/ios/Swift/Classes/ClassComponent.swift +95 -0
  124. package/ios/Swift/Classes/ClassComponentElement.swift +33 -0
  125. package/ios/Swift/Classes/ClassComponentElementsBuilder.swift +34 -0
  126. package/ios/Swift/Classes/ClassComponentFactories.swift +96 -0
  127. package/ios/Swift/DynamicTypes/AnyDynamicType.swift +44 -0
  128. package/ios/Swift/DynamicTypes/DynamicArrayType.swift +56 -0
  129. package/ios/Swift/DynamicTypes/DynamicConvertibleType.swift +27 -0
  130. package/ios/Swift/DynamicTypes/DynamicEnumType.swift +27 -0
  131. package/ios/Swift/DynamicTypes/DynamicOptionalType.swift +63 -0
  132. package/ios/Swift/DynamicTypes/DynamicRawType.swift +33 -0
  133. package/ios/Swift/DynamicTypes/DynamicSharedObjectType.swift +37 -0
  134. package/ios/Swift/DynamicTypes/DynamicType.swift +39 -0
  135. package/ios/Swift/DynamicTypes/DynamicTypedArrayType.swift +46 -0
  136. package/ios/Swift/Exceptions/ChainableException.swift +3 -3
  137. package/ios/Swift/Exceptions/CodedError.swift +1 -1
  138. package/ios/Swift/Exceptions/Exception.swift +8 -6
  139. package/ios/Swift/Exceptions/UnexpectedException.swift +2 -1
  140. package/ios/Swift/ExpoBridgeModule.m +5 -0
  141. package/ios/Swift/ExpoBridgeModule.swift +79 -0
  142. package/ios/Swift/Functions/AnyFunction.swift +33 -31
  143. package/ios/Swift/Functions/AsyncFunctionComponent.swift +196 -59
  144. package/ios/Swift/Functions/SyncFunctionComponent.swift +142 -58
  145. package/ios/Swift/JavaScriptUtils.swift +32 -57
  146. package/ios/Swift/Logging/LogHandlers.swift +39 -0
  147. package/ios/Swift/Logging/LogType.swift +62 -0
  148. package/ios/Swift/Logging/Logger.swift +201 -0
  149. package/ios/Swift/ModuleHolder.swift +19 -54
  150. package/ios/Swift/ModuleRegistry.swift +7 -1
  151. package/ios/Swift/Modules/AnyModule.swift +3 -3
  152. package/ios/Swift/ModulesProvider.swift +2 -0
  153. package/ios/Swift/Objects/JavaScriptObjectBuilder.swift +37 -0
  154. package/ios/Swift/Objects/ObjectDefinition.swift +74 -1
  155. package/ios/Swift/Objects/ObjectDefinitionComponents.swift +77 -68
  156. package/ios/Swift/Objects/PropertyComponent.swift +147 -0
  157. package/ios/Swift/Promise.swift +17 -4
  158. package/ios/Swift/Records/Field.swift +2 -2
  159. package/ios/Swift/SharedObjects/SharedObject.swift +20 -0
  160. package/ios/Swift/SharedObjects/SharedObjectRegistry.swift +129 -0
  161. package/ios/Swift/TypedArrays/AnyTypedArray.swift +11 -0
  162. package/ios/Swift/TypedArrays/ConcreteTypedArrays.swift +56 -0
  163. package/ios/Swift/TypedArrays/GenericTypedArray.swift +49 -0
  164. package/ios/Swift/TypedArrays/TypedArray.swift +80 -0
  165. package/ios/Swift/Utilities.swift +28 -0
  166. package/ios/Swift/Views/ConcreteViewProp.swift +3 -3
  167. package/ios/Swift/Views/ViewManagerDefinitionComponents.swift +2 -2
  168. package/ios/Tests/ClassComponentSpec.swift +210 -0
  169. package/ios/Tests/DynamicTypeSpec.swift +336 -0
  170. package/ios/Tests/EnumArgumentSpec.swift +48 -0
  171. package/ios/Tests/ExpoModulesSpec.swift +17 -3
  172. package/ios/Tests/FunctionSpec.swift +167 -118
  173. package/ios/Tests/Mocks/ModuleMocks.swift +1 -1
  174. package/ios/Tests/PropertyComponentSpec.swift +95 -0
  175. package/ios/Tests/SharedObjectRegistrySpec.swift +109 -0
  176. package/ios/Tests/TypedArraysSpec.swift +136 -0
  177. package/package.json +2 -2
  178. package/src/NativeModulesProxy.native.ts +13 -3
  179. package/src/PermissionsInterface.ts +29 -0
  180. package/src/ts-declarations/ExpoModules.d.ts +7 -0
  181. package/tsconfig.json +1 -1
  182. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromise.kt +0 -15
  183. package/android/src/main/java/expo/modules/kotlin/functions/AsyncSuspendFunction.kt +0 -36
  184. package/ios/Swift/Arguments/AnyArgumentType.swift +0 -13
  185. package/ios/Swift/Arguments/ArgumentType.swift +0 -28
  186. package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +0 -42
  187. package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +0 -16
  188. package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +0 -49
  189. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +0 -15
  190. package/ios/Swift/Arguments/Types/RawArgumentType.swift +0 -25
  191. package/ios/Swift/Functions/ConcreteFunction.swift +0 -103
  192. package/ios/Swift/SwiftInteropBridge.swift +0 -155
  193. package/ios/Tests/ArgumentTypeSpec.swift +0 -143
@@ -0,0 +1,340 @@
1
+ #include "MethodMetadata.h"
2
+ #include "JSIInteropModuleRegistry.h"
3
+ #include "JavaScriptValue.h"
4
+ #include "JavaScriptObject.h"
5
+ #include "CachedReferencesRegistry.h"
6
+
7
+ #include <utility>
8
+
9
+ #include "react/jni/ReadableNativeMap.h"
10
+ #include "react/jni/ReadableNativeArray.h"
11
+
12
+ namespace jni = facebook::jni;
13
+ namespace jsi = facebook::jsi;
14
+ namespace react = facebook::react;
15
+
16
+ namespace expo {
17
+
18
+ // Modified version of the RN implementation
19
+ // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
20
+ jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
21
+ jsi::Function &&function,
22
+ jsi::Runtime &rt,
23
+ std::shared_ptr<react::CallInvoker> jsInvoker
24
+ ) {
25
+ auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
26
+ std::move(jsInvoker));
27
+
28
+ // This needs to be a shared_ptr because:
29
+ // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
30
+ // not.
31
+ // 2. It cannot be weak_ptr since we need this object to live on.
32
+ // 3. It cannot be a value, because that would be deleted as soon as this
33
+ // function returns.
34
+ auto callbackWrapperOwner =
35
+ std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
36
+
37
+ std::function<void(folly::dynamic)> fn =
38
+ [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
39
+ folly::dynamic responses) mutable {
40
+ if (wrapperWasCalled) {
41
+ throw std::runtime_error(
42
+ "callback 2 arg cannot be called more than once");
43
+ }
44
+
45
+ auto strongWrapper = weakWrapper.lock();
46
+ if (!strongWrapper) {
47
+ return;
48
+ }
49
+
50
+ strongWrapper->jsInvoker().invokeAsync(
51
+ [weakWrapper, callbackWrapperOwner, responses]() mutable {
52
+ auto strongWrapper2 = weakWrapper.lock();
53
+ if (!strongWrapper2) {
54
+ return;
55
+ }
56
+
57
+ jsi::Value args =
58
+ jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
59
+ auto argsArray = args.getObject(strongWrapper2->runtime())
60
+ .asArray(strongWrapper2->runtime());
61
+ jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
62
+
63
+ strongWrapper2->callback().call(
64
+ strongWrapper2->runtime(),
65
+ (const jsi::Value *) &arg,
66
+ (size_t) 1
67
+ );
68
+
69
+ callbackWrapperOwner.reset();
70
+ });
71
+
72
+ wrapperWasCalled = true;
73
+ };
74
+
75
+ return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
76
+ }
77
+
78
+ std::vector<jvalue> MethodMetadata::convertJSIArgsToJNI(
79
+ JSIInteropModuleRegistry *moduleRegistry,
80
+ JNIEnv *env,
81
+ jsi::Runtime &rt,
82
+ const jsi::Value *args,
83
+ size_t count
84
+ ) {
85
+ std::vector<jvalue> result(count);
86
+
87
+ for (unsigned int argIndex = 0; argIndex < count; argIndex++) {
88
+ const jsi::Value *arg = &args[argIndex];
89
+ jvalue *jarg = &result[argIndex];
90
+ int desiredType = desiredTypes[argIndex];
91
+
92
+ if (desiredType & CppType::JS_VALUE) {
93
+ jarg->l = JavaScriptValue::newObjectCxxArgs(
94
+ moduleRegistry->runtimeHolder->weak_from_this(),
95
+ // TODO(@lukmccall): make sure that copy here is necessary
96
+ std::make_shared<jsi::Value>(jsi::Value(rt, *arg))
97
+ ).release();
98
+ } else if (desiredType & CppType::JS_OBJECT) {
99
+ jarg->l = JavaScriptObject::newObjectCxxArgs(
100
+ moduleRegistry->runtimeHolder->weak_from_this(),
101
+ std::make_shared<jsi::Object>(arg->getObject(rt))
102
+ ).release();
103
+ } else if (arg->isNull() || arg->isUndefined()) {
104
+ jarg->l = nullptr;
105
+ } else if (arg->isNumber()) {
106
+ auto &doubleClass = CachedReferencesRegistry::instance()
107
+ ->getJClass("java/lang/Double");
108
+ jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V");
109
+ jarg->l = env->NewObject(doubleClass.clazz, doubleConstructor, arg->getNumber());
110
+ } else if (arg->isBool()) {
111
+ auto &booleanClass = CachedReferencesRegistry::instance()
112
+ ->getJClass("java/lang/Boolean");
113
+ jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V");
114
+ jarg->l = env->NewObject(booleanClass.clazz, booleanConstructor, arg->getBool());
115
+ } else if (arg->isString()) {
116
+ jarg->l = env->NewStringUTF(arg->getString(rt).utf8(rt).c_str());
117
+ } else if (arg->isObject()) {
118
+ const jsi::Object object = arg->getObject(rt);
119
+
120
+ // TODO(@lukmccall): stop using dynamic
121
+ auto dynamic = jsi::dynamicFromValue(rt, *arg);
122
+ if (arg->getObject(rt).isArray(rt)) {
123
+ jarg->l = react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release();
124
+ } else {
125
+ jarg->l = react::ReadableNativeMap::createWithContents(std::move(dynamic)).release();
126
+ }
127
+ } else {
128
+ // TODO(@lukmccall): throw an exception
129
+ jarg->l = nullptr;
130
+ }
131
+ }
132
+
133
+ return result;
134
+ }
135
+
136
+ MethodMetadata::MethodMetadata(
137
+ std::string name,
138
+ int args,
139
+ bool isAsync,
140
+ std::unique_ptr<int[]> desiredTypes,
141
+ jni::global_ref<jobject> &&jBodyReference
142
+ ) : name(std::move(name)),
143
+ args(args),
144
+ isAsync(isAsync),
145
+ desiredTypes(std::move(desiredTypes)),
146
+ jBodyReference(std::move(jBodyReference)) {}
147
+
148
+ std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
149
+ jsi::Runtime &runtime,
150
+ JSIInteropModuleRegistry *moduleRegistry
151
+ ) {
152
+ if (body == nullptr) {
153
+ if (isAsync) {
154
+ body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
155
+ } else {
156
+ body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
157
+ }
158
+ }
159
+
160
+ return body;
161
+ }
162
+
163
+ jsi::Function MethodMetadata::toSyncFunction(
164
+ jsi::Runtime &runtime,
165
+ JSIInteropModuleRegistry *moduleRegistry
166
+ ) {
167
+ return jsi::Function::createFromHostFunction(
168
+ runtime,
169
+ jsi::PropNameID::forAscii(runtime, name),
170
+ args,
171
+ [this, moduleRegistry](
172
+ jsi::Runtime &rt,
173
+ const jsi::Value &thisValue,
174
+ const jsi::Value *args,
175
+ size_t count
176
+ ) -> jsi::Value {
177
+ return this->callSync(
178
+ rt,
179
+ moduleRegistry,
180
+ args,
181
+ count
182
+ );
183
+ });
184
+ }
185
+
186
+ jsi::Value MethodMetadata::callSync(
187
+ jsi::Runtime &rt,
188
+ JSIInteropModuleRegistry *moduleRegistry,
189
+ const jsi::Value *args,
190
+ size_t count
191
+ ) {
192
+ if (this->jBodyReference == nullptr) {
193
+ return jsi::Value::undefined();
194
+ }
195
+
196
+ JNIEnv *env = jni::Environment::current();
197
+
198
+ /**
199
+ * This will push a new JNI stack frame for the LocalReferences in this
200
+ * function call. When the stack frame for this lambda is popped,
201
+ * all LocalReferences are deleted.
202
+ */
203
+ jni::JniLocalScope scope(env, (int) count);
204
+
205
+ std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
206
+
207
+ // TODO(@lukmccall): Remove this temp array
208
+ auto tempArray = jni::JArrayClass<jobject>::newArray(count);
209
+ for (size_t i = 0; i < convertedArgs.size(); i++) {
210
+ tempArray->setElement(i, convertedArgs[i].l);
211
+ }
212
+
213
+ // Cast in this place is safe, cause we know that this function is promise-less.
214
+ auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
215
+ auto result = syncFunction->invoke(
216
+ std::move(tempArray)
217
+ );
218
+
219
+ if (result == nullptr) {
220
+ return jsi::Value::undefined();
221
+ }
222
+
223
+ return jsi::valueFromDynamic(rt, result->cthis()->consume())
224
+ .asObject(rt)
225
+ .asArray(rt)
226
+ .getValueAtIndex(rt, 0);
227
+ }
228
+
229
+ jsi::Function MethodMetadata::toAsyncFunction(
230
+ jsi::Runtime &runtime,
231
+ JSIInteropModuleRegistry *moduleRegistry
232
+ ) {
233
+ return jsi::Function::createFromHostFunction(
234
+ runtime,
235
+ jsi::PropNameID::forAscii(runtime, name),
236
+ args,
237
+ [this, moduleRegistry](
238
+ jsi::Runtime &rt,
239
+ const jsi::Value &thisValue,
240
+ const jsi::Value *args,
241
+ size_t count
242
+ ) -> jsi::Value {
243
+ JNIEnv *env = jni::Environment::current();
244
+
245
+ /**
246
+ * This will push a new JNI stack frame for the LocalReferences in this
247
+ * function call. When the stack frame for this lambda is popped,
248
+ * all LocalReferences are deleted.
249
+ */
250
+ jni::JniLocalScope scope(env, (int) count);
251
+
252
+ std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
253
+
254
+ // TODO(@lukmccall): Remove this temp array
255
+ auto tempArray = jni::JArrayClass<jobject>::newArray(count);
256
+ for (size_t i = 0; i < convertedArgs.size(); i++) {
257
+ tempArray->setElement(i, convertedArgs[i].l);
258
+ }
259
+
260
+ auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
261
+ // Creates a JSI promise
262
+ jsi::Value promise = Promise.callAsConstructor(
263
+ rt,
264
+ createPromiseBody(rt, moduleRegistry, std::move(tempArray))
265
+ );
266
+ return promise;
267
+ }
268
+ );
269
+ }
270
+
271
+ jsi::Function MethodMetadata::createPromiseBody(
272
+ jsi::Runtime &runtime,
273
+ JSIInteropModuleRegistry *moduleRegistry,
274
+ jni::local_ref<jni::JArrayClass<jobject>::javaobject> &&args
275
+ ) {
276
+ return jsi::Function::createFromHostFunction(
277
+ runtime,
278
+ jsi::PropNameID::forAscii(runtime, "promiseFn"),
279
+ 2,
280
+ [this, args = std::move(args), moduleRegistry](
281
+ jsi::Runtime &rt,
282
+ const jsi::Value &thisVal,
283
+ const jsi::Value *promiseConstructorArgs,
284
+ size_t promiseConstructorArgCount
285
+ ) {
286
+ if (promiseConstructorArgCount != 2) {
287
+ throw std::invalid_argument("Promise fn arg count must be 2");
288
+ }
289
+
290
+ jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
291
+ jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
292
+
293
+ auto &runtimeHolder = moduleRegistry->runtimeHolder;
294
+ jobject resolve = createJavaCallbackFromJSIFunction(
295
+ std::move(resolveJSIFn),
296
+ rt,
297
+ runtimeHolder->jsInvoker
298
+ ).release();
299
+
300
+ jobject reject = createJavaCallbackFromJSIFunction(
301
+ std::move(rejectJSIFn),
302
+ rt,
303
+ runtimeHolder->jsInvoker
304
+ ).release();
305
+
306
+ JNIEnv *env = jni::Environment::current();
307
+
308
+ auto &jPromise = CachedReferencesRegistry::instance()->getJClass(
309
+ "com/facebook/react/bridge/PromiseImpl");
310
+ jmethodID jPromiseConstructor = jPromise.getMethod(
311
+ "<init>",
312
+ "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
313
+ );
314
+
315
+ // Creates a promise object
316
+ jobject promise = env->NewObject(
317
+ jPromise.clazz,
318
+ jPromiseConstructor,
319
+ resolve,
320
+ reject
321
+ );
322
+
323
+ // Cast in this place is safe, cause we know that this function expects promise.
324
+ auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
325
+ asyncFunction->invoke(
326
+ args,
327
+ promise
328
+ );
329
+
330
+ // We have to remove the local reference to the promise object.
331
+ // It doesn't mean that the promise will be deallocated, but rather that we move
332
+ // the ownership to the `JNIAsyncFunctionBody`.
333
+ env->DeleteLocalRef(promise);
334
+
335
+ return jsi::Value::undefined();
336
+ }
337
+ );
338
+ }
339
+
340
+ } // namespace expo
@@ -0,0 +1,131 @@
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 cpp version of the `expo.modules.kotlin.jni.CppType` enum.
22
+ * Used to determine which representation of the js value should be sent to the Kotlin.
23
+ */
24
+ enum CppType {
25
+ DOUBLE = 1 << 0,
26
+ BOOLEAN = 1 << 1,
27
+ STRING = 1 << 2,
28
+ JS_OBJECT = 1 << 3,
29
+ JS_VALUE = 1 << 4,
30
+ READABLE_ARRAY = 1 << 5,
31
+ READABLE_MAP = 1 << 6
32
+ };
33
+
34
+ /**
35
+ * A class that holds information about the exported function.
36
+ */
37
+ class MethodMetadata {
38
+ public:
39
+ /**
40
+ * Function name
41
+ */
42
+ std::string name;
43
+ /**
44
+ * Number of arguments
45
+ */
46
+ int args;
47
+ /*
48
+ * Whether this function is async
49
+ */
50
+ bool isAsync;
51
+
52
+ std::unique_ptr<int[]> desiredTypes;
53
+
54
+ MethodMetadata(
55
+ std::string name,
56
+ int args,
57
+ bool isAsync,
58
+ std::unique_ptr<int[]> desiredTypes,
59
+ jni::global_ref<jobject> &&jBodyReference
60
+ );
61
+
62
+ // We deleted the copy contractor to not deal with transforming the ownership of the `jBodyReference`.
63
+ MethodMetadata(const MethodMetadata &) = delete;
64
+
65
+ MethodMetadata(MethodMetadata &&other) = default;
66
+
67
+ /**
68
+ * MethodMetadata owns the only reference to the Kotlin function.
69
+ * We have to clean that, cause it's a `global_ref`.
70
+ */
71
+ ~MethodMetadata() {
72
+ if (jBodyReference != nullptr) {
73
+ jBodyReference.release();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Transforms metadata to a jsi::Function.
79
+ *
80
+ * @param runtime
81
+ * @param moduleRegistry
82
+ * @return shared ptr to the jsi::Function that wrapped the underlying Kotlin's function.
83
+ */
84
+ std::shared_ptr<jsi::Function> toJSFunction(
85
+ jsi::Runtime &runtime,
86
+ JSIInteropModuleRegistry *moduleRegistry
87
+ );
88
+
89
+ /**
90
+ * Calls the underlying Kotlin function.
91
+ */
92
+ jsi::Value callSync(
93
+ jsi::Runtime &rt,
94
+ JSIInteropModuleRegistry *moduleRegistry,
95
+ const jsi::Value *args,
96
+ size_t count
97
+ );
98
+
99
+ private:
100
+ /**
101
+ * Reference to one of two java objects - `JNIFunctionBody` or `JNIAsyncFunctionBody`.
102
+ *
103
+ * In case when `isAsync` is `true`, this variable will point to `JNIAsyncFunctionBody`.
104
+ * Otherwise to `JNIFunctionBody`
105
+ */
106
+ jni::global_ref<jobject> jBodyReference;
107
+
108
+ /**
109
+ * To not create a jsi::Function always when we need it, we cached that value.
110
+ */
111
+ std::shared_ptr<jsi::Function> body = nullptr;
112
+
113
+ jsi::Function toSyncFunction(jsi::Runtime &runtime, JSIInteropModuleRegistry *moduleRegistry);
114
+
115
+ jsi::Function toAsyncFunction(jsi::Runtime &runtime, JSIInteropModuleRegistry *moduleRegistry);
116
+
117
+ jsi::Function createPromiseBody(
118
+ jsi::Runtime &runtime,
119
+ JSIInteropModuleRegistry *moduleRegistry,
120
+ jni::local_ref<jni::JArrayClass<jobject>::javaobject> &&args
121
+ );
122
+
123
+ std::vector<jvalue> convertJSIArgsToJNI(
124
+ JSIInteropModuleRegistry *moduleRegistry,
125
+ JNIEnv *env,
126
+ jsi::Runtime &rt,
127
+ const jsi::Value *args,
128
+ size_t count
129
+ );
130
+ };
131
+ } // 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,15 @@
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.annotation.MainThread
9
+ import androidx.appcompat.app.AppCompatActivity
6
10
  import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
12
+ import expo.modules.core.errors.ContextDestroyedException
7
13
  import expo.modules.core.interfaces.ActivityProvider
8
14
  import expo.modules.interfaces.barcodescanner.BarCodeScannerInterface
9
15
  import expo.modules.interfaces.camera.CameraViewInterface
@@ -14,35 +20,86 @@ import expo.modules.interfaces.imageloader.ImageLoaderInterface
14
20
  import expo.modules.interfaces.permissions.Permissions
15
21
  import expo.modules.interfaces.sensors.SensorServiceInterface
16
22
  import expo.modules.interfaces.taskManager.TaskManagerInterface
23
+ import expo.modules.kotlin.activityresult.ActivityResultsManager
24
+ import expo.modules.kotlin.activityresult.AppContextActivityResultFallbackCallback
25
+ import expo.modules.kotlin.activityresult.AppContextActivityResultCaller
26
+ import expo.modules.kotlin.activityresult.AppContextActivityResultContract
27
+ import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher
17
28
  import expo.modules.kotlin.defaultmodules.ErrorManagerModule
18
29
  import expo.modules.kotlin.events.EventEmitter
19
30
  import expo.modules.kotlin.events.EventName
20
31
  import expo.modules.kotlin.events.KEventEmitterWrapper
21
32
  import expo.modules.kotlin.events.KModuleEventEmitterWrapper
22
33
  import expo.modules.kotlin.events.OnActivityResultPayload
34
+ import expo.modules.kotlin.jni.JSIInteropModuleRegistry
23
35
  import expo.modules.kotlin.modules.Module
36
+ import expo.modules.kotlin.providers.CurrentActivityProvider
37
+ import kotlinx.coroutines.CoroutineName
38
+ import kotlinx.coroutines.CoroutineScope
39
+ import kotlinx.coroutines.DelicateCoroutinesApi
40
+ import kotlinx.coroutines.SupervisorJob
41
+ import kotlinx.coroutines.cancel
42
+ import kotlinx.coroutines.newSingleThreadContext
43
+ import java.io.Serializable
24
44
  import java.lang.ref.WeakReference
25
45
 
26
46
  class AppContext(
27
47
  modulesProvider: ModulesProvider,
28
48
  val legacyModuleRegistry: expo.modules.core.ModuleRegistry,
29
49
  private val reactContextHolder: WeakReference<ReactApplicationContext>
30
- ) {
50
+ ) : CurrentActivityProvider, AppContextActivityResultCaller {
31
51
  val registry = ModuleRegistry(WeakReference(this)).apply {
32
- register(ErrorManagerModule())
33
- register(modulesProvider)
34
52
  }
35
53
  private val reactLifecycleDelegate = ReactLifecycleDelegate(this)
36
54
 
55
+ // We postpone creating the `JSIInteropModuleRegistry` to not load so files in unit tests.
56
+ private lateinit var jsiInterop: JSIInteropModuleRegistry
57
+
58
+ /**
59
+ * A queue used to dispatch all async methods that are called via JSI.
60
+ */
61
+ internal val modulesQueue = CoroutineScope(
62
+ // TODO(@lukmccall): maybe it will be better to use a thread pool
63
+ newSingleThreadContext("ExpoModulesCoreQueue") +
64
+ SupervisorJob() +
65
+ CoroutineName("ExpoModulesCoreCoroutineQueue")
66
+ )
67
+
68
+ private val activityResultsManager = ActivityResultsManager(this)
69
+
37
70
  init {
38
71
  requireNotNull(reactContextHolder.get()) {
39
72
  "The app context should be created with valid react context."
40
73
  }.apply {
41
74
  addLifecycleEventListener(reactLifecycleDelegate)
42
75
  addActivityEventListener(reactLifecycleDelegate)
76
+
77
+ // Registering modules has to happen at the very end of `AppContext` creation. Some modules need to access
78
+ // `AppContext` during their initialisation (or during `OnCreate` method), so we need to ensure all `AppContext`'s
79
+ // properties are initialized first. Not having that would trigger NPE.
80
+ registry.register(ErrorManagerModule())
81
+ registry.register(modulesProvider)
43
82
  }
44
83
  }
45
84
 
85
+ /**
86
+ * Initializes a JSI part of the module registry.
87
+ * It will be a NOOP if the remote debugging was activated.
88
+ */
89
+ fun installJSIInterop() {
90
+ jsiInterop = JSIInteropModuleRegistry(this)
91
+ val reactContext = reactContextHolder.get() ?: return
92
+ reactContext.javaScriptContextHolder?.get()
93
+ ?.takeIf { it != 0L }
94
+ ?.let {
95
+ jsiInterop.installJSI(
96
+ it,
97
+ reactContext.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl,
98
+ reactContext.catalystInstance.nativeCallInvokerHolder as CallInvokerHolderImpl
99
+ )
100
+ }
101
+ }
102
+
46
103
  /**
47
104
  * Returns a legacy module implementing given interface.
48
105
  */
@@ -149,9 +206,15 @@ class AppContext(
149
206
  reactContextHolder.get()?.removeLifecycleEventListener(reactLifecycleDelegate)
150
207
  registry.post(EventName.MODULE_DESTROY)
151
208
  registry.cleanUp()
209
+ modulesQueue.cancel(ContextDestroyedException())
152
210
  }
153
211
 
154
212
  fun onHostResume() {
213
+ activityResultsManager.onHostResume(
214
+ requireNotNull(currentActivity) {
215
+ "Current Activity is not available at this moment. This is an invalid state and this should never happen"
216
+ }
217
+ )
155
218
  registry.post(EventName.ACTIVITY_ENTERS_FOREGROUND)
156
219
  }
157
220
 
@@ -160,10 +223,16 @@ class AppContext(
160
223
  }
161
224
 
162
225
  fun onHostDestroy() {
226
+ activityResultsManager.onHostDestroy(
227
+ requireNotNull(currentActivity) {
228
+ "Current Activity is not available at this moment. This is an invalid state and this should never happen"
229
+ }
230
+ )
163
231
  registry.post(EventName.ACTIVITY_DESTROYS)
164
232
  }
165
233
 
166
234
  fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
235
+ activityResultsManager.onActivityResult(activity, requestCode, resultCode, data)
167
236
  registry.post(
168
237
  EventName.ON_ACTIVITY_RESULT,
169
238
  activity,
@@ -181,4 +250,30 @@ class AppContext(
181
250
  intent
182
251
  )
183
252
  }
253
+
254
+ // region CurrentActivityProvider
255
+
256
+ override val currentActivity: AppCompatActivity?
257
+ get() {
258
+ val currentActivity = this.activityProvider?.currentActivity ?: return null
259
+
260
+ check(currentActivity is AppCompatActivity) {
261
+ "Current Activity is of incorrect class, expected AppCompatActivity, received ${currentActivity.localClassName}"
262
+ }
263
+
264
+ return currentActivity
265
+ }
266
+
267
+ // endregion
268
+
269
+ // region AppContextActivityResultCaller
270
+
271
+ @MainThread
272
+ override suspend fun <I : Serializable, O> registerForActivityResult(
273
+ contract: AppContextActivityResultContract<I, O>,
274
+ fallbackCallback: AppContextActivityResultFallbackCallback<I, O>
275
+ ): AppContextActivityResultLauncher<I, O> =
276
+ activityResultsManager.registerForActivityResult(contract, fallbackCallback)
277
+
278
+ // endregion
184
279
  }