expo-modules-core 56.0.17 → 56.0.18

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 (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +10 -1
  4. package/android/src/main/cpp/JavaScriptObject.cpp +6 -0
  5. package/android/src/main/cpp/JavaScriptObject.h +5 -0
  6. package/android/src/main/cpp/installers/WorkletRuntimeInstaller.cpp +25 -1
  7. package/android/src/main/cpp/installers/WorkletRuntimeInstaller.h +9 -0
  8. package/android/src/main/java/expo/modules/kotlin/defaultmodules/CoreModule.kt +9 -18
  9. package/android/src/main/java/expo/modules/kotlin/jni/WorkletRuntimeInstaller.kt +11 -0
  10. package/build/ts-declarations/global.d.ts +4 -3
  11. package/build/ts-declarations/global.d.ts.map +1 -1
  12. package/build/worklets/index.d.ts +8 -1
  13. package/build/worklets/index.d.ts.map +1 -1
  14. package/ios/Core/AppContext.swift +7 -2
  15. package/ios/Core/Modules/CoreModule.swift +3 -10
  16. package/ios/Worklets/Core/WorkletIntegration.swift +7 -14
  17. package/ios/Worklets/EXWorkletsProvider.h +4 -0
  18. package/ios/Worklets/WorkletRuntimeResolver.h +23 -0
  19. package/ios/Worklets/WorkletRuntimeResolver.mm +15 -0
  20. package/ios/WorkletsAdapter/ExpoWorkletsBridgeProvider.mm +21 -0
  21. package/ios/WorkletsTests/WorkletsNotInstalledTests.swift +20 -0
  22. package/package.json +2 -2
  23. package/prebuilds/output/debug/xcframeworks/ExpoModulesCore.tar.gz +0 -0
  24. package/prebuilds/output/debug/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
  25. package/prebuilds/output/release/xcframeworks/ExpoModulesCore.tar.gz +0 -0
  26. package/prebuilds/output/release/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
  27. package/src/polyfill/dangerous-internal.ts +1 -1
  28. package/src/ts-declarations/global.ts +4 -3
  29. package/src/worklets/index.ts +10 -3
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.18 — 2026-07-01
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android][compose] Guard `onLayout` against detached window to prevent `LayoutNode should be attached to an owner` crash. ([#47085](https://github.com/expo/expo/pull/47085) by [@roitium](https://github.com/roitium))
18
+ - [Android] Fix Jetpack Compose `Host` content disappearing before a react-native-screens pop animation finishes, by deferring composition disposal to window detach while the view is still on-screen for a transition. ([#45914](https://github.com/expo/expo/issues/45914), [#47086](https://github.com/expo/expo/issues/47086) by [@aubrey-wodonga](https://github.com/aubrey-wodonga)) ([#47099](https://github.com/expo/expo/pull/47099) by [@nishan](https://github.com/intergalacticspacehighway))
19
+ - [iOS] Fix a reload deadlock on the new architecture where reloading (from pressing `r` in the iOS simulator) would freeze the app until the watchdog killed it. `reloadAppAsync` now triggers the reload synchronously when already on the main thread instead of always deferring via `DispatchQueue.main.async`. ([#47392](https://github.com/expo/expo/pull/47392) by [@brentvatne](https://github.com/brentvatne))
20
+
21
+ ### 💡 Others
22
+
23
+ - [iOS][android] Resolve the worklet UI runtime from its `react-native-worklets` holder instead of the reanimated `_WORKLET_RUNTIME` global; `installOnUIRuntime` now takes the holder from `getUIRuntimeHolder()`. ([#46922](https://github.com/expo/expo/pull/46922), [#46935](https://github.com/expo/expo/pull/46935) by [@nishan](https://github.com/intergalacticspacehighway))
24
+
13
25
  ## 56.0.17 — 2026-06-15
14
26
 
15
27
  ### 🐛 Bug fixes
@@ -27,7 +27,7 @@ if (shouldIncludeCompose) {
27
27
  }
28
28
 
29
29
  group = 'host.exp.exponent'
30
- version = '56.0.17'
30
+ version = '56.0.18'
31
31
 
32
32
  def isExpoModulesCoreTests = {
33
33
  Gradle gradle = getGradle()
@@ -94,7 +94,7 @@ android {
94
94
  defaultConfig {
95
95
  consumerProguardFiles 'proguard-rules.pro'
96
96
  versionCode 1
97
- versionName "56.0.17"
97
+ versionName "56.0.18"
98
98
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
99
99
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
100
100
 
@@ -88,6 +88,9 @@ abstract class ExpoComposeView<T : ComposeProps>(
88
88
  }
89
89
 
90
90
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
91
+ if (shouldUseAndroidLayout && !isAttachedToWindow) {
92
+ return
93
+ }
91
94
  super.onLayout(changed, left, top, right, bottom)
92
95
 
93
96
  // Makes sure the child ComposeView is sticky with the current hosting view
@@ -245,7 +248,13 @@ abstract class ExpoComposeView<T : ComposeProps>(
245
248
  // registered on the Activity, which leaks this view.
246
249
  // Swapping the strategy first detaches that observer, then we dispose.
247
250
  it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
248
- it.disposeComposition()
251
+ // If the view is still attached when RN drops it, react-native-screens is keeping it
252
+ // on-screen for an in-progress navigation transition (e.g. a pop). Disposing now blanks
253
+ // the Compose content before the animation finishes (https://github.com/expo/expo/issues/47086).
254
+ // View eventually gets decomposed when RN screen detatches view from window
255
+ if (!it.isAttachedToWindow) {
256
+ it.disposeComposition()
257
+ }
249
258
  }
250
259
  }
251
260
 
@@ -58,6 +58,12 @@ std::shared_ptr<jsi::Object> JavaScriptObject::get() {
58
58
  return jsObject;
59
59
  }
60
60
 
61
+ jsi::Runtime &JavaScriptObject::getRuntime() {
62
+ auto runtime = runtimeHolder.lock();
63
+ assert((runtime != nullptr) && "JS Runtime was used after deallocation");
64
+ return runtime->get();
65
+ }
66
+
61
67
  bool JavaScriptObject::hasProperty(const std::string &name) {
62
68
  auto runtime = runtimeHolder.lock();
63
69
  assert((runtime != nullptr) && "JS Runtime was used after deallocation");
@@ -44,6 +44,11 @@ public:
44
44
 
45
45
  std::shared_ptr<jsi::Object> get() override;
46
46
 
47
+ /**
48
+ * @return the `jsi::Runtime` this object is bound to.
49
+ */
50
+ jsi::Runtime &getRuntime();
51
+
47
52
  /**
48
53
  * @return a bool whether the object has a property with the given name
49
54
  */
@@ -3,6 +3,7 @@
3
3
  #if WORKLETS_ENABLED
4
4
 
5
5
  #include "../worklets/WorkletJSCallInvoker.h"
6
+ #include <worklets/Compat/StableApi.h>
6
7
  #include <worklets/WorkletRuntime/WorkletRuntime.h>
7
8
 
8
9
  #endif
@@ -15,7 +16,8 @@ namespace expo {
15
16
 
16
17
  void WorkletRuntimeInstaller::registerNatives() {
17
18
  javaClassLocal()->registerNatives({
18
- makeNativeMethod("install", WorkletRuntimeInstaller::install)
19
+ makeNativeMethod("install", WorkletRuntimeInstaller::install),
20
+ makeNativeMethod("resolveUIRuntimePointer", WorkletRuntimeInstaller::resolveUIRuntimePointer)
19
21
  });
20
22
  }
21
23
 
@@ -44,6 +46,28 @@ jni::local_ref<JSIContext::javaobject> WorkletRuntimeInstaller::install(
44
46
  #endif
45
47
  }
46
48
 
49
+ jlong WorkletRuntimeInstaller::resolveUIRuntimePointer(
50
+ jni::alias_ref<jni::JClass>,
51
+ jni::alias_ref<JavaScriptObject::javaobject> uiRuntimeHolder
52
+ ) noexcept {
53
+ #if WORKLETS_ENABLED
54
+ auto *holder = uiRuntimeHolder->cthis();
55
+ jsi::Runtime &runtime = holder->getRuntime();
56
+ std::shared_ptr<jsi::Object> holderObject = holder->get();
57
+
58
+ std::shared_ptr<worklets::WorkletRuntime> workletRuntime =
59
+ worklets::getWorkletRuntimeFromHolder(runtime, *holderObject);
60
+ if (!workletRuntime) {
61
+ return 0;
62
+ }
63
+
64
+ jsi::Runtime &uiRuntime = worklets::getJSIRuntimeFromWorkletRuntime(workletRuntime);
65
+ return reinterpret_cast<jlong>(&uiRuntime);
66
+ #else
67
+ return 0;
68
+ #endif
69
+ }
70
+
47
71
  void WorkletRuntimeInstaller::prepareRuntime(
48
72
  jni::local_ref<JSIContext::javaobject> jsiContext
49
73
  ) noexcept {
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include "../ExpoHeader.pch"
4
+ #include "../JavaScriptObject.h"
4
5
  #include "MainRuntimeInstaller.h"
5
6
 
6
7
  namespace jni = facebook::jni;
@@ -24,6 +25,14 @@ public:
24
25
  static void prepareRuntime(
25
26
  jni::local_ref<JSIContext::javaobject> jsiContext
26
27
  ) noexcept;
28
+
29
+ // Resolves the raw UI `jsi::Runtime *` (as a jlong) from a
30
+ // `react-native-worklets` UI runtime holder. Returns 0 when worklets isn't
31
+ // linked or the holder doesn't wrap a worklet runtime.
32
+ static jlong resolveUIRuntimePointer(
33
+ jni::alias_ref<jni::JClass> clazz,
34
+ jni::alias_ref<JavaScriptObject::javaobject> uiRuntimeHolder
35
+ ) noexcept;
27
36
  };
28
37
 
29
38
  } // namespace expo
@@ -6,6 +6,8 @@ import com.facebook.react.ReactActivity
6
6
  import expo.modules.BuildConfig
7
7
  import expo.modules.kotlin.events.normalizeEventName
8
8
  import expo.modules.kotlin.exception.Exceptions
9
+ import expo.modules.kotlin.jni.JavaScriptObject
10
+ import expo.modules.kotlin.jni.WorkletRuntimeInstaller
9
11
  import expo.modules.kotlin.modules.DEFAULT_MODULE_VIEW
10
12
  import expo.modules.kotlin.modules.Module
11
13
  import expo.modules.kotlin.modules.ModuleDefinition
@@ -95,26 +97,15 @@ class CoreModule : Module() {
95
97
  reactDelegate.reload()
96
98
  }
97
99
 
98
- Function("installOnUIRuntime") {
99
- val uiRuntimePointerHolder = appContext.runtime.jsiContext.global()["_WORKLET_RUNTIME"]
100
- ?: throw IllegalStateException("UI Runtime is not available. Make sure you have Reanimated installed and imported in your project.")
101
-
102
- assert(uiRuntimePointerHolder.isObject()) {
103
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer, got: ${uiRuntimePointerHolder.kind()}."
104
- }
105
-
106
- val uiRuntimePointerHolderObj = uiRuntimePointerHolder.getObject()
107
- assert(uiRuntimePointerHolderObj.isArrayBuffer()) {
108
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer, got: ${uiRuntimePointerHolder.kind()}."
100
+ Function("installOnUIRuntime") { uiRuntimeHolder: JavaScriptObject ->
101
+ val runtimePointer = WorkletRuntimeInstaller.resolveUIRuntimePointer(uiRuntimeHolder)
102
+ if (runtimePointer == 0L) {
103
+ throw IllegalStateException(
104
+ "Couldn't resolve the worklets UI runtime from the provided holder. " +
105
+ "Make sure `react-native-worklets` is installed and rebuild the app."
106
+ )
109
107
  }
110
108
 
111
- val arayBuffer = uiRuntimePointerHolderObj.getArrayBuffer()
112
- assert(arayBuffer.size() == 8) {
113
- "Expected _WORKLET_RUNTIME to be an ArrayBuffer with a size of 8 bytes (a pointer size), got: ${arayBuffer.size()}."
114
- }
115
-
116
- val runtimePointer = arayBuffer.read8Byte(0)
117
-
118
109
  runBlocking {
119
110
  withContext(Dispatchers.Main) {
120
111
  appContext.uiRuntime.install(runtimePointer)
@@ -22,4 +22,15 @@ class WorkletRuntimeInstaller(
22
22
  jsRuntimePointer: Long,
23
23
  jniDeallocator: JNIDeallocator
24
24
  ): JSIContext
25
+
26
+ companion object {
27
+ /**
28
+ * Resolves the raw UI `jsi::Runtime*` (as a Long) from a `react-native-worklets`
29
+ * UI runtime holder. Returns 0 when worklets isn't installed or the holder
30
+ * doesn't wrap a worklet runtime.
31
+ */
32
+ @JvmStatic
33
+ @Suppress("KotlinJniMissingFunction")
34
+ external fun resolveUIRuntimePointer(uiRuntimeHolder: JavaScriptObject): Long
35
+ }
25
36
  }
@@ -79,10 +79,11 @@ declare namespace ExpoGlobal {
79
79
  */
80
80
  export function reloadAppAsync(reason: string): Promise<void>;
81
81
  /**
82
- * Installs Expo Modules inside the Reanimated UI worklet runtime.
83
- * @platform android
82
+ * Installs Expo Modules inside the worklets UI runtime.
83
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
84
+ * `react-native-worklets`.
84
85
  */
85
- export function installOnUIRuntime(): void;
86
+ export function installOnUIRuntime(uiRuntimeHolder: object): void;
86
87
  }
87
88
  declare global {
88
89
  namespace NodeJS {
@@ -1 +1 @@
1
- {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/global.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/global.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,9 @@
1
- export declare function installOnUIRuntime(): void;
1
+ /**
2
+ * Installs Expo Modules on the UI worklet runtime so worklet callbacks and
3
+ * serializable SharedObjects work there.
4
+ *
5
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
6
+ * `react-native-worklets`.
7
+ */
8
+ export declare function installOnUIRuntime(uiRuntimeHolder: object): void;
2
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worklets/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,SASjC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worklets/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,QASzD"}
@@ -670,8 +670,13 @@ public final class AppContext: NSObject, EXAppContextProtocol, @unchecked Sendab
670
670
  if moduleRegistry.has(moduleWithName: "ExpoGo") {
671
671
  NotificationCenter.default.post(name: NSNotification.Name(rawValue: "EXReloadActiveAppRequest"), object: nil)
672
672
  } else {
673
- DispatchQueue.main.async {
674
- RCTTriggerReloadCommandListeners(reason)
673
+ // Must run on the main thread (see #31789), but synchronously when already there — deferring
674
+ // via `DispatchQueue.main.async` deadlocks the bridgeless reload against TurboModule teardown.
675
+ let trigger = { RCTTriggerReloadCommandListeners(reason) }
676
+ if Thread.isMainThread {
677
+ trigger()
678
+ } else {
679
+ DispatchQueue.main.async(execute: trigger)
675
680
  }
676
681
  }
677
682
  }
@@ -4,8 +4,6 @@ internal import React
4
4
  import Foundation
5
5
  import ExpoModulesJSI
6
6
 
7
- private let WORKLET_RUNTIME_KEY = "_WORKLET_RUNTIME"
8
-
9
7
  // The core module that describes the `global.expo` object.
10
8
  internal final class CoreModule: Module {
11
9
  internal func definition() -> ModuleDefinition {
@@ -29,7 +27,7 @@ internal final class CoreModule: Module {
29
27
  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.path ?? ""
30
28
  }
31
29
 
32
- Function("installOnUIRuntime") {
30
+ Function("installOnUIRuntime") { (uiRuntimeHolder: JavaScriptValue) in
33
31
  guard let appContext else {
34
32
  throw Exceptions.AppContextLost()
35
33
  }
@@ -40,12 +38,7 @@ internal final class CoreModule: Module {
40
38
  }
41
39
 
42
40
  let runtime = try appContext.runtime
43
- if !runtime.global().hasProperty(WORKLET_RUNTIME_KEY) {
44
- throw WorkletUIRuntimeException()
45
- }
46
-
47
- let pointerHolder = runtime.global().getProperty(WORKLET_RUNTIME_KEY)
48
- if !pointerHolder.isObject() {
41
+ guard uiRuntimeHolder.isObject() else {
49
42
  throw WorkletUIRuntimeException()
50
43
  }
51
44
 
@@ -61,7 +54,7 @@ internal final class CoreModule: Module {
61
54
 
62
55
  let block = {
63
56
  do {
64
- let uiRuntime = try factory(appContext, pointerHolder, runtime)
57
+ let uiRuntime = try factory(appContext, uiRuntimeHolder, runtime)
65
58
  appContext._uiRuntime = uiRuntime
66
59
  } catch {
67
60
  errorHolder.error = error
@@ -8,20 +8,13 @@ import ExpoModulesJSI
8
8
  @objc(EXWorkletIntegration)
9
9
  public final class WorkletIntegration: NSObject {
10
10
  @objc public static func register() {
11
- AppContext.uiRuntimeFactory = { _, pointerValue, runtime in
12
- // The worklet runtime pointer is passed as a JS ArrayBuffer containing the raw address.
13
- // This is an internal transport between the worklets package's own install code and this
14
- // factory we trust the ArrayBuffer bytes to be a valid `jsi::Runtime *`. No authentication
15
- // is performed; the size check below is only a sanity guard against malformed transport.
16
- guard pointerValue.isArrayBuffer() else {
17
- throw WorkletRuntimePointerExtractionException()
18
- }
19
- let arrayBuffer = pointerValue.getArrayBuffer()
20
- guard arrayBuffer.size == MemoryLayout<UnsafeMutableRawPointer>.size else {
21
- throw WorkletRuntimePointerExtractionException()
11
+ AppContext.uiRuntimeFactory = { _, holder, runtime in
12
+ let runtimePointer: UnsafeMutableRawPointer? = runtime.withUnsafePointee { runtimePointee in
13
+ holder.withUnsafePointee { holderPointee in
14
+ WorkletRuntimeResolver.uiRuntimePointer(runtimePointer: runtimePointee, holderPointer: holderPointee)
15
+ }
22
16
  }
23
- let pointer = UnsafeMutableRawPointer(arrayBuffer.data()).load(as: UnsafeMutableRawPointer.self)
24
- guard let workletRuntime = WorkletRuntime(runtimePointer: pointer) else {
17
+ guard let runtimePointer, let workletRuntime = WorkletRuntime(runtimePointer: runtimePointer) else {
25
18
  throw WorkletRuntimePointerExtractionException()
26
19
  }
27
20
  return workletRuntime
@@ -29,7 +22,7 @@ public final class WorkletIntegration: NSObject {
29
22
  }
30
23
  }
31
24
 
32
- private final class WorkletRuntimePointerExtractionException: Exception, @unchecked Sendable {
25
+ internal final class WorkletRuntimePointerExtractionException: Exception, @unchecked Sendable {
33
26
  override var reason: String {
34
27
  "Cannot extract pointer to UI worklet runtime"
35
28
  }
@@ -25,6 +25,10 @@ NS_SWIFT_NAME(WorkletsProvider)
25
25
  - (nullable id)workletRuntimeHandleForRawPointer:(void *)rawPointer
26
26
  NS_SWIFT_NAME(workletRuntimeHandle(rawPointer:));
27
27
 
28
+ - (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
29
+ holderPointer:(const void *)holderPointer
30
+ NS_SWIFT_NAME(uiRuntimePointer(runtimePointer:holderPointer:));
31
+
28
32
  - (void)scheduleWorkletWithRuntimeHandle:(id)runtimeHandle
29
33
  serializable:(EXJavaScriptSerializable *)serializable
30
34
  arguments:(NSArray *)arguments
@@ -0,0 +1,23 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ NS_SWIFT_NAME(WorkletRuntimeResolver)
8
+ @interface EXWorkletRuntimeResolver : NSObject
9
+
10
+ /**
11
+ Resolves the raw `jsi::Runtime *` of the UI worklet runtime from a
12
+ `react-native-worklets` UI runtime holder.
13
+ @param runtimePointer Raw pointer to the RN `jsi::Runtime`.
14
+ @param holderPointer Raw pointer to the holder `jsi::Value`.
15
+ Returns NULL when the runtime can't be resolved.
16
+ */
17
+ + (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
18
+ holderPointer:(const void *)holderPointer
19
+ NS_SWIFT_NAME(uiRuntimePointer(runtimePointer:holderPointer:));
20
+
21
+ @end
22
+
23
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,15 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ #import <ExpoModulesWorklets/WorkletRuntimeResolver.h>
4
+ #import <ExpoModulesWorklets/EXWorkletsProvider.h>
5
+
6
+ @implementation EXWorkletRuntimeResolver
7
+
8
+ + (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
9
+ holderPointer:(const void *)holderPointer
10
+ {
11
+ id<EXWorkletsProvider> provider = EXWorkletsProviderRegistry.shared;
12
+ return [provider uiRuntimePointerWithRuntimePointer:runtimePointer holderPointer:holderPointer];
13
+ }
14
+
15
+ @end
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include <memory>
9
9
  #include <vector>
10
+ #include <worklets/Compat/StableApi.h>
10
11
  #include <worklets/SharedItems/Serializable.h>
11
12
  #include <worklets/WorkletRuntime/WorkletRuntime.h>
12
13
 
@@ -163,6 +164,26 @@ static jsi::Value callWorklet(jsi::Runtime &rt, std::shared_ptr<worklets::Serial
163
164
  return handle;
164
165
  }
165
166
 
167
+ - (void * _Nullable)uiRuntimePointerWithRuntimePointer:(void *)runtimePointer
168
+ holderPointer:(const void *)holderPointer
169
+ {
170
+ jsi::Runtime &rt = *reinterpret_cast<jsi::Runtime *>(runtimePointer);
171
+ const jsi::Value &holderValue = *reinterpret_cast<const jsi::Value *>(holderPointer);
172
+
173
+ if (!holderValue.isObject()) {
174
+ return nullptr;
175
+ }
176
+
177
+ jsi::Object holder = holderValue.getObject(rt);
178
+ std::shared_ptr<worklets::WorkletRuntime> workletRuntime = worklets::getWorkletRuntimeFromHolder(rt, holder);
179
+ if (!workletRuntime) {
180
+ return nullptr;
181
+ }
182
+
183
+ jsi::Runtime &uiRuntime = worklets::getJSIRuntimeFromWorkletRuntime(workletRuntime);
184
+ return &uiRuntime;
185
+ }
186
+
166
187
  - (void)scheduleWorkletWithRuntimeHandle:(id)runtimeHandle
167
188
  serializable:(EXJavaScriptSerializable *)serializable
168
189
  arguments:(NSArray *)arguments
@@ -30,4 +30,24 @@ struct WorkletsNotInstalledTests {
30
30
  _ = try dynamicType.cast(jsValue: jsValue, appContext: appContext)
31
31
  }
32
32
  }
33
+
34
+ @Test
35
+ func `UI runtime install fails with an error (not a crash) when the worklets adapter is not linked`() throws {
36
+ // Only meaningful while the adapter is unlinked (the default in this test harness).
37
+ try #require(WorkletsProviderRegistry.shared == nil)
38
+
39
+ // Installs the core-side hook that turns a UI runtime holder into a runtime.
40
+ WorkletIntegration.register()
41
+ let factory = try #require(AppContext.uiRuntimeFactory)
42
+
43
+ let appContext = AppContext.create()
44
+ let runtime = try appContext.runtime
45
+ // With no adapter linked the resolver returns NULL for any holder, so the
46
+ // factory must throw rather than dereference a null runtime pointer.
47
+ let holder = try runtime.eval("({})")
48
+
49
+ #expect(throws: WorkletRuntimePointerExtractionException.self) {
50
+ _ = try factory(appContext, holder, runtime)
51
+ }
52
+ }
33
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "56.0.17",
3
+ "version": "56.0.18",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -66,7 +66,7 @@
66
66
  "@types/invariant": "^2.2.33",
67
67
  "expo-module-scripts": "56.0.3"
68
68
  },
69
- "gitHead": "812dc007aefed0c432c0439fdfe05ee2f4f21da2",
69
+ "gitHead": "b293744c7b199c6cbe910188863e93a174c21250",
70
70
  "scripts": {
71
71
  "build": "expo-module build",
72
72
  "clean": "expo-module clean",
@@ -27,7 +27,7 @@ export function installExpoGlobalPolyfill() {
27
27
  cacheDir: undefined,
28
28
  documentsDir: undefined,
29
29
 
30
- installOnUIRuntime: () => {
30
+ installOnUIRuntime: (_uiRuntimeHolder: object) => {
31
31
  throw new Error('Method not implemented.');
32
32
  },
33
33
  };
@@ -102,10 +102,11 @@ declare namespace ExpoGlobal {
102
102
  export function reloadAppAsync(reason: string): Promise<void>;
103
103
 
104
104
  /**
105
- * Installs Expo Modules inside the Reanimated UI worklet runtime.
106
- * @platform android
105
+ * Installs Expo Modules inside the worklets UI runtime.
106
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
107
+ * `react-native-worklets`.
107
108
  */
108
- export function installOnUIRuntime(): void;
109
+ export function installOnUIRuntime(uiRuntimeHolder: object): void;
109
110
  }
110
111
 
111
112
  /* eslint-disable no-var */
@@ -1,10 +1,17 @@
1
- export function installOnUIRuntime() {
1
+ /**
2
+ * Installs Expo Modules on the UI worklet runtime so worklet callbacks and
3
+ * serializable SharedObjects work there.
4
+ *
5
+ * @param uiRuntimeHolder The UI runtime holder from `getUIRuntimeHolder()` in
6
+ * `react-native-worklets`.
7
+ */
8
+ export function installOnUIRuntime(uiRuntimeHolder: object) {
2
9
  const installOnUIRuntimeNative = globalThis?.expo?.installOnUIRuntime;
3
10
  if (!installOnUIRuntimeNative) {
4
11
  throw Error(
5
- "Native method to install Expo Modules on Reanimated UI worklet runtime wasn't found in `expo-modules-core`."
12
+ "Native method to install Expo Modules on the worklets UI runtime wasn't found in `expo-modules-core`."
6
13
  );
7
14
  }
8
15
 
9
- installOnUIRuntimeNative();
16
+ installOnUIRuntimeNative(uiRuntimeHolder);
10
17
  }