expo-updates 55.0.20 → 55.0.21
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 +8 -0
- package/android/build.gradle +2 -2
- package/ios/EXUpdates/AppController.swift +1 -0
- package/ios/EXUpdates/AppLauncher/AppLauncherNoDatabase.swift +1 -1
- package/ios/EXUpdates/AppLauncher/AppLauncherWithDatabase.swift +1 -1
- package/ios/EXUpdates/DevLauncherAppController.swift +1 -0
- package/ios/EXUpdates/DisabledAppController.swift +1 -1
- package/ios/EXUpdates/EnabledAppController.swift +1 -1
- package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesReactDelegateHandler.swift +50 -7
- package/ios/EXUpdates/UpdatesConfig.swift +1 -1
- package/ios/EXUpdates/UpdatesUtils.swift +20 -4
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,11 +10,19 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.21 — 2026-04-21
|
|
14
|
+
|
|
15
|
+
### 💡 Others
|
|
16
|
+
|
|
17
|
+
- [ios] resolve Expo.plist lookup in brownfield xcframework builds ([#44645](https://github.com/expo/expo/pull/44645) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
18
|
+
- [ios] Support multiple root view creations in brownfield ([#44771](https://github.com/expo/expo/pull/44771) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
19
|
+
|
|
13
20
|
## 55.0.20 — 2026-04-09
|
|
14
21
|
|
|
15
22
|
### 🐛 Bug fixes
|
|
16
23
|
|
|
17
24
|
- Pass absolute path to CLI helpers when creating build manifest, since the underlying functions now handle entry file inputs properly, instead of applying `mainModuleName` semantics to them ([#44414](https://github.com/expo/expo/pull/44414) by [@kitten](https://github.com/kitten))
|
|
25
|
+
- [ios] Fix loading assets in brownfield ([#44724](https://github.com/expo/expo/pull/44724) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
18
26
|
|
|
19
27
|
## 55.0.19 — 2026-04-07
|
|
20
28
|
|
package/android/build.gradle
CHANGED
|
@@ -42,7 +42,7 @@ expoModule {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
group = 'host.exp.exponent'
|
|
45
|
-
version = '55.0.
|
|
45
|
+
version = '55.0.21'
|
|
46
46
|
|
|
47
47
|
// Utility method to derive boolean values from the environment or from Java properties,
|
|
48
48
|
// and return them as strings to be used in BuildConfig fields
|
|
@@ -89,7 +89,7 @@ android {
|
|
|
89
89
|
namespace "expo.modules.updates"
|
|
90
90
|
defaultConfig {
|
|
91
91
|
versionCode 31
|
|
92
|
-
versionName '55.0.
|
|
92
|
+
versionName '55.0.21'
|
|
93
93
|
consumerProguardFiles("proguard-rules.pro")
|
|
94
94
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
95
95
|
|
|
@@ -149,6 +149,7 @@ public protocol InternalAppControllerInterface: AppControllerInterface {
|
|
|
149
149
|
var reloadScreenManager: Reloadable? { get }
|
|
150
150
|
|
|
151
151
|
var eventManager: UpdatesEventManager { get }
|
|
152
|
+
var isStarted: Bool { get }
|
|
152
153
|
func onEventListenerStartObserving()
|
|
153
154
|
|
|
154
155
|
func getConstantsForModule() -> UpdatesModuleConstants
|
|
@@ -21,7 +21,7 @@ public final class AppLauncherNoDatabase: NSObject, AppLauncher {
|
|
|
21
21
|
|
|
22
22
|
public func launchUpdate() {
|
|
23
23
|
precondition(assetFilesMap == nil, "assetFilesMap should be null for embedded updates")
|
|
24
|
-
launchAssetUrl =
|
|
24
|
+
launchAssetUrl = updatesBundle.url(
|
|
25
25
|
forResource: EmbeddedAppLoader.EXUpdatesBareEmbeddedBundleFilename,
|
|
26
26
|
withExtension: EmbeddedAppLoader.EXUpdatesBareEmbeddedBundleFileType
|
|
27
27
|
)
|
|
@@ -179,7 +179,7 @@ public class AppLauncherWithDatabase: NSObject, AppLauncher {
|
|
|
179
179
|
|
|
180
180
|
if launchedUpdate.status == UpdateStatus.StatusEmbedded {
|
|
181
181
|
precondition(assetFilesMap == nil, "assetFilesMap should be null for embedded updates")
|
|
182
|
-
launchAssetUrl =
|
|
182
|
+
launchAssetUrl = updatesBundle.url(
|
|
183
183
|
forResource: EmbeddedAppLoader.EXUpdatesBareEmbeddedBundleFilename,
|
|
184
184
|
withExtension: EmbeddedAppLoader.EXUpdatesBareEmbeddedBundleFileType
|
|
185
185
|
)
|
|
@@ -55,6 +55,7 @@ public final class DevLauncherAppController: NSObject, InternalAppControllerInte
|
|
|
55
55
|
public var embeddedUpdateId: UUID?
|
|
56
56
|
|
|
57
57
|
public var isEnabled: Bool
|
|
58
|
+
public let isStarted = false
|
|
58
59
|
|
|
59
60
|
public let eventManager: UpdatesEventManager = NoOpUpdatesEventManager()
|
|
60
61
|
public var reloadScreenManager: Reloadable? = ReloadScreenManager()
|
|
@@ -29,7 +29,7 @@ public class DisabledAppController: InternalAppControllerInterface, UpdatesInter
|
|
|
29
29
|
public var reloadScreenManager: Reloadable?
|
|
30
30
|
|
|
31
31
|
public let isActiveController = false
|
|
32
|
-
private var isStarted: Bool = false
|
|
32
|
+
public private(set) var isStarted: Bool = false
|
|
33
33
|
private var startupStartTime: DispatchTime?
|
|
34
34
|
private var startupEndTime: DispatchTime?
|
|
35
35
|
|
|
@@ -39,7 +39,7 @@ public class EnabledAppController: InternalAppControllerInterface, UpdatesInterf
|
|
|
39
39
|
private let updatesDirectoryInternal: URL
|
|
40
40
|
private let controllerQueue = DispatchQueue(label: "expo.controller.ControllerQueue")
|
|
41
41
|
public let isActiveController = true
|
|
42
|
-
private var isStarted = false
|
|
42
|
+
public private(set) var isStarted = false
|
|
43
43
|
private var startupStartTime: DispatchTime?
|
|
44
44
|
private var startupEndTime: DispatchTime?
|
|
45
45
|
|
|
@@ -32,10 +32,24 @@ public final class ExpoUpdatesReactDelegateHandler: ExpoReactDelegateHandler, Ap
|
|
|
32
32
|
return nil
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// If startup already completed, create the real view directly to handle
|
|
36
|
+
// brownfield re-mounts and simultaneous multi-view scenarios, given that
|
|
37
|
+
// didStartWithSuccess fires only once per controller lifetime.
|
|
38
|
+
if controller.isStarted, let launchAssetUrl = controller.launchAssetUrl() {
|
|
39
|
+
return reactDelegate.reactNativeFactory.recreateRootView(
|
|
40
|
+
withBundleURL: launchAssetUrl,
|
|
41
|
+
moduleName: moduleName,
|
|
42
|
+
initialProps: initialProperties,
|
|
43
|
+
launchOptions: launchOptions
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
self.reactDelegate = reactDelegate
|
|
36
48
|
self.launchOptions = launchOptions
|
|
37
|
-
controller.
|
|
38
|
-
|
|
49
|
+
if !controller.isStarted {
|
|
50
|
+
controller.delegate = self
|
|
51
|
+
controller.start()
|
|
52
|
+
}
|
|
39
53
|
|
|
40
54
|
self.rootViewModuleName = moduleName
|
|
41
55
|
self.rootViewInitialProperties = initialProperties
|
|
@@ -83,14 +97,27 @@ public final class ExpoUpdatesReactDelegateHandler: ExpoReactDelegateHandler, Ap
|
|
|
83
97
|
launchOptions: self.launchOptions
|
|
84
98
|
)
|
|
85
99
|
|
|
86
|
-
let window = getWindow()
|
|
87
|
-
let rootViewController = reactDelegate.createRootViewController()
|
|
88
100
|
#if os(iOS) || os(tvOS)
|
|
89
101
|
rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
|
|
103
|
+
// In brownfield setups, the deferred root view is embedded within the host app's
|
|
104
|
+
// view hierarchy (e.g. inside a NavigationController). Replacing the window's root
|
|
105
|
+
// view controller would break the host app's navigation. Instead, find the view
|
|
106
|
+
// controller that owns the deferred view and replace its view in-place.
|
|
107
|
+
if let deferredRootView = self.deferredRootView,
|
|
108
|
+
let owningViewController = findViewController(for: deferredRootView),
|
|
109
|
+
owningViewController != getWindow().rootViewController {
|
|
110
|
+
owningViewController.view = rootView
|
|
111
|
+
} else {
|
|
112
|
+
let window = getWindow()
|
|
113
|
+
let rootViewController = reactDelegate.createRootViewController()
|
|
114
|
+
rootViewController.view = rootView
|
|
115
|
+
window.rootViewController = rootViewController
|
|
116
|
+
window.makeKeyAndVisible()
|
|
117
|
+
}
|
|
93
118
|
#else
|
|
119
|
+
let window = getWindow()
|
|
120
|
+
let rootViewController = reactDelegate.createRootViewController()
|
|
94
121
|
rootViewController.view = rootView
|
|
95
122
|
rootView.frame = window.frame
|
|
96
123
|
window.contentViewController = rootViewController
|
|
@@ -133,6 +160,22 @@ public final class ExpoUpdatesReactDelegateHandler: ExpoReactDelegateHandler, Ap
|
|
|
133
160
|
|
|
134
161
|
return view
|
|
135
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
Finds the nearest view controller that owns the given view by walking
|
|
166
|
+
up the responder chain. Returns the first UIViewController whose view
|
|
167
|
+
matches the target view.
|
|
168
|
+
*/
|
|
169
|
+
private func findViewController(for view: UIView) -> UIViewController? {
|
|
170
|
+
var responder: UIResponder? = view.next
|
|
171
|
+
while let current = responder {
|
|
172
|
+
if let viewController = current as? UIViewController, viewController.view == view {
|
|
173
|
+
return viewController
|
|
174
|
+
}
|
|
175
|
+
responder = current.next
|
|
176
|
+
}
|
|
177
|
+
return nil
|
|
178
|
+
}
|
|
136
179
|
#endif
|
|
137
180
|
|
|
138
181
|
private func getWindow() -> UIWindow {
|
|
@@ -145,7 +145,7 @@ public final class UpdatesConfig: NSObject {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
private static func configDictionaryWithExpoPlist(mergingOtherDictionary: [String: Any]?) throws -> [String: Any] {
|
|
148
|
-
guard let configPlistPath =
|
|
148
|
+
guard let configPlistPath = updatesBundle.path(forResource: PlistName, ofType: "plist") else {
|
|
149
149
|
throw UpdatesConfigError.ExpoUpdatesConfigPlistError
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -16,6 +16,22 @@ internal extension Array where Element: Equatable {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* In brownfield setups the Expo project is packaged as an xcframework, so
|
|
21
|
+
* resources like Expo.plist, main.jsbundle, and image assets live in the
|
|
22
|
+
* framework bundle instead of the host app's main bundle.
|
|
23
|
+
*
|
|
24
|
+
* `updatesBundle` resolves to whichever bundle actually contains the
|
|
25
|
+
* expo-updates resources at runtime: `Bundle.main` for standard apps,
|
|
26
|
+
* or the framework bundle for brownfield xcframeworks.
|
|
27
|
+
*/
|
|
28
|
+
internal let updatesBundle: Bundle = {
|
|
29
|
+
if Bundle.main.path(forResource: "Expo", ofType: "plist") != nil {
|
|
30
|
+
return Bundle.main
|
|
31
|
+
}
|
|
32
|
+
return Bundle(for: UpdatesUtils.self)
|
|
33
|
+
}()
|
|
34
|
+
|
|
19
35
|
@objc(EXUpdatesUtils)
|
|
20
36
|
@objcMembers
|
|
21
37
|
public final class UpdatesUtils: NSObject {
|
|
@@ -110,16 +126,16 @@ public final class UpdatesUtils: NSObject {
|
|
|
110
126
|
|
|
111
127
|
internal static func url(forBundledAsset asset: UpdateAsset) -> URL? {
|
|
112
128
|
guard let mainBundleDir = asset.mainBundleDir else {
|
|
113
|
-
return
|
|
129
|
+
return updatesBundle.url(forResource: asset.mainBundleFilename, withExtension: asset.type)
|
|
114
130
|
}
|
|
115
|
-
return
|
|
131
|
+
return updatesBundle.url(forResource: asset.mainBundleFilename, withExtension: asset.type, subdirectory: mainBundleDir)
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
internal static func path(forBundledAsset asset: UpdateAsset) -> String? {
|
|
119
135
|
guard let mainBundleDir = asset.mainBundleDir else {
|
|
120
|
-
return
|
|
136
|
+
return updatesBundle.path(forResource: asset.mainBundleFilename, ofType: asset.type)
|
|
121
137
|
}
|
|
122
|
-
return
|
|
138
|
+
return updatesBundle.path(forResource: asset.mainBundleFilename, ofType: asset.type, inDirectory: mainBundleDir)
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.21",
|
|
4
4
|
"description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"chalk": "^4.1.2",
|
|
46
46
|
"debug": "^4.3.4",
|
|
47
47
|
"expo-eas-client": "~55.0.5",
|
|
48
|
-
"expo-manifests": "~55.0.
|
|
48
|
+
"expo-manifests": "~55.0.16",
|
|
49
49
|
"expo-structured-headers": "~55.0.2",
|
|
50
|
-
"expo-updates-interface": "~55.1.
|
|
50
|
+
"expo-updates-interface": "~55.1.6",
|
|
51
51
|
"getenv": "^2.0.0",
|
|
52
52
|
"glob": "^13.0.0",
|
|
53
53
|
"ignore": "^5.3.1",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"react": "*",
|
|
72
72
|
"react-native": "*"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "e37e614d97c3ca53f16b91609a787675d044c284"
|
|
75
75
|
}
|