expo-modules-jsi 56.0.3 → 56.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.5 — 2026-05-15
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 56.0.4 — 2026-05-13
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [iOS] Fixed xcframework build cache not invalidating when React-jsi headers change. ([#45735](https://github.com/expo/expo/pull/45735) by [@tsapeta](https://github.com/tsapeta))
22
+ - [iOS] Added support for `facebook::jsi::IRuntime` so the package builds against React Native 0.86+, while staying compatible with 0.85 and react-native-tvos. ([#45728](https://github.com/expo/expo/pull/45728) by [@zoontek](https://github.com/zoontek))
23
+
13
24
  ## 56.0.3 — 2026-05-11
14
25
 
15
26
  ### 🐛 Bug fixes
@@ -14,3 +14,11 @@ Namespaces:
14
14
  SwiftImportAs: reference
15
15
  SwiftReleaseOp: immortal
16
16
  SwiftRetainOp: immortal
17
+ - Name: IRuntime
18
+ SwiftImportAs: reference
19
+ SwiftReleaseOp: immortal
20
+ SwiftRetainOp: immortal
21
+ - Name: ICast
22
+ SwiftImportAs: reference
23
+ SwiftReleaseOp: immortal
24
+ SwiftRetainOp: immortal
@@ -7,21 +7,21 @@ internal import ExpoModulesJSI_Cxx
7
7
  */
8
8
  internal protocol JSIRepresentable: JavaScriptRepresentable, Sendable, ~Copyable {
9
9
  /**
10
- Creates an instance of this type from the given `facebook.jsi.Value` in `facebook.jsi.Runtime`.
10
+ Creates an instance of this type from the given `facebook.jsi.Value` in `facebook.jsi.IRuntime`.
11
11
  */
12
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Self
12
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self
13
13
  /**
14
14
  Creates a JSI value representing this value in the given JSI runtime.
15
15
  */
16
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value
16
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value
17
17
  }
18
18
 
19
19
  internal extension JSIRepresentable {
20
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Self {
20
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self {
21
21
  FatalError.unimplemented()
22
22
  }
23
23
 
24
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
24
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
25
25
  FatalError.unimplemented()
26
26
  }
27
27
  }
@@ -29,11 +29,11 @@ internal extension JSIRepresentable {
29
29
  // MARK: - Implementations
30
30
 
31
31
  extension Bool: JSIRepresentable {
32
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Bool {
32
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Bool {
33
33
  return value.getBool()
34
34
  }
35
35
 
36
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
36
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
37
37
  return facebook.jsi.Value(self)
38
38
  }
39
39
  }
@@ -41,19 +41,19 @@ extension Bool: JSIRepresentable {
41
41
  internal protocol JSIRepresentableNumber: JSIRepresentable {}
42
42
 
43
43
  extension JSIRepresentableNumber {
44
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Int where Self: FixedWidthInteger {
44
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Int where Self: FixedWidthInteger {
45
45
  return Int(value.getNumber())
46
46
  }
47
47
 
48
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Double where Self: BinaryFloatingPoint {
48
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Double where Self: BinaryFloatingPoint {
49
49
  return value.getNumber()
50
50
  }
51
51
 
52
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value where Self: FixedWidthInteger {
52
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value where Self: FixedWidthInteger {
53
53
  return facebook.jsi.Value(Double(self))
54
54
  }
55
55
 
56
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value where Self: BinaryFloatingPoint {
56
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value where Self: BinaryFloatingPoint {
57
57
  return facebook.jsi.Value(Double(self))
58
58
  }
59
59
  }
@@ -74,30 +74,30 @@ extension Float64: JSIRepresentableNumber {}
74
74
  extension CGFloat: JSIRepresentableNumber {}
75
75
 
76
76
  extension String: JSIRepresentable {
77
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> String {
77
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> String {
78
78
  return String(value.getString(runtime).utf8(runtime))
79
79
  }
80
80
 
81
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
81
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
82
82
  return facebook.jsi.Value(runtime, facebook.jsi.String.createFromUtf8(runtime, std.string(self)))
83
83
  }
84
84
  }
85
85
 
86
86
  extension Optional: JSIRepresentable where Wrapped: JSIRepresentable {
87
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Self {
87
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self {
88
88
  if value.isNull() || value.isUndefined() {
89
89
  return nil
90
90
  }
91
91
  return Wrapped.fromJSIValue(value, in: runtime)
92
92
  }
93
93
 
94
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
94
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
95
95
  return self?.toJSIValue(in: runtime) ?? .null()
96
96
  }
97
97
  }
98
98
 
99
99
  extension Array: JSIRepresentable where Element: JSIRepresentable {
100
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Array<Element> {
100
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Array<Element> {
101
101
  let jsiArray = value.getObject(runtime).getArray(runtime)
102
102
  let size = jsiArray.size(runtime)
103
103
  var result: Self = []
@@ -110,7 +110,7 @@ extension Array: JSIRepresentable where Element: JSIRepresentable {
110
110
  return result
111
111
  }
112
112
 
113
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
113
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
114
114
  let jsiArray = facebook.jsi.Array(runtime, count)
115
115
 
116
116
  for index in 0..<count {
@@ -121,7 +121,7 @@ extension Array: JSIRepresentable where Element: JSIRepresentable {
121
121
  }
122
122
 
123
123
  extension Dictionary: JSIRepresentable where Key == String, Value: JSIRepresentable {
124
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> Dictionary<Key, Value> {
124
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Dictionary<Key, Value> {
125
125
  let object = value.getObject(runtime)
126
126
  let propertyNames = object.getPropertyNames(runtime)
127
127
  let size = propertyNames.size(runtime)
@@ -137,7 +137,7 @@ extension Dictionary: JSIRepresentable where Key == String, Value: JSIRepresenta
137
137
  return result
138
138
  }
139
139
 
140
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
140
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
141
141
  let object = facebook.jsi.Object(runtime)
142
142
 
143
143
  for (key, value) in self {
@@ -91,11 +91,11 @@ public final class JavaScriptRef<T: JavaScriptType & ~Copyable>: JavaScriptType,
91
91
 
92
92
  extension JavaScriptRef: JavaScriptRepresentable where T: JavaScriptRepresentable & ~Copyable {}
93
93
  extension JavaScriptRef: JSIRepresentable where T: JSIRepresentable & ~Copyable {
94
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptRef {
94
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptRef {
95
95
  FatalError.unimplemented()
96
96
  }
97
97
 
98
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
98
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
99
99
  return take()?.toJSIValue(in: runtime) ?? .undefined()
100
100
  }
101
101
  }
@@ -22,16 +22,38 @@ internal import ExpoModulesJSI_Cxx
22
22
  */
23
23
  open class JavaScriptRuntime: Equatable, @unchecked Sendable {
24
24
  /**
25
- The underlying JSI runtime this `JavaScriptRuntime` points to.
26
- Note that `facebook.jsi.Runtime` is annotated with `SWIFT_UNSAFE_REFERENCE` in our copy of `jsi.h` header,
27
- so for the Swift compiler it is treated as a reference type (like `class` and not `struct`).
28
- This is important because the `facebook.jsi.Runtime`:
29
- - is an abstract class with many virtual methods. Swift/C++ interop does not support calling pure virtual methods on value types.
30
- - is non-copyable. As a value type, we would have to "borrow" it from React Native in an unsafe manner.
31
- */
32
- internal let pointee: facebook.jsi.Runtime
25
+ The underlying JSI runtime this `JavaScriptRuntime` points to, exposed as
26
+ `IRuntime` the abstract base interface that virtually all JSI value/object/
27
+ function methods take (`Value::getString`, `Object::setProperty`,
28
+ `Function::call`, …) since RN 0.86 split the API. Stored as the upcast result
29
+ of `runtimePointee` because Swift's C++ interop does not auto-upcast between
30
+ two `SwiftImportAs: reference` types.
31
+
32
+ Use ``runtimePointee`` instead when you specifically need a `jsi::Runtime&`
33
+ (e.g. constructing an `expo.RuntimeScheduler` whose binding lookup is typed on
34
+ `Runtime&` upstream).
35
+
36
+ Note that `facebook.jsi.IRuntime` and `facebook.jsi.Runtime` are imported as
37
+ reference types so for the Swift compiler they are treated like classes.
38
+ This is important because they:
39
+ - are abstract classes with many virtual methods. Swift/C++ interop does not support calling pure virtual methods on value types.
40
+ - are non-copyable. As value types, we would have to "borrow" them from React Native in an unsafe manner.
41
+ */
42
+ internal let pointee: facebook.jsi.IRuntime
43
+ internal let runtimePointee: facebook.jsi.Runtime
33
44
  internal let scheduler: expo.RuntimeScheduler
34
45
 
46
+ /**
47
+ Thread ID of the JavaScript thread, captured at construction time. Used by `isOnJavaScriptThread()`
48
+ for a fast integer comparison instead of `Thread.current.name == "..."`.
49
+ Assumes runtime initializers always run on the JS thread.
50
+ */
51
+ private let jsThreadID: UInt64 = {
52
+ var id: UInt64 = 0
53
+ pthread_threadid_np(nil, &id)
54
+ return id
55
+ }()
56
+
35
57
  /**
36
58
  Actor for running runtime work.
37
59
  */
@@ -43,7 +65,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
43
65
  `init(unsafePointer:nativeScheduler:dispatch:)` instead.
44
66
  */
45
67
  internal init(_ runtime: facebook.jsi.Runtime) {
46
- self.pointee = runtime
68
+ self.runtimePointee = runtime
69
+ self.pointee = expo.iruntime(runtime)
47
70
  self.scheduler = expo.RuntimeScheduler()
48
71
  }
49
72
 
@@ -52,7 +75,9 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
52
75
  no React scheduler is wired up.
53
76
  */
54
77
  public init() {
55
- self.pointee = expo.createHermesRuntime()
78
+ let runtime = expo.createHermesRuntime()
79
+ self.runtimePointee = runtime
80
+ self.pointee = expo.iruntime(runtime)
56
81
  self.scheduler = expo.RuntimeScheduler()
57
82
  }
58
83
 
@@ -63,7 +88,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
63
88
  */
64
89
  public init(unsafePointer: UnsafeMutableRawPointer) {
65
90
  let runtime = unsafeBitCast(unsafePointer, to: facebook.jsi.Runtime.self)
66
- self.pointee = runtime
91
+ self.runtimePointee = runtime
92
+ self.pointee = expo.iruntime(runtime)
67
93
  self.scheduler = expo.RuntimeScheduler()
68
94
  }
69
95
 
@@ -85,7 +111,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
85
111
  ) {
86
112
  let runtime = unsafeBitCast(unsafePointer, to: facebook.jsi.Runtime.self)
87
113
  let fn = unsafeBitCast(dispatch, to: expo.RuntimeScheduler.ScheduleFn.self)
88
- self.pointee = runtime
114
+ self.runtimePointee = runtime
115
+ self.pointee = expo.iruntime(runtime)
89
116
  self.scheduler = expo.RuntimeScheduler(scheduler, fn)
90
117
  }
91
118
 
@@ -94,7 +121,7 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
94
121
  The pointer is valid only for the duration of the closure and must not be stored or escaped.
95
122
  */
96
123
  public func withUnsafePointee<R>(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R {
97
- return try body(Unmanaged<facebook.jsi.Runtime>.passUnretained(pointee).toOpaque())
124
+ return try body(Unmanaged<facebook.jsi.Runtime>.passUnretained(runtimePointee).toOpaque())
98
125
  }
99
126
 
100
127
  /**
@@ -346,7 +373,7 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
346
373
  let argumentsRef = arguments.copy().ref()
347
374
 
348
375
  // Switch to asynchronous context.
349
- self.schedule(taskName: "[JS] Async function \(name)") {
376
+ self.schedule {
350
377
  // Invoke the asynchronous function and resolve/reject the promise.
351
378
  do {
352
379
  let result = try await function(this, argumentsRef.take())
@@ -383,11 +410,10 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
383
410
 
384
411
  public func schedule(
385
412
  priority: SchedulerPriority = .normal,
386
- taskName: String? = "[JS] runtime.schedule (\(#function))",
387
413
  @_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> Void
388
414
  ) -> Void {
389
415
  schedule(priority: priority) {
390
- Task.immediate_polyfill(name: taskName) {
416
+ Task.immediate_polyfill {
391
417
  try await closure()
392
418
  }
393
419
  }
@@ -398,8 +424,14 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
398
424
  Not available in async contexts to prevent blocking the cooperative thread pool.
399
425
  */
400
426
  @available(*, noasync)
427
+ @discardableResult
401
428
  public func execute<R: Sendable>(@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () throws -> R) throws -> sending R {
429
+ if isOnJavaScriptThread() {
430
+ return try JavaScriptActor.assumeIsolated(closure)
431
+ }
432
+
402
433
  var result: Result<R, any Error>!
434
+ nonisolated(unsafe) let callerRunLoop = CFRunLoopGetCurrent()
403
435
 
404
436
  scheduler.scheduleTask(.ImmediatePriority) {
405
437
  do {
@@ -407,12 +439,23 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
407
439
  } catch {
408
440
  result = .failure(error)
409
441
  }
442
+ // Wake the caller's run loop so its `CFRunLoopRunInMode(...)` returns immediately
443
+ // instead of waiting out the timeout backstop.
444
+ CFRunLoopPerformBlock(callerRunLoop, CFRunLoopMode.commonModes.rawValue) {}
445
+ CFRunLoopWakeUp(callerRunLoop)
410
446
  }
411
447
 
412
- // Use RunLoop to wait for the task to finish. As opposed to DispatchSemaphore or DispatchGroup,
413
- // this solution lets the current run loop to process other events in the meantime.
448
+ // Pump the caller's run loop until the task finishes. As opposed to DispatchSemaphore
449
+ // or DispatchGroup, this lets the run loop continue to process other events in the meantime,
450
+ // and the spin is also faster than a real kernel-mediated context switch when the JS work
451
+ // is short (the common case). The 100ms timeout is a backstop in case the wakeup is missed;
452
+ // the common path is woken by `CFRunLoopWakeUp` from the scheduled block above.
453
+ //
454
+ // `CFRunLoopRunInMode` is the C API rather than `RunLoop.current.run(mode:before:)` to
455
+ // avoid the per-iteration `+[NSRunLoop currentRunLoop]` autorelease push and `Date()`
456
+ // allocation that dominated the caller-thread profile otherwise.
414
457
  while result == nil {
415
- RunLoop.current.run(mode: .common, before: Date().addingTimeInterval(0.001))
458
+ CFRunLoopRunInMode(.commonModes, 0.1, false)
416
459
  }
417
460
  return try result.get()
418
461
  }
@@ -422,26 +465,40 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
422
465
  Not available in async contexts to prevent blocking the cooperative thread pool.
423
466
  */
424
467
  @available(*, noasync)
468
+ @discardableResult
425
469
  public func execute<R: Sendable>(
426
- taskName: String? = "[JS] runtime.execute (\(#function))",
427
470
  @_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> R
428
471
  ) throws -> sending R {
429
472
  let result = NonisolatedUnsafeVar<Result<R, any Error>>()
473
+ let runInline = isOnJavaScriptThread()
474
+ // Wrapped in `NonisolatedUnsafeVar` instead of `nonisolated(unsafe) let`
475
+ // to work around a Swift 6.2.3 compiler bug.
476
+ let callerRunLoop = NonisolatedUnsafeVar(CFRunLoopGetCurrent())
430
477
 
431
- scheduler.scheduleTask(.ImmediatePriority) {
432
- Task.immediate_polyfill(name: taskName, priority: .high) {
478
+ func body() -> Void {
479
+ Task.immediate_polyfill(priority: .high) {
433
480
  do {
434
481
  result.value = .success(try await closure())
435
482
  } catch {
436
483
  result.value = .failure(error)
437
484
  }
485
+ // Wake the caller's run loop so its `CFRunLoopRunInMode(...)` returns immediately
486
+ // instead of waiting out the timeout backstop.
487
+ CFRunLoopPerformBlock(callerRunLoop.value, CFRunLoopMode.commonModes.rawValue) {}
488
+ CFRunLoopWakeUp(callerRunLoop.value)
438
489
  }
439
490
  }
491
+ if runInline {
492
+ body()
493
+ } else {
494
+ scheduler.scheduleTask(.ImmediatePriority, body)
495
+ }
440
496
 
441
- // Use RunLoop to wait for the task to finish. As opposed to DispatchSemaphore or DispatchGroup,
442
- // this solution lets the current run loop to process other events in the meantime.
497
+ // Pump the caller's run loop until the task finishes. See the sync overload above for
498
+ // the rationale on `CFRunLoopRunInMode` vs. `RunLoop.current.run(...)` and on pumping
499
+ // the run loop instead of blocking on a semaphore.
443
500
  while result.value == nil {
444
- RunLoop.current.run(mode: .common, before: Date().addingTimeInterval(0.001))
501
+ CFRunLoopRunInMode(.commonModes, 0.1, false)
445
502
  }
446
503
  return try result.value.get()
447
504
  }
@@ -449,9 +506,13 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
449
506
  /**
450
507
  Asynchronously executes a sync closure on the JavaScript runtime thread, awaiting its completion without blocking.
451
508
  */
509
+ @discardableResult
452
510
  public func execute<R: Sendable>(
453
511
  @_implicitSelfCapture _ closure: @escaping @JavaScriptActor () throws -> R
454
512
  ) async throws -> sending R {
513
+ if isOnJavaScriptThread() {
514
+ return try JavaScriptActor.assumeIsolated(closure)
515
+ }
455
516
  return try await withUnsafeThrowingContinuation { continuation in
456
517
  scheduler.scheduleTask(.ImmediatePriority) {
457
518
  do {
@@ -466,13 +527,16 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
466
527
  /**
467
528
  Asynchronously executes an async closure on the JavaScript runtime thread, awaiting its completion without blocking.
468
529
  */
530
+ @discardableResult
469
531
  public func execute<R: Sendable>(
470
- taskName: String? = "[JS] runtime.execute (async \(#function))",
471
532
  @_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> R
472
533
  ) async throws -> sending R {
534
+ if isOnJavaScriptThread() {
535
+ return try await Task.immediate_polyfill(priority: .high, operation: closure).value
536
+ }
473
537
  return try await withUnsafeThrowingContinuation { continuation in
474
538
  scheduler.scheduleTask(.ImmediatePriority) {
475
- Task.immediate_polyfill(name: taskName, priority: .high) { @JavaScriptActor in
539
+ Task.immediate_polyfill(priority: .high) { @JavaScriptActor in
476
540
  do {
477
541
  continuation.resume(returning: try await closure())
478
542
  } catch {
@@ -486,14 +550,18 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
486
550
  /**
487
551
  Checks whether the function is called on the JavaScript thread.
488
552
  */
489
- public func isOnJavaScriptThread() -> Bool {
490
- return Thread.current.name == "com.facebook.react.runtime.JavaScript"
553
+ @inline(__always)
554
+ public final func isOnJavaScriptThread() -> Bool {
555
+ var current: UInt64 = 0
556
+ pthread_threadid_np(nil, &current)
557
+ return current == jsThreadID
491
558
  }
492
559
 
493
560
  /**
494
561
  Asserts whether we are on the JavaScript thread. Helpful for debugging threading issues.
495
562
  */
496
- public func assertThread(file: String = #file, function: String = #function, line: Int = #line) {
563
+ @inline(__always)
564
+ public final func assertThread(file: String = #file, function: String = #function, line: Int = #line) {
497
565
  assert(isOnJavaScriptThread(), "Function '\(function)' is not run on the JavaScript thread (\(file):\(line))")
498
566
  }
499
567
 
@@ -308,11 +308,11 @@ extension JavaScriptBigInt: JavaScriptRepresentable {
308
308
  }
309
309
 
310
310
  extension JavaScriptBigInt: JSIRepresentable {
311
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptBigInt {
311
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptBigInt {
312
312
  fatalError("Not implemented: Use JavaScriptValue.getBigInt() instead")
313
313
  }
314
314
 
315
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
315
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
316
316
  return asJSIValue()
317
317
  }
318
318
  }
@@ -67,11 +67,11 @@ extension JavaScriptError: JavaScriptRepresentable {
67
67
  }
68
68
 
69
69
  extension JavaScriptError: JSIRepresentable {
70
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptError {
70
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptError {
71
71
  FatalError.unimplemented()
72
72
  }
73
73
 
74
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
74
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
75
75
  return asJSIValue()
76
76
  }
77
77
  }
@@ -130,11 +130,11 @@ extension JavaScriptFunction: JavaScriptRepresentable {
130
130
  }
131
131
 
132
132
  extension JavaScriptFunction: JSIRepresentable {
133
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptFunction {
133
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptFunction {
134
134
  FatalError.unimplemented()
135
135
  }
136
136
 
137
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
137
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
138
138
  return asJSIValue()
139
139
  }
140
140
  }
@@ -587,11 +587,11 @@ extension JavaScriptObject: JavaScriptRepresentable {
587
587
  }
588
588
 
589
589
  extension JavaScriptObject: JSIRepresentable {
590
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptObject {
590
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptObject {
591
591
  FatalError.unimplemented()
592
592
  }
593
593
 
594
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
594
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
595
595
  return asJSIValue()
596
596
  }
597
597
  }
@@ -704,11 +704,11 @@ extension JavaScriptValue: JavaScriptRepresentable {
704
704
  }
705
705
 
706
706
  extension JavaScriptValue: JSIRepresentable {
707
- static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.Runtime) -> JavaScriptValue {
707
+ static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptValue {
708
708
  FatalError.unimplemented()
709
709
  }
710
710
 
711
- func toJSIValue(in runtime: facebook.jsi.Runtime) -> facebook.jsi.Value {
711
+ func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
712
712
  return facebook.jsi.Value(runtime, pointee)
713
713
  }
714
714
  }
@@ -23,7 +23,7 @@ TypedArrayKind getTypedArrayKindForName(const std::string &name) {
23
23
  return nameToKindMap.at(name);
24
24
  }
25
25
 
26
- TypedArrayKind getTypedArrayKind(jsi::Runtime &runtime, const jsi::Object &jsObj) {
26
+ TypedArrayKind getTypedArrayKind(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
27
27
  auto constructorName = jsObj.getPropertyAsObject(runtime, "constructor")
28
28
  .getProperty(runtime, "name")
29
29
  .asString(runtime)
@@ -31,7 +31,7 @@ TypedArrayKind getTypedArrayKind(jsi::Runtime &runtime, const jsi::Object &jsObj
31
31
  return getTypedArrayKindForName(constructorName);
32
32
  }
33
33
 
34
- jsi::ArrayBuffer getTypedArrayBuffer(jsi::Runtime &runtime, const jsi::Object &jsObj) {
34
+ jsi::ArrayBuffer getTypedArrayBuffer(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
35
35
  auto buffer = jsObj.getProperty(runtime, "buffer");
36
36
  if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) {
37
37
  return buffer.asObject(runtime).getArrayBuffer(runtime);
@@ -39,7 +39,7 @@ jsi::ArrayBuffer getTypedArrayBuffer(jsi::Runtime &runtime, const jsi::Object &j
39
39
  throw std::runtime_error("no ArrayBuffer attached");
40
40
  }
41
41
 
42
- bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj) {
42
+ bool isTypedArray(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
43
43
  jsi::Object ArrayBuffer = runtime
44
44
  .global()
45
45
  .getPropertyAsObject(runtime, "ArrayBuffer");
@@ -3,7 +3,8 @@
3
3
  #include <exception>
4
4
  #include <memory>
5
5
  #include <swift/bridging>
6
- #include <jsi/jsi.h>
6
+
7
+ #include "IRuntimeCompat.h"
7
8
 
8
9
  namespace expo {
9
10
 
@@ -62,7 +63,7 @@ public:
62
63
  Returns the underlying JavaScript value of the wrapped `jsi::JSError`.
63
64
  This is a JavaScript Error object with all its properties preserved.
64
65
  */
65
- inline jsi::Value asValue(jsi::Runtime &runtime) noexcept {
66
+ inline jsi::Value asValue(jsi::IRuntime &runtime) noexcept {
66
67
  return jsi::Value(runtime, jsError.value());
67
68
  }
68
69
 
@@ -72,7 +73,7 @@ public:
72
73
  using `getCurrent()`. The function returns `nullptr` when an exception occurs.
73
74
  */
74
75
  template <typename Result>
75
- inline static Result tryCatch(jsi::Runtime &runtime, Result(^block)(void)) {
76
+ inline static Result tryCatch(jsi::IRuntime &runtime, Result(^block)(void)) {
76
77
  try {
77
78
  return block();
78
79
  } catch (jsi::JSError e) {
@@ -116,7 +117,7 @@ public:
116
117
  Sets the current thread's error by creating a `jsi::JSError` from the given message.
117
118
  Called from Swift when a host function closure throws a plain error.
118
119
  */
119
- inline static void setCurrent(jsi::Runtime &runtime, const std::string &message) {
120
+ inline static void setCurrent(jsi::IRuntime &runtime, const std::string &message) {
120
121
  _current = std::make_unique<CppError>(jsi::JSError(runtime, message));
121
122
  }
122
123
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  #include <string>
6
6
  #include <vector>
7
- #include <jsi/jsi.h>
8
7
 
9
8
  #include "CppError.h"
10
9
  #include "HostObjectCallbacks.h"
10
+ #include "IRuntimeCompat.h"
11
11
 
12
12
  namespace jsi = facebook::jsi;
13
13
 
@@ -47,7 +47,7 @@ public:
47
47
  return _callbacks.getPropertyNames();
48
48
  }
49
49
 
50
- inline static jsi::Object makeObject(jsi::Runtime &runtime, HostObjectCallbacks callbacks) {
50
+ inline static jsi::Object makeObject(jsi::IRuntime &runtime, HostObjectCallbacks callbacks) {
51
51
  return jsi::Object::createFromHostObject(runtime, std::make_shared<HostObject>(callbacks));
52
52
  }
53
53
 
@@ -0,0 +1,22 @@
1
+ #pragma once
2
+ #ifdef __cplusplus
3
+
4
+ #include <jsi/jsi.h>
5
+
6
+ // React Native 0.86 split the JSI API into the abstract `jsi::IRuntime` base
7
+ // and the concrete `jsi::Runtime` derived class — most value/object/function
8
+ // methods now take `IRuntime&` instead of `Runtime&`. Older React Native
9
+ // versions (e.g. react-native-tvos 0.85) only have `jsi::Runtime`. Alias
10
+ // `IRuntime` to `Runtime` there so the same source compiles against both.
11
+ #if __has_include(<cxxreact/ReactNativeVersion.h>)
12
+ #include <cxxreact/ReactNativeVersion.h>
13
+ #endif
14
+
15
+ #if !defined(REACT_NATIVE_VERSION_MAJOR) || \
16
+ (REACT_NATIVE_VERSION_MAJOR == 0 && REACT_NATIVE_VERSION_MINOR < 86)
17
+ namespace facebook::jsi {
18
+ using IRuntime = Runtime;
19
+ } // namespace facebook::jsi
20
+ #endif
21
+
22
+ #endif // __cplusplus
@@ -3,10 +3,11 @@
3
3
  #pragma once
4
4
  #ifdef __cplusplus
5
5
 
6
- #include <jsi/jsi.h>
7
6
  #include <hermes/hermes.h>
7
+
8
8
  #include "HostFunctionClosure.h"
9
9
  #include "CppError.h"
10
+ #include "IRuntimeCompat.h"
10
11
  #include "MemoryBuffer.h"
11
12
  #include "NativeState.h"
12
13
  #include "TypedArray.h"
@@ -19,24 +20,35 @@ namespace jsi = facebook::jsi;
19
20
 
20
21
  namespace expo {
21
22
 
22
- inline jsi::Value valueFromFunction(jsi::Runtime &runtime, const jsi::Function &function) {
23
+ /**
24
+ Upcasts a `jsi::Runtime&` to a `jsi::IRuntime&`. RN 0.86 split the JSI API into
25
+ the abstract `IRuntime` base and concrete `Runtime` derived class — most
26
+ value/object/function methods now take `IRuntime&`. Swift's C++ interop does
27
+ not auto-upcast between two `SwiftImportAs: reference` types, so Swift can't
28
+ pass a `Runtime` reference where `IRuntime` is expected without this helper.
29
+ */
30
+ inline jsi::IRuntime &iruntime(jsi::Runtime &runtime) noexcept {
31
+ return runtime;
32
+ }
33
+
34
+ inline jsi::Value valueFromFunction(jsi::IRuntime &runtime, const jsi::Function &function) {
23
35
  return jsi::Value(runtime, function);
24
36
  }
25
37
 
26
38
  // `jsi::Object::setProperty` is a template function that Swift does not support. We need to provide specialized versions.
27
- inline void setProperty(jsi::Runtime &runtime, const jsi::Object &object, const char *name, const jsi::Value value) {
39
+ inline void setProperty(jsi::IRuntime &runtime, const jsi::Object &object, const char *name, const jsi::Value value) {
28
40
  object.setProperty(runtime, name, value);
29
41
  }
30
42
 
31
- inline void setProperty(jsi::Runtime &runtime, const jsi::Array &array, const char *name, const jsi::Value &value) {
43
+ inline void setProperty(jsi::IRuntime &runtime, const jsi::Array &array, const char *name, const jsi::Value &value) {
32
44
  array.setProperty(runtime, name, value);
33
45
  }
34
46
 
35
- inline void setValueAtIndex(jsi::Runtime &runtime, const jsi::Array &array, size_t index, const jsi::Value &value) {
47
+ inline void setValueAtIndex(jsi::IRuntime &runtime, const jsi::Array &array, size_t index, const jsi::Value &value) {
36
48
  array.setValueAtIndex(runtime, index, value);
37
49
  }
38
50
 
39
- inline void setArrayLength(jsi::Runtime &runtime, const jsi::Array &array, long length) {
51
+ inline void setArrayLength(jsi::IRuntime &runtime, const jsi::Array &array, long length) {
40
52
  auto oldLength = (int)array.size(runtime);
41
53
  auto newLength = (int)length;
42
54
 
@@ -52,15 +64,15 @@ inline void setArrayLength(jsi::Runtime &runtime, const jsi::Array &array, long
52
64
  }
53
65
  }
54
66
 
55
- inline jsi::Value getProperty(jsi::Runtime &runtime, const jsi::Array &array, const jsi::PropNameID &name) {
67
+ inline jsi::Value getProperty(jsi::IRuntime &runtime, const jsi::Array &array, const jsi::PropNameID &name) {
56
68
  return array.getProperty(runtime, name);
57
69
  }
58
70
 
59
- inline jsi::Value valueFromArray(jsi::Runtime &runtime, const jsi::Array &array) {
71
+ inline jsi::Value valueFromArray(jsi::IRuntime &runtime, const jsi::Array &array) {
60
72
  return jsi::Value(runtime, array);
61
73
  }
62
74
 
63
- inline const jsi::Value valueFromError(jsi::Runtime &runtime, const jsi::JSError &error) {
75
+ inline const jsi::Value valueFromError(jsi::IRuntime &runtime, const jsi::JSError &error) {
64
76
  return jsi::Value(runtime, error.value());
65
77
  }
66
78
 
@@ -68,7 +80,7 @@ inline std::shared_ptr<const jsi::Buffer> makeSharedStringBuffer(const std::stri
68
80
  return std::make_shared<jsi::StringBuffer>(source);
69
81
  }
70
82
 
71
- inline jsi::Function createHostFunction(jsi::Runtime &runtime, const jsi::PropNameID &propName, HostFunctionClosure *closure) {
83
+ inline jsi::Function createHostFunction(jsi::IRuntime &runtime, const jsi::PropNameID &propName, HostFunctionClosure *closure) {
72
84
  auto closurePtr = std::shared_ptr<HostFunctionClosure>(closure);
73
85
  return jsi::Function::createFromHostFunction(runtime, propName, 0, [closurePtr](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *_Nonnull args, size_t count) -> jsi::Value {
74
86
  auto result = closurePtr->call(thisValue, args, count);
@@ -82,7 +94,7 @@ inline jsi::Function createHostFunction(jsi::Runtime &runtime, const jsi::PropNa
82
94
  });
83
95
  }
84
96
 
85
- inline jsi::Function createHostFunction(jsi::Runtime &runtime, const char *name, HostFunctionClosure *closure) {
97
+ inline jsi::Function createHostFunction(jsi::IRuntime &runtime, const char *name, HostFunctionClosure *closure) {
86
98
  jsi::PropNameID propName = jsi::PropNameID::forAscii(runtime, name);
87
99
  return createHostFunction(runtime, propName, closure);
88
100
  }
@@ -93,31 +105,31 @@ inline jsi::Function createHostFunction(jsi::Runtime &runtime, const char *name,
93
105
  and by JS engines themselves. Wraps the templated `jsi::Object::isHostObject<T>` so the
94
106
  check is callable from Swift, where C++ function templates cannot be imported directly.
95
107
  */
96
- inline bool isHostObject(jsi::Runtime &runtime, const jsi::Object &object) {
108
+ inline bool isHostObject(jsi::IRuntime &runtime, const jsi::Object &object) {
97
109
  return object.isHostObject<jsi::HostObject>(runtime);
98
110
  }
99
111
 
100
112
  jsi::Runtime* createHermesRuntime();
101
113
 
102
- inline jsi::Value evaluateJavaScript(jsi::Runtime &runtime, const std::shared_ptr<const jsi::Buffer>& buffer, const std::string& sourceURL) {
114
+ inline jsi::Value evaluateJavaScript(jsi::IRuntime &runtime, const std::shared_ptr<const jsi::Buffer>& buffer, const std::string& sourceURL) {
103
115
  return expo::CppError::tryCatch(runtime, ^{
104
116
  return runtime.evaluateJavaScript(buffer, sourceURL);
105
117
  });
106
118
  }
107
119
 
108
- inline jsi::Value callFunction(jsi::Runtime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
120
+ inline jsi::Value callFunction(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
109
121
  return expo::CppError::tryCatch(runtime, ^{
110
122
  return function.call(runtime, args, count);
111
123
  });
112
124
  }
113
125
 
114
- inline jsi::Value callFunctionWithThis(jsi::Runtime &runtime, const jsi::Function &function, const jsi::Object &jsThis, const jsi::Value *_Nullable args, size_t count) {
126
+ inline jsi::Value callFunctionWithThis(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Object &jsThis, const jsi::Value *_Nullable args, size_t count) {
115
127
  return expo::CppError::tryCatch(runtime, ^{
116
128
  return function.callWithThis(runtime, jsThis, args, count);
117
129
  });
118
130
  }
119
131
 
120
- inline jsi::Value callAsConstructor(jsi::Runtime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
132
+ inline jsi::Value callAsConstructor(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
121
133
  return expo::CppError::tryCatch(runtime, ^{
122
134
  return function.callAsConstructor(runtime, args, count);
123
135
  });
@@ -129,28 +141,28 @@ inline jsi::Value callAsConstructor(jsi::Runtime &runtime, const jsi::Function &
129
141
  * Converts a `jsi::ArrayBuffer` to a `jsi::Value`. Needed because Swift/C++ interop
130
142
  * does not implicitly upcast `ArrayBuffer` to `Object` for the `Value` constructor.
131
143
  */
132
- inline jsi::Value valueFromArrayBuffer(jsi::Runtime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
144
+ inline jsi::Value valueFromArrayBuffer(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
133
145
  return jsi::Value(runtime, arrayBuffer);
134
146
  }
135
147
 
136
148
  /**
137
149
  * Returns the size of the array buffer storage in bytes.
138
150
  */
139
- inline size_t arrayBufferSize(jsi::Runtime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
151
+ inline size_t arrayBufferSize(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
140
152
  return arrayBuffer.size(runtime);
141
153
  }
142
154
 
143
155
  /**
144
156
  * Returns a pointer to the underlying data of the array buffer.
145
157
  */
146
- inline uint8_t *arrayBufferData(jsi::Runtime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
158
+ inline uint8_t *arrayBufferData(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
147
159
  return arrayBuffer.data(runtime);
148
160
  }
149
161
 
150
162
  /**
151
163
  * Creates a new array buffer of the given size with zero-initialized memory.
152
164
  */
153
- inline jsi::ArrayBuffer createArrayBuffer(jsi::Runtime &runtime, size_t size) {
165
+ inline jsi::ArrayBuffer createArrayBuffer(jsi::IRuntime &runtime, size_t size) {
154
166
  uint8_t *data = new uint8_t[size]();
155
167
  auto buffer = std::make_shared<MemoryBuffer>(data, size, [data]() { delete[] data; });
156
168
  return jsi::ArrayBuffer(runtime, std::move(buffer));
@@ -161,7 +173,7 @@ inline jsi::ArrayBuffer createArrayBuffer(jsi::Runtime &runtime, size_t size) {
161
173
  * The cleanup function is called (with the cleanup context) when the ArrayBuffer is deallocated.
162
174
  */
163
175
  inline jsi::ArrayBuffer createArrayBuffer(
164
- jsi::Runtime &runtime,
176
+ jsi::IRuntime &runtime,
165
177
  uint8_t *data,
166
178
  size_t size,
167
179
  void *_Nonnull cleanupContext,
@@ -179,20 +191,20 @@ inline jsi::ArrayBuffer createArrayBuffer(
179
191
 
180
192
  // MARK: - Native state
181
193
 
182
- inline bool hasNativeState(jsi::Runtime &runtime, const jsi::Object &object) {
194
+ inline bool hasNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
183
195
  return object.hasNativeState<expo::NativeState>(runtime);
184
196
  }
185
197
 
186
- inline void setNativeState(jsi::Runtime &runtime, const jsi::Object &object, expo::NativeState &nativeState) {
198
+ inline void setNativeState(jsi::IRuntime &runtime, const jsi::Object &object, expo::NativeState &nativeState) {
187
199
  std::shared_ptr<expo::NativeState> nativeStatePtr = std::shared_ptr<expo::NativeState>(&nativeState);
188
200
  object.setNativeState(runtime, nativeStatePtr);
189
201
  }
190
202
 
191
- inline void unsetNativeState(jsi::Runtime &runtime, const jsi::Object &object) {
203
+ inline void unsetNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
192
204
  object.setNativeState(runtime, nullptr);
193
205
  }
194
206
 
195
- inline expo::NativeState *_Nullable getNativeState(jsi::Runtime &runtime, const jsi::Object &object) {
207
+ inline expo::NativeState *_Nullable getNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
196
208
  if (!object.hasNativeState<expo::NativeState>(runtime)) {
197
209
  // JSI's implementation asserts if `hasNativeState` returns true, but we prefer to make it nullable.
198
210
  return nullptr;
@@ -4,7 +4,7 @@
4
4
 
5
5
  #pragma once
6
6
 
7
- #include <jsi/jsi.h>
7
+ #include "IRuntimeCompat.h"
8
8
 
9
9
  namespace jsi = facebook::jsi;
10
10
 
@@ -26,19 +26,19 @@ enum class TypedArrayKind {
26
26
  BigUint64Array = 11,
27
27
  };
28
28
 
29
- bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj);
29
+ bool isTypedArray(jsi::IRuntime &runtime, const jsi::Object &jsObj);
30
30
 
31
31
  /**
32
32
  * Returns the `TypedArrayKind` of the given typed-array object, derived from its
33
33
  * `constructor.name` (e.g. `Uint8Array`, `Float32Array`).
34
34
  */
35
- TypedArrayKind getTypedArrayKind(jsi::Runtime &runtime, const jsi::Object &jsObj);
35
+ TypedArrayKind getTypedArrayKind(jsi::IRuntime &runtime, const jsi::Object &jsObj);
36
36
 
37
37
  /**
38
38
  * Returns the underlying `ArrayBuffer` backing the given typed-array object.
39
39
  * Throws if the object has no attached ArrayBuffer.
40
40
  */
41
- jsi::ArrayBuffer getTypedArrayBuffer(jsi::Runtime &runtime, const jsi::Object &jsObj);
41
+ jsi::ArrayBuffer getTypedArrayBuffer(jsi::IRuntime &runtime, const jsi::Object &jsObj);
42
42
 
43
43
  } // namespace expo
44
44
 
@@ -1,5 +1,6 @@
1
1
  import Testing
2
2
  import ExpoModulesJSI
3
+ import Foundation
3
4
 
4
5
  @Suite
5
6
  @JavaScriptActor
@@ -118,6 +119,49 @@ struct JavaScriptRuntimeTests {
118
119
  #expect(result == 100)
119
120
  }
120
121
 
122
+ // The execute<R> overloads have a same-thread fast path and a cross-thread path that
123
+ // schedules the closure onto the JS thread and pumps the caller's run loop until it
124
+ // completes. The tests above run on `@JavaScriptActor` (the JS thread), so they only
125
+ // exercise the fast path. The next three hop off the JS thread first to cover the
126
+ // cross-thread scheduling + run-loop pump. The sync overloads of `execute` are
127
+ // `@available(*, noasync)`, so the caller must be a real synchronous thread — wrapping
128
+ // in `Task.detached` would stay on the cooperative pool and trip the noasync diagnostic.
129
+
130
+ @Test
131
+ func `execute sync from off-thread caller`() async throws {
132
+ let runtime = self.runtime
133
+ let result = try await onSyncOffThread {
134
+ try runtime.execute { @JavaScriptActor in
135
+ return runtime.global().hasProperty("Object") ? 1 : 0
136
+ }
137
+ }
138
+ #expect(result == 1)
139
+ }
140
+
141
+ @Test
142
+ func `execute blocking-async from off-thread caller`() async throws {
143
+ let runtime = self.runtime
144
+ let result = try await onSyncOffThread {
145
+ try runtime.execute { @JavaScriptActor () async in
146
+ await Task.yield()
147
+ return runtime.global().hasProperty("Object") ? 1 : 0
148
+ }
149
+ }
150
+ #expect(result == 1)
151
+ }
152
+
153
+ @Test
154
+ func `execute sync rethrows from off-thread caller`() async throws {
155
+ let runtime = self.runtime
156
+ await #expect(throws: ScriptEvaluationError.self) {
157
+ try await onSyncOffThread {
158
+ try runtime.execute { @JavaScriptActor in
159
+ try runtime.eval("invalid syntax +++")
160
+ }
161
+ }
162
+ }
163
+ }
164
+
121
165
  // MARK: - Host objects
122
166
 
123
167
  @Test
@@ -698,3 +742,19 @@ struct JavaScriptRuntimeTests {
698
742
  #expect(result.getInt() == 3)
699
743
  }
700
744
  }
745
+
746
+ /// Runs `body` on a freshly spawned synchronous thread and bridges the result back into the
747
+ /// async test. The thread has a real run loop, which the cross-thread `execute` path pumps.
748
+ private func onSyncOffThread<R: Sendable>(
749
+ _ body: @escaping @Sendable () throws -> R
750
+ ) async throws -> R {
751
+ return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<R, any Error>) in
752
+ Thread.detachNewThread {
753
+ do {
754
+ continuation.resume(returning: try body())
755
+ } catch {
756
+ continuation.resume(throwing: error)
757
+ }
758
+ }
759
+ }
760
+ }
@@ -15,10 +15,11 @@
15
15
  # - Cleans .swiftinterface files for cross-compiler compatibility
16
16
  #
17
17
  # Usage:
18
- # PODS_ROOT=/path/to/Pods ./build-xcframework.sh [--clean]
18
+ # ./build-xcframework.sh [--clean]
19
19
  #
20
20
  # Environment:
21
- # PODS_ROOT (required) Path to the CocoaPods Pods directory
21
+ # PODS_ROOT Path to the CocoaPods Pods directory. Defaults to
22
+ # $EXPO_ROOT_DIR/apps/bare-expo/ios/Pods (set by direnv).
22
23
  # PLATFORM_NAME (optional) Build for a specific platform (e.g. iphoneos, iphonesimulator).
23
24
  # When unset, builds for both iphoneos and iphonesimulator.
24
25
 
@@ -36,6 +37,8 @@ BUILD_PRODUCTS_PATH="${DERIVED_DATA_PATH}/Build/Products"
36
37
 
37
38
  source "${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
38
39
 
40
+ resolve_pods_root "$PACKAGE_DIR"
41
+
39
42
  CLEAN=false
40
43
 
41
44
  while [[ $# -gt 0 ]]; do
@@ -74,6 +77,10 @@ SOURCE_FILES=(
74
77
  "${PACKAGE_DIR}/scripts/build-xcframework.sh"
75
78
  "${PACKAGE_DIR}/scripts/create-stub-xcframework.sh"
76
79
  "${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
80
+ # JSI headers we compile against. `cat` follows the symlinks CocoaPods
81
+ # installs into Pods/Headers/Public so the real header contents get hashed.
82
+ "${PODS_ROOT}/Headers/Public/React-jsi/jsi/jsi.h"
83
+ "${PODS_ROOT}/Headers/Public/React-jsi/jsi/jsi-inl.h"
77
84
  )
78
85
 
79
86
  compute_hash() {
@@ -29,26 +29,11 @@ set -eo pipefail
29
29
 
30
30
  PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
31
31
 
32
- # --- PODS_ROOT ---
33
- #
34
- # Prefer an explicit PODS_ROOT (e.g. to test against a different app's Pods).
35
- # Otherwise fall back to bare-expo under EXPO_ROOT_DIR (set by the repo's
36
- # direnv config). Without either, point at a likely repo root computed from
37
- # this script's location so the script still works outside a direnv shell.
38
-
39
- if [[ -z "${PODS_ROOT:-}" ]]; then
40
- : "${EXPO_ROOT_DIR:=$(cd "${PACKAGE_DIR}/../../.." && pwd)}"
41
- PODS_ROOT="${EXPO_ROOT_DIR}/apps/bare-expo/ios/Pods"
42
- fi
43
- if [[ ! -d "$PODS_ROOT" ]]; then
44
- echo "error: PODS_ROOT does not exist: $PODS_ROOT" >&2
45
- echo " Run \`pod install\` in apps/bare-expo/ios first, or set PODS_ROOT explicitly." >&2
46
- exit 1
47
- fi
48
- PODS_ROOT="$(cd "$PODS_ROOT" && pwd)"
49
- # Export so xcodebuild forwards it to SwiftPM's manifest evaluation, where
50
- # Package.swift reads it via resolvePodsRoot().
51
- export PODS_ROOT
32
+ source "${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
33
+
34
+ # Resolve PODS_ROOT with fallback logic (from xcframework-helpers.sh).
35
+ # Must be passed to subprocess calls, not exported, to avoid polluting the environment.
36
+ resolve_pods_root "$PACKAGE_DIR"
52
37
 
53
38
  # --- Symlink xcframeworks for SwiftPM binary targets ---
54
39
 
@@ -79,7 +64,7 @@ link_xcframework "ReactNativeDependencies" \
79
64
 
80
65
  # --- Generate the jsi module map ---
81
66
 
82
- "${PACKAGE_DIR}/scripts/generate-modulemap.sh"
67
+ env PODS_ROOT="$PODS_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh"
83
68
 
84
69
  # --- Pick a simulator destination ---
85
70
 
@@ -113,7 +98,7 @@ fi
113
98
  # --- Run the tests ---
114
99
 
115
100
  cd "$PACKAGE_DIR"
116
- exec xcodebuild test \
101
+ exec env PODS_ROOT="$PODS_ROOT" xcodebuild test \
117
102
  -scheme ExpoModulesJSI \
118
103
  -destination "$DESTINATION" \
119
104
  -derivedDataPath "${PACKAGE_DIR}/.DerivedData" \
@@ -4,6 +4,24 @@
4
4
  # canonical slice metadata and the Info.plist writer used by both scripts so
5
5
  # the manifest is produced from a single place.
6
6
 
7
+ # resolve_pods_root PACKAGE_DIR
8
+ # Sets PODS_ROOT, preferring an explicit value (e.g. from CocoaPods build phase)
9
+ # over a fallback to bare-expo under EXPO_ROOT_DIR. Exits if PODS_ROOT doesn't exist.
10
+ resolve_pods_root() {
11
+ local package_dir="$1"
12
+
13
+ if [[ -z "${PODS_ROOT:-}" ]]; then
14
+ : "${EXPO_ROOT_DIR:=$(cd "${package_dir}/../../.." && pwd)}"
15
+ PODS_ROOT="${EXPO_ROOT_DIR}/apps/bare-expo/ios/Pods"
16
+ fi
17
+ if [[ ! -d "$PODS_ROOT" ]]; then
18
+ echo "error: PODS_ROOT does not exist: $PODS_ROOT" >&2
19
+ echo " Run \`pod install\` in apps/bare-expo/ios first, or set PODS_ROOT explicitly." >&2
20
+ exit 1
21
+ fi
22
+ PODS_ROOT="$(cd "$PODS_ROOT" && pwd)"
23
+ }
24
+
7
25
  # All slice IDs known to the xcframework, mapped to their plist metadata.
8
26
  # CocoaPods reads Info.plist at `pod install` time and generates a per-slice
9
27
  # copy script; any slice missing from this table will be skipped by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-jsi",
3
- "version": "56.0.3",
3
+ "version": "56.0.5",
4
4
  "description": "The JavaScript Interface for Expo Modules",
5
5
  "main": "index.js",
6
6
  "sideEffects": [],
@@ -41,7 +41,7 @@
41
41
  "./apple/scripts/test.sh"
42
42
  ]
43
43
  },
44
- "gitHead": "42013232893cb2aa71ab218e9b422d4a8476b3f0",
44
+ "gitHead": "f26be3dd9396bf7c399a1d607865d0fabdbc0d64",
45
45
  "scripts": {
46
46
  "build": "apple/scripts/build-xcframework.sh",
47
47
  "test": "apple/scripts/test.sh"