expo-dev-menu 58.0.5 → 58.0.6

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,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 58.0.6 — 2026-09-23
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix a data race on `DevMenuManager`'s registered callbacks and app context that could crash the app when one app context registered its dev menu callbacks while another tore down, such as on a dev-client reload. ([#49559](https://github.com/expo/expo/pull/49559) by [@vonovak](https://github.com/vonovak))
18
+
13
19
  ## 58.0.5 — 2026-09-22
14
20
 
15
21
  ### 🐛 Bug fixes
@@ -12,7 +12,7 @@ apply plugin: 'expo-module-gradle-plugin'
12
12
  apply plugin: 'org.jetbrains.kotlin.plugin.compose'
13
13
 
14
14
  group = 'host.exp.exponent'
15
- version = '58.0.5'
15
+ version = '58.0.6'
16
16
 
17
17
  def hasDevLauncher = findProject(":expo-dev-launcher") != null
18
18
  def configureInRelease = findProperty("expo.devmenu.configureInRelease") == "true"
@@ -29,7 +29,7 @@ android {
29
29
 
30
30
  defaultConfig {
31
31
  versionCode 10
32
- versionName '58.0.5'
32
+ versionName '58.0.6'
33
33
  }
34
34
 
35
35
  buildTypes {
@@ -128,6 +128,9 @@ Pod::Spec.new do |s|
128
128
  test_spec.dependency 'React'
129
129
  # ExpoModulesCore requires React-hermes or React-jsc in tests, add ExpoModulesTestCore for the underlying dependencies
130
130
  test_spec.dependency 'ExpoModulesTestCore'
131
+ test_spec.pod_target_xcconfig = {
132
+ 'OTHER_LDFLAGS' => '$(inherited) -lc++'
133
+ }
131
134
  test_spec.platforms = {
132
135
  :ios => '16.4'
133
136
  }
@@ -55,6 +55,18 @@ class DevMenuCacheContainer<T> {
55
55
  }
56
56
  }
57
57
 
58
+ final class DevMenuCallbacksRegistry {
59
+ private let storage = Mutex<[DevMenuManager.Callback]>([])
60
+
61
+ var callbacks: [DevMenuManager.Callback] {
62
+ storage.withLock { $0 }
63
+ }
64
+
65
+ func replace(with callbacks: [DevMenuManager.Callback]) {
66
+ storage.withLock { $0 = callbacks }
67
+ }
68
+ }
69
+
58
70
  /**
59
71
  Manages the dev menu and provides most of the public API.
60
72
  */
@@ -74,10 +86,17 @@ open class DevMenuManager: NSObject {
74
86
  var canLaunchDevMenuOnStart = true
75
87
  @objc public var isReactAppRunning = false
76
88
 
89
+ private let appContextLock = NSLock()
90
+ private weak var _currentAppContext: AppContext?
91
+
77
92
  /**
78
93
  The AppContext for the currently running React app. Set by DevMenuModule.OnCreate.
79
94
  */
80
- public private(set) weak var currentAppContext: AppContext?
95
+ public var currentAppContext: AppContext? {
96
+ appContextLock.lock()
97
+ defer { appContextLock.unlock() }
98
+ return _currentAppContext
99
+ }
81
100
 
82
101
  @objc public var configuration = DevMenuConfiguration()
83
102
 
@@ -179,7 +198,9 @@ open class DevMenuManager: NSObject {
179
198
 
180
199
  @objc
181
200
  public func setAppContext(_ appContext: AppContext?) {
182
- currentAppContext = appContext
201
+ appContextLock.lock()
202
+ _currentAppContext = appContext
203
+ appContextLock.unlock()
183
204
  if appContext != nil {
184
205
  isReloading = false
185
206
  lastReloadEventAt = Date()
@@ -202,10 +223,17 @@ open class DevMenuManager: NSObject {
202
223
  */
203
224
  @objc
204
225
  public func clearAppContext(current context: AppContext?) {
205
- if currentAppContext != nil && currentAppContext !== context {
226
+ appContextLock.lock()
227
+ let shouldClear = _currentAppContext == nil || _currentAppContext === context
228
+ if shouldClear {
229
+ _currentAppContext = nil
230
+ }
231
+ appContextLock.unlock()
232
+ guard shouldClear else {
206
233
  return
207
234
  }
208
- setAppContext(nil)
235
+ isReactAppRunning = false
236
+ updateAutoLaunchObserver()
209
237
  }
210
238
 
211
239
  @objc
@@ -470,9 +498,18 @@ open class DevMenuManager: NSObject {
470
498
  callbacksSubject.eraseToAnyPublisher()
471
499
  }
472
500
 
473
- public var registeredCallbacks: [Callback] = [] {
474
- didSet {
475
- callbacksSubject.send(registeredCallbacks)
501
+ private let callbacksRegistry = DevMenuCallbacksRegistry()
502
+
503
+ public var registeredCallbacks: [Callback] {
504
+ get {
505
+ callbacksRegistry.callbacks
506
+ }
507
+ set {
508
+ callbacksRegistry.replace(with: newValue)
509
+ // PassthroughSubject doesn't support concurrent sends.
510
+ DispatchQueue.main.async { [weak self] in
511
+ self?.callbacksSubject.send(newValue)
512
+ }
476
513
  }
477
514
  }
478
515
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-dev-menu",
3
- "version": "58.0.5",
3
+ "version": "58.0.6",
4
4
  "description": "Expo/React Native module with the developer menu.",
5
5
  "keywords": [
6
6
  "dev",
@@ -40,7 +40,7 @@
40
40
  "expo": "*",
41
41
  "react-native": "*"
42
42
  },
43
- "gitHead": "add8cbf49f477cfd2283ae05af35b4713a773c55",
43
+ "gitHead": "8aeb4368d396d2d71a2fc8c652c4d1d4d53ed858",
44
44
  "scripts": {
45
45
  "build": "pnpm run build:lib && pnpm run build:plugin",
46
46
  "build:lib": "expo-build src",