expo 57.0.21 → 57.0.23

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.
@@ -10,7 +10,7 @@ buildscript {
10
10
  }
11
11
 
12
12
  group = 'host.exp.exponent'
13
- version = '57.0.21'
13
+ version = '57.0.23'
14
14
 
15
15
  expoModule {
16
16
  // We can't prebuild the module because it depends on the generated files.
@@ -21,7 +21,7 @@ android {
21
21
  namespace "expo.core"
22
22
  defaultConfig {
23
23
  versionCode 1
24
- versionName "57.0.21"
24
+ versionName "57.0.23"
25
25
  consumerProguardFiles("proguard-rules.pro")
26
26
  }
27
27
  testOptions {
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "@expo/dom-webview": "~57.0.1",
3
- "@expo/fingerprint": "~0.20.12",
3
+ "@expo/fingerprint": "~0.20.13",
4
4
  "@expo/metro-runtime": "~57.0.15",
5
5
  "@expo/vector-icons": "^15.0.2",
6
- "@expo/ui": "~57.0.17",
6
+ "@expo/ui": "~57.0.18",
7
7
  "@react-native-async-storage/async-storage": "2.2.0",
8
8
  "@react-native-community/datetimepicker": "9.1.0",
9
9
  "@react-native-masked-view/masked-view": "0.3.2",
@@ -17,85 +17,85 @@
17
17
  "expo-analytics-amplitude": "~11.3.0",
18
18
  "expo-app-auth": "~11.1.0",
19
19
  "expo-app-loader-provider": "~8.0.0",
20
- "expo-app-metrics": "~57.0.17",
21
- "expo-apple-authentication": "~57.0.1",
22
- "expo-application": "~57.0.2",
23
- "expo-asset": "~57.0.16",
24
- "expo-audio": "~57.0.4",
25
- "expo-auth-session": "~57.0.11",
26
- "expo-background-fetch": "~57.0.16",
27
- "expo-background-task": "~57.0.16",
28
- "expo-battery": "~57.0.2",
29
- "expo-blur": "~57.0.2",
30
- "expo-brightness": "~57.0.1",
31
- "expo-brownfield": "~57.0.18",
32
- "expo-build-properties": "~57.0.17",
20
+ "expo-app-metrics": "~57.0.19",
21
+ "expo-apple-authentication": "~57.0.2",
22
+ "expo-application": "~57.0.3",
23
+ "expo-asset": "~57.0.17",
24
+ "expo-audio": "~57.0.5",
25
+ "expo-auth-session": "~57.0.12",
26
+ "expo-background-fetch": "~57.0.18",
27
+ "expo-background-task": "~57.0.18",
28
+ "expo-battery": "~57.0.3",
29
+ "expo-blur": "~57.0.3",
30
+ "expo-brightness": "~57.0.2",
31
+ "expo-brownfield": "~57.0.20",
32
+ "expo-build-properties": "~57.0.18",
33
33
  "expo-calendar": "~57.0.3",
34
- "expo-camera": "~57.0.4",
35
- "expo-cellular": "~57.0.1",
34
+ "expo-camera": "~57.0.5",
35
+ "expo-cellular": "~57.0.2",
36
36
  "expo-checkbox": "~57.0.0",
37
- "expo-clipboard": "~57.0.1",
38
- "expo-constants": "~57.0.17",
39
- "expo-contacts": "~57.0.4",
40
- "expo-crypto": "~57.0.2",
41
- "expo-dev-client": "~57.0.18",
42
- "expo-device": "~57.0.1",
43
- "expo-document-picker": "~57.0.1",
44
- "expo-eas-client": "~57.0.3",
45
- "expo-file-system": "~57.0.6",
46
- "expo-font": "~57.0.3",
37
+ "expo-clipboard": "~57.0.2",
38
+ "expo-constants": "~57.0.18",
39
+ "expo-contacts": "~57.0.5",
40
+ "expo-crypto": "~57.0.3",
41
+ "expo-dev-client": "~57.0.19",
42
+ "expo-device": "~57.0.2",
43
+ "expo-document-picker": "~57.0.2",
44
+ "expo-eas-client": "~57.0.4",
45
+ "expo-file-system": "~57.0.7",
46
+ "expo-font": "~57.0.4",
47
47
  "expo-gl": "~57.0.2",
48
- "expo-glass-effect": "~57.0.2",
48
+ "expo-glass-effect": "~57.0.3",
49
49
  "expo-google-app-auth": "~8.3.0",
50
- "expo-haptics": "~57.0.2",
51
- "expo-image": "~57.0.4",
50
+ "expo-haptics": "~57.0.3",
51
+ "expo-image": "~57.0.5",
52
52
  "expo-image-loader": "~57.0.1",
53
- "expo-image-manipulator": "~57.0.16",
54
- "expo-image-picker": "~57.0.16",
53
+ "expo-image-manipulator": "~57.0.18",
54
+ "expo-image-picker": "~57.0.18",
55
55
  "expo-intent-launcher": "~57.0.1",
56
- "expo-insights": "~57.0.16",
57
- "expo-keep-awake": "~57.0.1",
58
- "expo-linear-gradient": "~57.0.1",
59
- "expo-linking": "~57.0.9",
60
- "expo-local-authentication": "~57.0.2",
61
- "expo-localization": "~57.0.1",
62
- "expo-location": "~57.0.16",
63
- "expo-mail-composer": "~57.0.1",
64
- "expo-manifests": "~57.0.1",
65
- "expo-maps": "~57.0.2",
56
+ "expo-insights": "~57.0.18",
57
+ "expo-keep-awake": "~57.0.2",
58
+ "expo-linear-gradient": "~57.0.2",
59
+ "expo-linking": "~57.0.10",
60
+ "expo-local-authentication": "~57.0.3",
61
+ "expo-localization": "~57.0.2",
62
+ "expo-location": "~57.0.18",
63
+ "expo-mail-composer": "~57.0.2",
64
+ "expo-manifests": "~57.0.2",
65
+ "expo-maps": "~57.0.3",
66
66
  "expo-mcp": "~0.2.1",
67
- "expo-media-library": "~57.0.4",
68
- "expo-mesh-gradient": "~57.0.1",
69
- "expo-module-template": "~57.0.11",
70
- "expo-modules-core": "~57.0.17",
67
+ "expo-media-library": "~57.0.5",
68
+ "expo-mesh-gradient": "~57.0.2",
69
+ "expo-module-template": "~57.0.12",
70
+ "expo-modules-core": "~57.0.18",
71
71
  "expo-navigation-bar": "~57.0.2",
72
- "expo-network": "~57.0.1",
73
- "expo-notifications": "~57.0.17",
74
- "expo-observe": "~57.0.20",
75
- "expo-print": "~57.0.1",
76
- "expo-live-photo": "~57.0.1",
77
- "expo-router": "~57.0.20",
78
- "expo-screen-capture": "~57.0.2",
72
+ "expo-network": "~57.0.2",
73
+ "expo-notifications": "~57.0.19",
74
+ "expo-observe": "~57.0.22",
75
+ "expo-print": "~57.0.2",
76
+ "expo-live-photo": "~57.0.2",
77
+ "expo-router": "~57.0.21",
78
+ "expo-screen-capture": "~57.0.3",
79
79
  "expo-screen-orientation": "~57.0.2",
80
- "expo-secure-store": "~57.0.3",
81
- "expo-sensors": "~57.0.2",
80
+ "expo-secure-store": "~57.0.4",
81
+ "expo-sensors": "~57.0.3",
82
82
  "expo-server": "~57.0.3",
83
- "expo-sharing": "~57.0.18",
84
- "expo-sms": "~57.0.1",
85
- "expo-speech": "~57.0.2",
86
- "expo-splash-screen": "~57.0.8",
87
- "expo-sqlite": "~57.0.2",
83
+ "expo-sharing": "~57.0.20",
84
+ "expo-sms": "~57.0.2",
85
+ "expo-speech": "~57.0.3",
86
+ "expo-splash-screen": "~57.0.9",
87
+ "expo-sqlite": "~57.0.3",
88
88
  "expo-status-bar": "~57.0.1",
89
- "expo-store-review": "~57.0.2",
90
- "expo-symbols": "~57.0.2",
91
- "expo-system-ui": "~57.0.3",
92
- "expo-task-manager": "~57.0.16",
93
- "expo-tracking-transparency": "~57.0.1",
94
- "expo-updates": "~57.0.21",
95
- "expo-video-thumbnails": "~57.0.1",
96
- "expo-video": "~57.0.3",
97
- "expo-web-browser": "~57.0.2",
98
- "expo-widgets": "~57.0.18",
89
+ "expo-store-review": "~57.0.3",
90
+ "expo-symbols": "~57.0.3",
91
+ "expo-system-ui": "~57.0.4",
92
+ "expo-task-manager": "~57.0.18",
93
+ "expo-tracking-transparency": "~57.0.2",
94
+ "expo-updates": "~57.0.22",
95
+ "expo-video-thumbnails": "~57.0.2",
96
+ "expo-video": "~57.0.4",
97
+ "expo-web-browser": "~57.0.3",
98
+ "expo-widgets": "~57.0.19",
99
99
  "jest-expo": "~57.0.5",
100
100
  "lottie-react-native": "~7.3.8",
101
101
  "react": "19.2.3",
@@ -116,7 +116,7 @@
116
116
  "react-native-webview": "13.16.1",
117
117
  "react-server-dom-webpack": "~19.2.4",
118
118
  "sentry-expo": "~7.0.0",
119
- "unimodules-app-loader": "~57.0.1",
119
+ "unimodules-app-loader": "~57.0.2",
120
120
  "unimodules-image-loader-interface": "~6.1.0",
121
121
  "@shopify/react-native-skia": "2.6.2",
122
122
  "@shopify/flash-list": "2.0.2",
@@ -0,0 +1,181 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import React
5
+
6
+ #if os(iOS) || os(tvOS)
7
+
8
+ /**
9
+ `UIWindowSceneDelegate` base class for Expo apps. Required by the iOS 27, which
10
+ asserts at launch unless the app adopts the scene-based life cycle.
11
+
12
+ Responsibilities:
13
+ - Create the `UIWindow` from the connecting `UIWindowScene` and start React Native into it.
14
+ - Re-feed scene life-cycle, URL, user-activity, and quick-action events to the app delegate.
15
+
16
+ Requires the app delegate to be an `ExpoAppDelegate`; it forwards every event to the subscribers,
17
+ so one call reaches both subscribers and `AppDelegate` overrides.
18
+ */
19
+ @available(iOSApplicationExtension, unavailable)
20
+ @objc(EXExpoAppSceneDelegate)
21
+ open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
22
+ open var window: UIWindow?
23
+
24
+ let forwarder = SceneEventForwarder()
25
+
26
+ open func scene(
27
+ _ scene: UIScene,
28
+ willConnectTo session: UISceneSession,
29
+ options connectionOptions: UIScene.ConnectionOptions
30
+ ) {
31
+ guard let windowScene = scene as? UIWindowScene else {
32
+ return
33
+ }
34
+ guard let appDelegate = UIApplication.shared.delegate as? ExpoAppDelegate,
35
+ let provider = appDelegate as? ExpoReactNativeFactoryProvider,
36
+ let factory = provider.reactNativeFactory else {
37
+ fatalError(
38
+ "ExpoAppSceneDelegate couldn't start React Native because the app delegate isn't an "
39
+ + "ExpoAppDelegate that provides a React Native factory. Make sure your AppDelegate subclasses "
40
+ + "ExpoAppDelegate, conforms to ExpoReactNativeFactoryProvider and creates its "
41
+ + "RCTReactNativeFactory in application(_:didFinishLaunchingWithOptions:)."
42
+ )
43
+ }
44
+
45
+ let window = UIWindow(windowScene: windowScene)
46
+ self.window = window
47
+
48
+ // Mirror the window onto the app delegate so code that reads
49
+ // `UIApplication.shared.delegate?.window` keeps working (e.g. expo-system-ui).
50
+ provider.window = window
51
+
52
+ // Under the scene life cycle UIKit passes cold-start URLs and activities in `connectionOptions`
53
+ // rather than in the app delegate's launch options. React Native's `Linking.getInitialURL()`
54
+ // only reads them from launch options, so rebuild them here; otherwise a link that cold-starts
55
+ // the app is delivered to no one, because the `url` event routed below fires before JS is ready.
56
+ let browsingWebActivity = connectionOptions.userActivities.first {
57
+ $0.activityType == NSUserActivityTypeBrowsingWeb
58
+ }
59
+ factory.startReactNative(
60
+ withModuleName: provider.reactNativeFactoryModuleName,
61
+ in: window,
62
+ launchOptions: Self.launchOptions(
63
+ url: connectionOptions.urlContexts.first?.url,
64
+ userActivity: browsingWebActivity
65
+ )
66
+ )
67
+
68
+ // Deep links / universal links.
69
+ connectionOptions.urlContexts.forEach {
70
+ forwarder.open(url: $0.url, options: Self.openURLOptions(from: $0.options))
71
+ }
72
+ connectionOptions.userActivities.forEach { forwarder.continue($0) }
73
+
74
+ #if os(iOS)
75
+ // A quick action that cold-starts the app arrives here instead of in
76
+ // `windowScene(_:performActionFor:completionHandler:)`, which UIKit only calls while running.
77
+ if let shortcutItem = connectionOptions.shortcutItem {
78
+ forwarder.perform(shortcutItem) { _ in }
79
+ }
80
+ #endif
81
+ }
82
+
83
+ open func sceneDidDisconnect(_ scene: UIScene) {
84
+ window = nil
85
+ }
86
+
87
+ open func sceneDidBecomeActive(_ scene: UIScene) {
88
+ forwarder.didBecomeActive()
89
+ }
90
+
91
+ open func sceneWillResignActive(_ scene: UIScene) {
92
+ forwarder.willResignActive()
93
+ }
94
+
95
+ open func sceneWillEnterForeground(_ scene: UIScene) {
96
+ forwarder.willEnterForeground()
97
+ }
98
+
99
+ open func sceneDidEnterBackground(_ scene: UIScene) {
100
+ forwarder.didEnterBackground()
101
+ }
102
+
103
+ open func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
104
+ URLContexts.forEach { forwarder.open(url: $0.url, options: Self.openURLOptions(from: $0.options)) }
105
+ }
106
+
107
+ open func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
108
+ forwarder.willContinueUserActivity(withType: userActivityType)
109
+ }
110
+
111
+ open func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
112
+ forwarder.continue(userActivity)
113
+ }
114
+
115
+ open func scene(
116
+ _ scene: UIScene,
117
+ didFailToContinueUserActivityWithType userActivityType: String,
118
+ error: Error
119
+ ) {
120
+ forwarder.didFailToContinueUserActivity(withType: userActivityType, error: error)
121
+ }
122
+
123
+ open func scene(_ scene: UIScene, didUpdate userActivity: NSUserActivity) {
124
+ forwarder.didUpdate(userActivity)
125
+ }
126
+
127
+ #if os(iOS)
128
+ open func windowScene(
129
+ _ windowScene: UIWindowScene,
130
+ performActionFor shortcutItem: UIApplicationShortcutItem,
131
+ completionHandler: @escaping (Bool) -> Void
132
+ ) {
133
+ forwarder.perform(shortcutItem, completionHandler: completionHandler)
134
+ }
135
+ #endif
136
+ // MARK: - Launch options
137
+
138
+ /// Rebuilds the launch options that `Linking.getInitialURL()` reads from a scene's connection
139
+ /// options. Returns `nil` when the app wasn't cold-started by a URL or a browsing-web activity,
140
+ /// so it can be forwarded to `startReactNative` as-is.
141
+ static func launchOptions(
142
+ url: URL?,
143
+ userActivity: NSUserActivity?
144
+ ) -> [UIApplication.LaunchOptionsKey: Any]? {
145
+ // Build the keys from their underlying constant strings rather than the `UIApplication`
146
+ // accessors (`.url`, `.userActivityDictionary`): those accessors are deprecated as of iOS 26
147
+ // in favor of the scene APIs, but React Native's `getInitialURL` still reads the launch options
148
+ // by these exact keys, so this is the shape it expects.
149
+ var launchOptions: [UIApplication.LaunchOptionsKey: Any] = [:]
150
+ if let url {
151
+ let urlKey = UIApplication.LaunchOptionsKey(rawValue: "UIApplicationLaunchOptionsURLKey")
152
+ launchOptions[urlKey] = url
153
+ }
154
+ if let userActivity {
155
+ let userActivityDictionaryKey = UIApplication.LaunchOptionsKey(
156
+ rawValue: "UIApplicationLaunchOptionsUserActivityDictionaryKey"
157
+ )
158
+ launchOptions[userActivityDictionaryKey] = [
159
+ "UIApplicationLaunchOptionsUserActivityTypeKey": userActivity.activityType,
160
+ "UIApplicationLaunchOptionsUserActivityKey": userActivity
161
+ ]
162
+ }
163
+ return launchOptions.isEmpty ? nil : launchOptions
164
+ }
165
+
166
+ private static func openURLOptions(
167
+ from sceneOptions: UIScene.OpenURLOptions
168
+ ) -> [UIApplication.OpenURLOptionsKey: Any] {
169
+ var options: [UIApplication.OpenURLOptionsKey: Any] = [:]
170
+ if let sourceApplication = sceneOptions.sourceApplication {
171
+ options[.sourceApplication] = sourceApplication
172
+ }
173
+ if let annotation = sceneOptions.annotation {
174
+ options[.annotation] = annotation
175
+ }
176
+ options[.openInPlace] = sceneOptions.openInPlace
177
+ return options
178
+ }
179
+ }
180
+
181
+ #endif
@@ -0,0 +1,30 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import React
4
+
5
+ /**
6
+ Conformed to by the application's `AppDelegate` so that the scene delegate can retrieve
7
+ the React Native factory it created during `application(_:didFinishLaunchingWithOptions:)`.
8
+
9
+ In the scene-based life cycle the window is created by the scene delegate, but the factory
10
+ is still owned by the app delegate. This protocol is the bridge between the two.
11
+ */
12
+ @MainActor
13
+ public protocol ExpoReactNativeFactoryProvider: AnyObject {
14
+ /// The app's window. The scene delegate creates the window and assigns it here so that code
15
+ /// reading `UIApplication.shared.delegate?.window` keeps working (e.g. expo-system-ui).
16
+ /// This is the same `var window: UIWindow?` the app delegate already declares for `UIApplicationDelegate`.
17
+ var window: UIWindow? { get set }
18
+
19
+ /// The factory created in `application(_:didFinishLaunchingWithOptions:)`.
20
+ var reactNativeFactory: RCTReactNativeFactory? { get }
21
+
22
+ /// The registered React Native module name. Defaults to `"main"`.
23
+ var reactNativeFactoryModuleName: String { get }
24
+ }
25
+
26
+ public extension ExpoReactNativeFactoryProvider {
27
+ var reactNativeFactoryModuleName: String {
28
+ return "main"
29
+ }
30
+ }
@@ -0,0 +1,154 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import React
5
+
6
+ #if os(iOS) || os(tvOS)
7
+
8
+ /**
9
+ Re-feeds scene events to the app delegate. Under the scene life cycle UIKit no longer calls the
10
+ app delegate's URL, user-activity, life-cycle and quick-action methods, so overrides in the app's
11
+ `AppDelegate: ExpoAppDelegate` would never run; `ExpoAppDelegate` forwards every event to the
12
+ subscribers, so one call reaches both.
13
+
14
+ Main-actor isolated: `UIWindowSceneDelegate` hands these events over on the main actor, and every
15
+ `ExpoAppDelegate` method they reach is main-actor isolated too.
16
+ */
17
+ @MainActor
18
+ @available(iOSApplicationExtension, unavailable)
19
+ struct SceneEventForwarder {
20
+ var appDelegate: () -> ExpoAppDelegate? = { UIApplication.shared.delegate as? ExpoAppDelegate }
21
+
22
+ func open(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) {
23
+ let application = UIApplication.shared
24
+ let delegate = appDelegate()
25
+
26
+ notifyLinkingManagerUnlessAlreadyNotified(of: url) {
27
+ _ = delegate?.application(application, open: url, options: options)
28
+ } notify: {
29
+ RCTLinkingManager.application(application, open: url, options: options)
30
+ }
31
+ }
32
+
33
+ func `continue`(_ userActivity: NSUserActivity) {
34
+ let application = UIApplication.shared
35
+ let delegate = appDelegate()
36
+
37
+ // `RCTLinkingManager` only announces browsing-web activities, so there is nothing to dedupe
38
+ // against for the other activity types.
39
+ notifyLinkingManagerUnlessAlreadyNotified(of: userActivity.webpageURL) {
40
+ _ = delegate?.application(application, continue: userActivity, restorationHandler: { _ in })
41
+ } notify: {
42
+ RCTLinkingManager.application(application, continue: userActivity, restorationHandler: { _ in })
43
+ }
44
+ }
45
+
46
+ func willContinueUserActivity(withType userActivityType: String) {
47
+ // The scene callback has no return value, unlike its app-delegate counterpart. Calling the app
48
+ // delegate still lets every subscriber prepare; its aggregated result is intentionally ignored.
49
+ _ = appDelegate()?.application(
50
+ UIApplication.shared,
51
+ willContinueUserActivityWithType: userActivityType
52
+ )
53
+ }
54
+
55
+ func didFailToContinueUserActivity(withType userActivityType: String, error: Error) {
56
+ appDelegate()?.application(
57
+ UIApplication.shared,
58
+ didFailToContinueUserActivityWithType: userActivityType,
59
+ error: error
60
+ )
61
+ }
62
+
63
+ func didUpdate(_ userActivity: NSUserActivity) {
64
+ appDelegate()?.application(UIApplication.shared, didUpdate: userActivity)
65
+ }
66
+
67
+ func didBecomeActive() {
68
+ appDelegate()?.applicationDidBecomeActive(UIApplication.shared)
69
+ }
70
+
71
+ func willResignActive() {
72
+ appDelegate()?.applicationWillResignActive(UIApplication.shared)
73
+ }
74
+
75
+ func willEnterForeground() {
76
+ appDelegate()?.applicationWillEnterForeground(UIApplication.shared)
77
+ }
78
+
79
+ func didEnterBackground() {
80
+ appDelegate()?.applicationDidEnterBackground(UIApplication.shared)
81
+ }
82
+
83
+ #if os(iOS)
84
+ /// UIKit waits on `completionHandler`, so it has to run exactly once.
85
+ func perform(
86
+ _ shortcutItem: UIApplicationShortcutItem,
87
+ completionHandler: @escaping (Bool) -> Void
88
+ ) {
89
+ guard let delegate = appDelegate() else {
90
+ completionHandler(false)
91
+ return
92
+ }
93
+ delegate.application(
94
+ UIApplication.shared,
95
+ performActionFor: shortcutItem,
96
+ completionHandler: completionHandler
97
+ )
98
+ }
99
+ #endif
100
+
101
+ /// Runs `body`, then calls `notify` unless `RCTLinkingManager` was already notified about `url`
102
+ /// while `body` ran. Always calls `notify` when `url` is `nil`.
103
+ ///
104
+ /// App delegates generated by SDK 57 and older call `RCTLinkingManager` themselves, which would
105
+ /// deliver the JS `url` event twice now that their overrides run again, and no static check can
106
+ /// tell such an override apart — so listen for what `RCTLinkingManager` announces instead.
107
+ private func notifyLinkingManagerUnlessAlreadyNotified(
108
+ of url: URL?,
109
+ during body: () -> Void,
110
+ notify: () -> Void
111
+ ) {
112
+ guard let url else {
113
+ body()
114
+ notify()
115
+ return
116
+ }
117
+ let observer = LinkingManagerObserver(url: url)
118
+ body()
119
+ if !observer.wasNotified {
120
+ notify()
121
+ }
122
+ }
123
+ }
124
+
125
+ /// Observes the notification `RCTLinkingManager` posts for every link it handles. The name is
126
+ /// private to React Native's `RCTLinkingManager.mm` — expo-router spells it out the same way in
127
+ /// `ExpoHeadAppDelegateSubscriber.swift`. If React Native renames it, legacy overrides deliver a
128
+ /// duplicate `url` event again; a link is never lost.
129
+ private final class LinkingManagerObserver: NSObject {
130
+ private let expectedURL: String
131
+ private(set) var wasNotified = false
132
+
133
+ init(url: URL) {
134
+ expectedURL = url.absoluteString
135
+ super.init()
136
+ NotificationCenter.default.addObserver(
137
+ self,
138
+ selector: #selector(linkingManagerDidOpenURL(_:)),
139
+ name: Notification.Name("RCTOpenURLNotification"),
140
+ object: nil
141
+ )
142
+ }
143
+
144
+ deinit {
145
+ NotificationCenter.default.removeObserver(self)
146
+ }
147
+
148
+ @objc
149
+ private func linkingManagerDidOpenURL(_ notification: Notification) {
150
+ wasNotified = wasNotified || notification.userInfo?["url"] as? String == expectedURL
151
+ }
152
+ }
153
+
154
+ #endif
@@ -0,0 +1,602 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import React
5
+ import Testing
6
+ @testable import Expo
7
+
8
+ #if os(iOS) || os(tvOS)
9
+
10
+ @Suite(.serialized)
11
+ struct ExpoAppSceneDelegateTests {
12
+ @Test
13
+ func `extends UIResponder`() {
14
+ // Assert that `ExpoAppSceneDelegate` extends from `UIResponder` so it doesn't regress in the future.
15
+ // UIKit instantiates the scene delegate by name and expects a responder; losing this would break
16
+ // responder-chain behavior such as key presses.
17
+ #expect(ExpoAppSceneDelegate.self is UIResponder.Type)
18
+ }
19
+
20
+ @Test
21
+ func `conforms to UIWindowSceneDelegate`() {
22
+ // The iOS 27 SDK asserts at launch unless the app's scene delegate adopts the scene life cycle.
23
+ // Conforming to `UIWindowSceneDelegate` is what makes the class usable as the scene delegate.
24
+ #expect(ExpoAppSceneDelegate.self is UIWindowSceneDelegate.Type)
25
+ }
26
+
27
+ @Test
28
+ @MainActor
29
+ func `exposes scene user activity callbacks to UIKit`() {
30
+ let sceneDelegate = ExpoAppSceneDelegate()
31
+ #expect(
32
+ sceneDelegate.responds(
33
+ to: #selector(UISceneDelegate.scene(_:willContinueUserActivityWithType:))
34
+ )
35
+ )
36
+ #expect(sceneDelegate.responds(to: #selector(UISceneDelegate.scene(_:didUpdate:))))
37
+ #expect(
38
+ sceneDelegate.responds(
39
+ to: #selector(UISceneDelegate.scene(_:didFailToContinueUserActivityWithType:error:))
40
+ )
41
+ )
42
+ }
43
+
44
+ @Test
45
+ @MainActor
46
+ func `builds launch options carrying a cold-start URL`() {
47
+ // Under the scene life cycle the URL that cold-started the app arrives in the scene connection
48
+ // options, not in the app delegate's launch options. Synthesizing launch options from it is
49
+ // what lets `Linking.getInitialURL()` (which reads `UIApplicationLaunchOptionsURLKey`) return
50
+ // the URL, matching the app-delegate life cycle it replaced.
51
+ let url = URL(string: "bareexpo://test-suite/run?tests=AppMetrics")!
52
+ let launchOptions = ExpoAppSceneDelegate.launchOptions(url: url, userActivity: nil)
53
+ let urlKey = UIApplication.LaunchOptionsKey(rawValue: "UIApplicationLaunchOptionsURLKey")
54
+ #expect(launchOptions?[urlKey] as? URL == url)
55
+ }
56
+
57
+ @Test
58
+ @MainActor
59
+ func `builds launch options carrying a browsing-web user activity`() {
60
+ let userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
61
+ userActivity.webpageURL = URL(string: "https://expo.dev/link")!
62
+ let launchOptions = ExpoAppSceneDelegate.launchOptions(url: nil, userActivity: userActivity)
63
+ let userActivityDictionaryKey = UIApplication.LaunchOptionsKey(
64
+ rawValue: "UIApplicationLaunchOptionsUserActivityDictionaryKey"
65
+ )
66
+ let activityDictionary = launchOptions?[userActivityDictionaryKey] as? [AnyHashable: Any]
67
+ #expect(activityDictionary?["UIApplicationLaunchOptionsUserActivityTypeKey"] as? String == NSUserActivityTypeBrowsingWeb)
68
+ #expect((activityDictionary?["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity) === userActivity)
69
+ }
70
+
71
+ @Test
72
+ @MainActor
73
+ func `returns nil launch options without a URL or user activity`() {
74
+ #expect(ExpoAppSceneDelegate.launchOptions(url: nil, userActivity: nil) == nil)
75
+ }
76
+
77
+ @Test
78
+ @MainActor
79
+ func `an app delegate can provide the React Native factory`() {
80
+ let appDelegate = FactoryProvidingAppDelegate()
81
+ #expect(appDelegate.reactNativeFactory == nil)
82
+ #expect(appDelegate.reactNativeFactoryModuleName == "main")
83
+ }
84
+
85
+ @Test
86
+ @MainActor
87
+ func `routes an opened URL through the app delegate`() {
88
+ let spy = SpyAppDelegate()
89
+ let url = URL(string: "bareexpo://scene-delegate/open-url")!
90
+ SceneEventForwarder(appDelegate: { spy }).open(url: url, options: [.sourceApplication: "dev.expo.Payments"])
91
+ #expect(spy.openedURLs.count == 1)
92
+ #expect(spy.openedURLs.first?.url == url)
93
+ #expect(spy.openedURLs.first?.options[.sourceApplication] as? String == "dev.expo.Payments")
94
+ }
95
+
96
+ @Test
97
+ @MainActor
98
+ func `routes a continued user activity through the app delegate`() {
99
+ let spy = SpyAppDelegate()
100
+ let subscriber = UserActivityRecordingSubscriber()
101
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(subscriber)
102
+ let userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
103
+ userActivity.webpageURL = URL(string: "https://expo.dev/scene-delegate")!
104
+ SceneEventForwarder(appDelegate: { spy }).continue(userActivity)
105
+ #expect(spy.continuedUserActivities.count == 1)
106
+ #expect(spy.continuedUserActivities.first === userActivity)
107
+ #expect(subscriber.continuations.filter { $0 === userActivity }.count == 1)
108
+ }
109
+
110
+ @Test
111
+ @MainActor
112
+ func `asks every subscriber to prepare for a scene user activity`() {
113
+ let activityType = "dev.expo.scene-delegate.prepare.\(UUID().uuidString)"
114
+ let firstSubscriber = UserActivityRecordingSubscriber(willContinueResult: false)
115
+ let handlingSubscriber = UserActivityRecordingSubscriber(willContinueResult: true)
116
+ let lastSubscriber = UserActivityRecordingSubscriber(willContinueResult: false)
117
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(firstSubscriber)
118
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(handlingSubscriber)
119
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(lastSubscriber)
120
+
121
+ let spy = SpyAppDelegate()
122
+ SceneEventForwarder(appDelegate: { spy }).willContinueUserActivity(withType: activityType)
123
+
124
+ #expect(spy.preparations.count == 1)
125
+ #expect(isSharedApplication(spy.preparations.first?.application))
126
+ #expect(spy.preparations.first?.activityType == activityType)
127
+ #expect(spy.continuedUserActivities.isEmpty)
128
+ for subscriber in [firstSubscriber, handlingSubscriber, lastSubscriber] {
129
+ let preparations = subscriber.preparations.filter { $0.activityType == activityType }
130
+ #expect(preparations.count == 1)
131
+ #expect(isSharedApplication(preparations.first?.application))
132
+ #expect(subscriber.continuations.isEmpty)
133
+ }
134
+ }
135
+
136
+ @Test
137
+ @MainActor
138
+ func `forwards scene user activity updates and failures without continuing`() {
139
+ let activityType = "dev.expo.scene-delegate.failure.\(UUID().uuidString)"
140
+ let userActivity = NSUserActivity(
141
+ activityType: "dev.expo.scene-delegate.update.\(UUID().uuidString)"
142
+ )
143
+ let error = NSError(
144
+ domain: "dev.expo.scene-delegate.handoff",
145
+ code: 17,
146
+ userInfo: [NSLocalizedDescriptionKey: "Handoff transfer failed"]
147
+ )
148
+ let subscriber = UserActivityRecordingSubscriber()
149
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(subscriber)
150
+
151
+ let spy = SpyAppDelegate()
152
+ let forwarder = SceneEventForwarder(appDelegate: { spy })
153
+ forwarder.didUpdate(userActivity)
154
+ forwarder.didFailToContinueUserActivity(withType: activityType, error: error)
155
+
156
+ #expect(spy.updatedUserActivities.count == 1)
157
+ #expect(isSharedApplication(spy.updatedUserActivities.first?.application))
158
+ #expect(spy.updatedUserActivities.first?.userActivity === userActivity)
159
+ #expect(spy.userActivityFailures.count == 1)
160
+ #expect(isSharedApplication(spy.userActivityFailures.first?.application))
161
+ #expect(spy.userActivityFailures.first?.activityType == activityType)
162
+ #expect((spy.userActivityFailures.first?.error as NSError?) === error)
163
+ #expect(spy.preparations.isEmpty)
164
+ #expect(spy.continuedUserActivities.isEmpty)
165
+ #expect(subscriber.updates.count == 1)
166
+ #expect(isSharedApplication(subscriber.updates.first?.application))
167
+ #expect(subscriber.updates.first?.userActivity === userActivity)
168
+ #expect(subscriber.failures.count == 1)
169
+ #expect(isSharedApplication(subscriber.failures.first?.application))
170
+ #expect(subscriber.failures.first?.activityType == activityType)
171
+ #expect((subscriber.failures.first?.error as NSError?) === error)
172
+ #expect(subscriber.preparations.isEmpty)
173
+ #expect(subscriber.continuations.isEmpty)
174
+ }
175
+
176
+ @Test
177
+ @MainActor
178
+ func `routes life cycle events through the app delegate`() {
179
+ let spy = SpyAppDelegate()
180
+ let forwarder = SceneEventForwarder(appDelegate: { spy })
181
+ forwarder.didBecomeActive()
182
+ forwarder.willResignActive()
183
+ forwarder.willEnterForeground()
184
+ forwarder.didEnterBackground()
185
+ #expect(spy.lifeCycleEvents == [
186
+ "applicationDidBecomeActive",
187
+ "applicationWillResignActive",
188
+ "applicationWillEnterForeground",
189
+ "applicationDidEnterBackground"
190
+ ])
191
+ }
192
+
193
+ #if os(iOS)
194
+ @Test
195
+ @MainActor
196
+ func `routes a quick action through the app delegate`() async {
197
+ let spy = SpyAppDelegate()
198
+ let shortcutItem = UIApplicationShortcutItem(
199
+ type: "dev.expo.bareexpo.run-tests",
200
+ localizedTitle: "Run tests"
201
+ )
202
+ var handled: [Bool] = []
203
+ // The subscriber manager completes on the next main-queue turn when a subscriber handles the
204
+ // action, so wait for the first reply instead of assuming a synchronous one.
205
+ await withCheckedContinuation { continuation in
206
+ var didResume = false
207
+ SceneEventForwarder(appDelegate: { spy }).perform(shortcutItem) { succeeded in
208
+ handled.append(succeeded)
209
+ if !didResume {
210
+ didResume = true
211
+ continuation.resume()
212
+ }
213
+ }
214
+ }
215
+ await drainMainQueue()
216
+ #expect(spy.shortcutItemTypes == [shortcutItem.type])
217
+ #expect(handled == [false])
218
+ }
219
+ #endif
220
+
221
+ @Test
222
+ @MainActor
223
+ func `notifies RCTLinkingManager once per routed URL`() {
224
+ let url = URL(string: "bareexpo://scene-delegate/linking")!
225
+ let recorder = OpenURLNotificationRecorder()
226
+ let spy = SpyAppDelegate()
227
+ SceneEventForwarder(appDelegate: { spy }).open(url: url, options: [:])
228
+ #expect(recorder.count(of: url) == 1)
229
+ }
230
+
231
+ @Test
232
+ @MainActor
233
+ func `notifies RCTLinkingManager for a universal link without a legacy linking override`() {
234
+ let delegate = SpyAppDelegate()
235
+ let url = URL(string: "https://expo.dev/scene-delegate/fallback-activity")!
236
+ let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
237
+ activity.webpageURL = url
238
+ let recorder = OpenURLNotificationRecorder()
239
+
240
+ SceneEventForwarder(appDelegate: { delegate }).continue(activity)
241
+
242
+ #expect(delegate.continuedUserActivities.first === activity)
243
+ #expect(recorder.count(of: url) == 1)
244
+ }
245
+
246
+ @Test
247
+ @MainActor
248
+ func `delivers repeated opens of the same URL once for each invocation`() {
249
+ let delegate = LegacyLinkingAppDelegate()
250
+ let url = URL(string: "bareexpo://scene-delegate/repeated-url")!
251
+ let recorder = OpenURLNotificationRecorder()
252
+ let forwarder = SceneEventForwarder(appDelegate: { delegate })
253
+
254
+ forwarder.open(url: url, options: [:])
255
+ forwarder.open(url: url, options: [:])
256
+
257
+ #expect(delegate.openedURLs == [url, url])
258
+ #expect(recorder.count(of: url) == 2)
259
+ }
260
+
261
+ @Test
262
+ @MainActor
263
+ func `notifies RCTLinkingManager once when the delegate notifies it too`() {
264
+ let delegate = LegacyLinkingAppDelegate()
265
+ let url = URL(string: "bareexpo://scene-delegate/legacy-open-url")!
266
+ let recorder = OpenURLNotificationRecorder()
267
+ SceneEventForwarder(appDelegate: { delegate }).open(url: url, options: [:])
268
+ #expect(delegate.openedURLs == [url])
269
+ #expect(recorder.count(of: url) == 1)
270
+ }
271
+
272
+ @Test
273
+ @MainActor
274
+ func `notifies RCTLinkingManager once per user activity when the delegate notifies it too`() {
275
+ let delegate = LegacyLinkingAppDelegate()
276
+ let webpageURL = URL(string: "https://expo.dev/scene-delegate/legacy-activity")!
277
+ let userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
278
+ userActivity.webpageURL = webpageURL
279
+ let recorder = OpenURLNotificationRecorder()
280
+ SceneEventForwarder(appDelegate: { delegate }).continue(userActivity)
281
+ #expect(delegate.continuedUserActivities.count == 1)
282
+ #expect(recorder.count(of: webpageURL) == 1)
283
+ }
284
+
285
+ @Test
286
+ @MainActor
287
+ func `does not notify subscribers without an ExpoAppDelegate`() {
288
+ let subscriber = URLRecordingSubscriber()
289
+ ExpoAppDelegateSubscriberRepository.registerSubscriber(subscriber)
290
+
291
+ let url = URL(string: "bareexpo://scene-delegate/no-app-delegate")!
292
+ let recorder = OpenURLNotificationRecorder()
293
+ SceneEventForwarder(appDelegate: { nil }).open(url: url, options: [:])
294
+ #expect(subscriber.count(of: url) == 0)
295
+ #expect(recorder.count(of: url) == 1)
296
+ }
297
+
298
+ #if os(iOS)
299
+ @Test
300
+ @MainActor
301
+ func `completes a quick action with false without an ExpoAppDelegate`() async {
302
+ let shortcutItem = UIApplicationShortcutItem(
303
+ type: "dev.expo.bareexpo.unhandled-action",
304
+ localizedTitle: "Unhandled action"
305
+ )
306
+ var handled: [Bool] = []
307
+ await withCheckedContinuation { continuation in
308
+ var didResume = false
309
+ SceneEventForwarder(appDelegate: { nil }).perform(shortcutItem) { succeeded in
310
+ handled.append(succeeded)
311
+ if !didResume {
312
+ didResume = true
313
+ continuation.resume()
314
+ }
315
+ }
316
+ }
317
+ await drainMainQueue()
318
+ #expect(handled == [false])
319
+ }
320
+ #endif
321
+
322
+ @Test
323
+ @MainActor
324
+ func `forwards to the app delegate of the running application`() {
325
+ let forwarder = ExpoAppSceneDelegate().forwarder
326
+ #expect(forwarder.appDelegate() === UIApplication.shared.delegate as? ExpoAppDelegate)
327
+ }
328
+ }
329
+
330
+ @MainActor
331
+ private func isSharedApplication(_ application: UIApplication?) -> Bool {
332
+ // A hostless XCTest runner can bridge `UIApplication.shared` as nil despite its nonoptional
333
+ // Swift declaration. Evaluate identity outside `#expect` so the macro does not capture it.
334
+ let sharedApplication: UIApplication? = UIApplication.shared
335
+ return application === sharedApplication
336
+ }
337
+
338
+ /// Records user-activity callbacks delivered through the app-delegate subscriber compatibility
339
+ /// layer. Subscribers cannot be unregistered, so tests use unique activity types and retain these
340
+ /// instances for the remainder of the test process.
341
+ private final class UserActivityRecordingSubscriber: NSObject, ExpoAppDelegateSubscriberProtocol {
342
+ struct Preparation {
343
+ let application: UIApplication?
344
+ let activityType: String
345
+ }
346
+
347
+ struct ActivityUpdate {
348
+ let application: UIApplication?
349
+ let userActivity: NSUserActivity
350
+ }
351
+
352
+ struct Failure {
353
+ let application: UIApplication?
354
+ let activityType: String
355
+ let error: Error
356
+ }
357
+
358
+ let willContinueResult: Bool
359
+ var preparations: [Preparation] = []
360
+ var continuations: [NSUserActivity] = []
361
+ var updates: [ActivityUpdate] = []
362
+ var failures: [Failure] = []
363
+
364
+ init(willContinueResult: Bool = false) {
365
+ self.willContinueResult = willContinueResult
366
+ }
367
+
368
+ func application(
369
+ _ application: UIApplication,
370
+ willContinueUserActivityWithType userActivityType: String
371
+ ) -> Bool {
372
+ preparations.append(Preparation(application: application, activityType: userActivityType))
373
+ return willContinueResult
374
+ }
375
+
376
+ func application(
377
+ _ application: UIApplication,
378
+ continue userActivity: NSUserActivity,
379
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
380
+ ) -> Bool {
381
+ continuations.append(userActivity)
382
+ restorationHandler(nil)
383
+ return false
384
+ }
385
+
386
+ func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) {
387
+ updates.append(ActivityUpdate(application: application, userActivity: userActivity))
388
+ }
389
+
390
+ func application(
391
+ _ application: UIApplication,
392
+ didFailToContinueUserActivityWithType userActivityType: String,
393
+ error: Error
394
+ ) {
395
+ failures.append(Failure(application: application, activityType: userActivityType, error: error))
396
+ }
397
+ }
398
+
399
+ /// App delegate shaped like the SDK ≤ 57 templates, which call `RCTLinkingManager` from their own
400
+ /// overrides. See `packages/expo-updates/e2e/fixtures/custom_init/AppDelegate.swift`.
401
+ private final class LegacyLinkingAppDelegate: ExpoAppDelegate {
402
+ var openedURLs: [URL] = []
403
+ var continuedUserActivities: [NSUserActivity] = []
404
+
405
+ override func application(
406
+ _ app: UIApplication,
407
+ open url: URL,
408
+ options: [UIApplication.OpenURLOptionsKey: Any]
409
+ ) -> Bool {
410
+ openedURLs.append(url)
411
+ let handled = super.application(app, open: url, options: options)
412
+ RCTLinkingManager.application(app, open: url, options: options)
413
+ return handled
414
+ }
415
+
416
+ override func application(
417
+ _ application: UIApplication,
418
+ continue userActivity: NSUserActivity,
419
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
420
+ ) -> Bool {
421
+ continuedUserActivities.append(userActivity)
422
+ let handled = super.application(application, continue: userActivity, restorationHandler: restorationHandler)
423
+ RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
424
+ return handled
425
+ }
426
+ }
427
+
428
+ /// Lets the main-queue blocks scheduled so far run, so a late duplicate reply is recorded before
429
+ /// the assertions.
430
+ @MainActor
431
+ private func drainMainQueue() async {
432
+ await withCheckedContinuation { continuation in
433
+ DispatchQueue.main.async { continuation.resume() }
434
+ }
435
+ }
436
+
437
+ /// Records the URLs a module subscriber receives. Registration is permanent for the process, so it
438
+ /// reports not having handled the URL to leave the other delegates' behavior untouched.
439
+ private final class URLRecordingSubscriber: NSObject, ExpoAppDelegateSubscriberProtocol {
440
+ private var urls: [URL] = []
441
+
442
+ func application(
443
+ _ app: UIApplication,
444
+ open url: URL,
445
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
446
+ ) -> Bool {
447
+ urls.append(url)
448
+ return false
449
+ }
450
+
451
+ func count(of url: URL) -> Int {
452
+ return urls.filter { $0 == url }.count
453
+ }
454
+ }
455
+
456
+ private final class FactoryProvidingAppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider {
457
+ var window: UIWindow?
458
+ var reactNativeFactory: RCTReactNativeFactory?
459
+ }
460
+
461
+ /// Records the events an `ExpoAppDelegate` subclass receives, then forwards them to `super` the way
462
+ /// an app's own `AppDelegate` override would.
463
+ private final class SpyAppDelegate: ExpoAppDelegate {
464
+ struct OpenedURL {
465
+ let url: URL
466
+ let options: [UIApplication.OpenURLOptionsKey: Any]
467
+ }
468
+
469
+ struct Preparation {
470
+ let application: UIApplication?
471
+ let activityType: String
472
+ }
473
+
474
+ struct ActivityUpdate {
475
+ let application: UIApplication?
476
+ let userActivity: NSUserActivity
477
+ }
478
+
479
+ struct UserActivityFailure {
480
+ let application: UIApplication?
481
+ let activityType: String
482
+ let error: Error
483
+ }
484
+
485
+ var openedURLs: [OpenedURL] = []
486
+ var continuedUserActivities: [NSUserActivity] = []
487
+ var preparations: [Preparation] = []
488
+ var updatedUserActivities: [ActivityUpdate] = []
489
+ var userActivityFailures: [UserActivityFailure] = []
490
+ var lifeCycleEvents: [String] = []
491
+ var shortcutItemTypes: [String] = []
492
+
493
+ override func application(
494
+ _ app: UIApplication,
495
+ open url: URL,
496
+ options: [UIApplication.OpenURLOptionsKey: Any]
497
+ ) -> Bool {
498
+ openedURLs.append(OpenedURL(url: url, options: options))
499
+ return super.application(app, open: url, options: options)
500
+ }
501
+
502
+ override func application(
503
+ _ application: UIApplication,
504
+ willContinueUserActivityWithType userActivityType: String
505
+ ) -> Bool {
506
+ preparations.append(Preparation(application: application, activityType: userActivityType))
507
+ return super.application(application, willContinueUserActivityWithType: userActivityType)
508
+ }
509
+
510
+ override func application(
511
+ _ application: UIApplication,
512
+ continue userActivity: NSUserActivity,
513
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
514
+ ) -> Bool {
515
+ continuedUserActivities.append(userActivity)
516
+ return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
517
+ }
518
+
519
+ override func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) {
520
+ updatedUserActivities.append(ActivityUpdate(application: application, userActivity: userActivity))
521
+ super.application(application, didUpdate: userActivity)
522
+ }
523
+
524
+ override func application(
525
+ _ application: UIApplication,
526
+ didFailToContinueUserActivityWithType userActivityType: String,
527
+ error: Error
528
+ ) {
529
+ userActivityFailures.append(
530
+ UserActivityFailure(application: application, activityType: userActivityType, error: error)
531
+ )
532
+ super.application(
533
+ application,
534
+ didFailToContinueUserActivityWithType: userActivityType,
535
+ error: error
536
+ )
537
+ }
538
+
539
+ override func applicationDidBecomeActive(_ application: UIApplication) {
540
+ lifeCycleEvents.append("applicationDidBecomeActive")
541
+ super.applicationDidBecomeActive(application)
542
+ }
543
+
544
+ override func applicationWillResignActive(_ application: UIApplication) {
545
+ lifeCycleEvents.append("applicationWillResignActive")
546
+ super.applicationWillResignActive(application)
547
+ }
548
+
549
+ override func applicationWillEnterForeground(_ application: UIApplication) {
550
+ lifeCycleEvents.append("applicationWillEnterForeground")
551
+ super.applicationWillEnterForeground(application)
552
+ }
553
+
554
+ override func applicationDidEnterBackground(_ application: UIApplication) {
555
+ lifeCycleEvents.append("applicationDidEnterBackground")
556
+ super.applicationDidEnterBackground(application)
557
+ }
558
+
559
+ #if os(iOS)
560
+ override func application(
561
+ _ application: UIApplication,
562
+ performActionFor shortcutItem: UIApplicationShortcutItem,
563
+ completionHandler: @escaping (Bool) -> Void
564
+ ) {
565
+ shortcutItemTypes.append(shortcutItem.type)
566
+ super.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
567
+ }
568
+ #endif
569
+ }
570
+
571
+ /// Counts the notifications `RCTLinkingManager` posts for handled URLs. React Native keeps the
572
+ /// notification name private to its implementation file, so it's spelled out here.
573
+ private final class OpenURLNotificationRecorder: NSObject {
574
+ private var urls: [String] = []
575
+
576
+ override init() {
577
+ super.init()
578
+ NotificationCenter.default.addObserver(
579
+ self,
580
+ selector: #selector(record(_:)),
581
+ name: Notification.Name("RCTOpenURLNotification"),
582
+ object: nil
583
+ )
584
+ }
585
+
586
+ deinit {
587
+ NotificationCenter.default.removeObserver(self)
588
+ }
589
+
590
+ func count(of url: URL) -> Int {
591
+ return urls.filter { $0 == url.absoluteString }.count
592
+ }
593
+
594
+ @objc
595
+ private func record(_ notification: Notification) {
596
+ if let url = notification.userInfo?["url"] as? String {
597
+ urls.append(url)
598
+ }
599
+ }
600
+ }
601
+
602
+ #endif
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo",
3
- "version": "57.0.21",
3
+ "version": "57.0.23",
4
4
  "description": "The Expo SDK",
5
5
  "main": "src/Expo.ts",
6
6
  "module": "src/Expo.ts",
@@ -68,25 +68,25 @@
68
68
  "homepage": "https://github.com/expo/expo/tree/main/packages/expo",
69
69
  "dependencies": {
70
70
  "@babel/runtime": "^7.20.0",
71
- "@expo/cli": "^57.0.23",
71
+ "@expo/cli": "^57.0.25",
72
72
  "@expo/config": "~57.0.9",
73
73
  "@expo/config-plugins": "~57.0.9",
74
74
  "@expo/devtools": "~57.0.1",
75
75
  "@expo/dom-webview": "~57.0.1",
76
- "@expo/fingerprint": "^0.20.12",
76
+ "@expo/fingerprint": "^0.20.13",
77
77
  "@expo/local-build-cache-provider": "^57.0.8",
78
78
  "@expo/log-box": "^57.0.4",
79
79
  "@expo/metro": "~56.0.2",
80
80
  "@expo/metro-config": "~57.0.12",
81
81
  "@ungap/structured-clone": "^1.3.0",
82
- "babel-preset-expo": "~57.0.11",
83
- "expo-asset": "~57.0.16",
84
- "expo-constants": "~57.0.17",
85
- "expo-file-system": "~57.0.6",
86
- "expo-font": "~57.0.3",
87
- "expo-keep-awake": "~57.0.1",
88
- "expo-modules-autolinking": "~57.0.12",
89
- "expo-modules-core": "~57.0.17",
82
+ "babel-preset-expo": "~57.0.12",
83
+ "expo-asset": "~57.0.17",
84
+ "expo-constants": "~57.0.18",
85
+ "expo-file-system": "~57.0.7",
86
+ "expo-font": "~57.0.4",
87
+ "expo-keep-awake": "~57.0.2",
88
+ "expo-modules-autolinking": "~57.0.13",
89
+ "expo-modules-core": "~57.0.18",
90
90
  "pretty-format": "^29.7.0",
91
91
  "react-refresh": "^0.14.2",
92
92
  "whatwg-url-minimum": "^0.1.2"
@@ -102,7 +102,7 @@
102
102
  "web-streams-polyfill": "^3.3.2",
103
103
  "@expo/dom-webview": "57.0.1",
104
104
  "@expo/metro-runtime": "57.0.15",
105
- "expo-updates": "57.0.21"
105
+ "expo-updates": "57.0.22"
106
106
  },
107
107
  "peerDependencies": {
108
108
  "@expo/dom-webview": "*",
@@ -139,7 +139,7 @@
139
139
  "scripts/resolveAppEntry.js"
140
140
  ]
141
141
  },
142
- "gitHead": "ee89f7c31877f111cb1705c8e5c2c8078fa80a03",
142
+ "gitHead": "f6f5af06b95cb7d511b52641885b2fb5adad525d",
143
143
  "scripts": {
144
144
  "build": "tsc",
145
145
  "clean": "rimraf build",
package/template.tgz CHANGED
Binary file