expo 58.0.0-preview.0 → 58.0.0-preview.2

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 = '58.0.0-preview.0'
13
+ version = '58.0.0-preview.2'
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 "58.0.0-preview.0"
24
+ versionName "58.0.0-preview.2"
25
25
  consumerProguardFiles("proguard-rules.pro")
26
26
  }
27
27
  testOptions {
@@ -0,0 +1,15 @@
1
+ package expo.modules
2
+
3
+ import expo.modules.v2.ExpoModulesV2Provider
4
+ import io.github.expo.modules.v2.Module
5
+ import io.github.expo.modules.v2.discoveredExpoModules
6
+
7
+ /**
8
+ * The list is filled in at compile time: the Expo Modules v2 compiler plugin, which autolinking
9
+ * applies to this project, expands `discoveredExpoModules()` into every `@ExpoModule` on the compile
10
+ * classpath. Autolinking adds every module project as a dependency of this one, so the classpath is
11
+ * exactly the set of installed modules and nothing has to be listed anywhere.
12
+ */
13
+ class ExpoModulesV2ModuleList : ExpoModulesV2Provider {
14
+ override fun getModules(): List<Class<out Module>> = discoveredExpoModules()
15
+ }
@@ -0,0 +1,19 @@
1
+ package expo.modules
2
+
3
+ import android.app.Activity
4
+ import android.content.pm.PackageManager
5
+ import android.os.Build
6
+ import com.facebook.react.common.build.ReactBuildConfig
7
+
8
+ // Android 17 blocks dev server traffic until the app holds the permission React Native's debug manifest declares.
9
+ internal object LocalNetworkPermission {
10
+ const val PERMISSION = "android.permission.ACCESS_LOCAL_NETWORK"
11
+ const val REQUEST_CODE = 0x4c4e
12
+
13
+ private const val FIRST_ENFORCED_SDK = 37
14
+
15
+ fun shouldRequest(activity: Activity): Boolean =
16
+ ReactBuildConfig.DEBUG &&
17
+ Build.VERSION.SDK_INT >= FIRST_ENFORCED_SDK &&
18
+ activity.checkSelfPermission(PERMISSION) != PackageManager.PERMISSION_GRANTED
19
+ }
@@ -30,13 +30,13 @@ import kotlinx.coroutines.CoroutineScope
30
30
  import kotlinx.coroutines.CoroutineStart
31
31
  import kotlinx.coroutines.Dispatchers
32
32
  import kotlinx.coroutines.launch
33
+ import kotlinx.coroutines.suspendCancellableCoroutine
33
34
  import kotlinx.coroutines.sync.Mutex
34
35
  import kotlinx.coroutines.sync.withLock
35
36
  import java.lang.reflect.Field
36
37
  import java.lang.reflect.Method
37
38
  import java.lang.reflect.Modifier
38
39
  import kotlin.coroutines.resume
39
- import kotlin.coroutines.suspendCoroutine
40
40
 
41
41
  class ReactActivityDelegateWrapper(
42
42
  private val activity: ReactActivity,
@@ -64,6 +64,7 @@ class ReactActivityDelegateWrapper(
64
64
  * A deferred that indicates when the app loading is ready
65
65
  */
66
66
  private val loadAppReady = CompletableDeferred<Unit>()
67
+ private var localNetworkPermissionResult: CompletableDeferred<Unit>? = null
67
68
 
68
69
  /**
69
70
  * A mutex to ensure all coroutines in a scope are running in atomic way.
@@ -161,6 +162,7 @@ class ReactActivityDelegateWrapper(
161
162
  mReactDelegate.isAccessible = true
162
163
  mReactDelegate.set(delegate, reactDelegate)
163
164
  if (mainComponentName != null) {
165
+ awaitLocalNetworkPermission()
164
166
  loadAppImpl(mainComponentName, supportsDelayLoad = false)
165
167
  }
166
168
  }
@@ -329,6 +331,10 @@ class ReactActivityDelegateWrapper(
329
331
  }
330
332
 
331
333
  override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
334
+ if (requestCode == LocalNetworkPermission.REQUEST_CODE) {
335
+ localNetworkPermissionResult?.complete(Unit)
336
+ return
337
+ }
332
338
  launchLifecycleScopeWithLock {
333
339
  loadAppReady.await()
334
340
  delegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
@@ -431,11 +437,22 @@ class ReactActivityDelegateWrapper(
431
437
  }
432
438
  }
433
439
 
440
+ private suspend fun awaitLocalNetworkPermission() {
441
+ if (!LocalNetworkPermission.shouldRequest(activity)) {
442
+ return
443
+ }
444
+ val result = CompletableDeferred<Unit>()
445
+ localNetworkPermissionResult = result
446
+ activity.requestPermissions(arrayOf(LocalNetworkPermission.PERMISSION), LocalNetworkPermission.REQUEST_CODE)
447
+ result.await()
448
+ localNetworkPermissionResult = null
449
+ }
450
+
434
451
  private suspend fun awaitDelayLoadAppWhenReady(delayLoadAppHandler: DelayLoadAppHandler?) {
435
452
  if (delayLoadAppHandler == null) {
436
453
  return
437
454
  }
438
- suspendCoroutine { continuation ->
455
+ suspendCancellableCoroutine { continuation ->
439
456
  delayLoadAppHandler.whenReady {
440
457
  Utils.assertMainThread()
441
458
  continuation.resume(Unit)
@@ -0,0 +1,99 @@
1
+ package expo.modules
2
+
3
+ import android.content.pm.PackageManager
4
+ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
5
+ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
6
+ import com.facebook.soloader.SoLoader
7
+ import com.google.common.truth.Truth.assertThat
8
+ import io.mockk.every
9
+ import io.mockk.mockkObject
10
+ import io.mockk.mockkStatic
11
+ import io.mockk.unmockkAll
12
+ import io.mockk.verify
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
15
+ import kotlinx.coroutines.test.UnconfinedTestDispatcher
16
+ import kotlinx.coroutines.test.setMain
17
+ import org.junit.After
18
+ import org.junit.Before
19
+ import org.junit.Test
20
+ import org.junit.runner.RunWith
21
+ import org.robolectric.Robolectric
22
+ import org.robolectric.RobolectricTestRunner
23
+ import org.robolectric.Shadows.shadowOf
24
+ import org.robolectric.android.controller.ActivityController
25
+ import org.robolectric.annotation.Config
26
+
27
+ @RunWith(RobolectricTestRunner::class)
28
+ @Config(application = MockApplication::class)
29
+ internal class ReactActivityDelegateWrapperLocalNetworkPermissionTest {
30
+ private lateinit var activityController: ActivityController<MockActivity>
31
+ private val activity: MockActivity
32
+ get() = activityController.get()
33
+ private val wrapper: ReactActivityDelegateWrapper
34
+ get() = activity.reactActivityDelegate as ReactActivityDelegateWrapper
35
+
36
+ @OptIn(ExperimentalCoroutinesApi::class)
37
+ @Before
38
+ fun setUp() {
39
+ SoLoader.setInTestMode()
40
+ mockkObject(ExpoModulesPackage.Companion)
41
+ every { ExpoModulesPackage.Companion.packageList } returns listOf(MockPackageWithoutDelayHandler())
42
+ ReactNativeFeatureFlagsForTests.setUp()
43
+ mockkStatic(ReactNativeFeatureFlags::class)
44
+ every { ReactNativeFeatureFlags.enableBridgelessArchitecture() } returns true
45
+ Dispatchers.setMain(UnconfinedTestDispatcher())
46
+ mockkObject(LocalNetworkPermission)
47
+ }
48
+
49
+ @After
50
+ fun tearDown() {
51
+ unmockkAll()
52
+ }
53
+
54
+ private fun launchActivity() {
55
+ activityController = Robolectric.buildActivity(MockActivity::class.java)
56
+ .also { (it.get().application as MockApplication).bindCurrentActivity(it.get()) }
57
+ .setup()
58
+ }
59
+
60
+ private fun verifyLoadApp(times: Int) {
61
+ verify(exactly = times) { wrapper.invokeDelegateMethod("loadApp", arrayOf(String::class.java), arrayOf("main")) }
62
+ }
63
+
64
+ @Test
65
+ fun `requests the local network permission before loading the app`() {
66
+ every { LocalNetworkPermission.shouldRequest(any()) } returns true
67
+
68
+ launchActivity()
69
+
70
+ verifyLoadApp(0)
71
+ val request = shadowOf(activity).lastRequestedPermission
72
+ assertThat(request.requestedPermissions.toList()).containsExactly(LocalNetworkPermission.PERMISSION)
73
+
74
+ activity.onRequestPermissionsResult(request.requestCode, request.requestedPermissions, intArrayOf(PackageManager.PERMISSION_GRANTED))
75
+
76
+ verifyLoadApp(1)
77
+ }
78
+
79
+ @Test
80
+ fun `loads the app when the local network permission is denied`() {
81
+ every { LocalNetworkPermission.shouldRequest(any()) } returns true
82
+
83
+ launchActivity()
84
+ val request = shadowOf(activity).lastRequestedPermission
85
+ activity.onRequestPermissionsResult(request.requestCode, request.requestedPermissions, intArrayOf(PackageManager.PERMISSION_DENIED))
86
+
87
+ verifyLoadApp(1)
88
+ }
89
+
90
+ @Test
91
+ fun `loads the app immediately when the permission is not required`() {
92
+ every { LocalNetworkPermission.shouldRequest(any()) } returns false
93
+
94
+ launchActivity()
95
+
96
+ assertThat(shadowOf(activity).lastRequestedPermission).isNull()
97
+ verifyLoadApp(1)
98
+ }
99
+ }
package/build/Expo.d.ts CHANGED
@@ -3,7 +3,7 @@ export { disableErrorHandling } from './errors/ExpoErrorManager';
3
3
  export { default as registerRootComponent } from './launch/registerRootComponent';
4
4
  export { isRunningInExpoGo, getExpoGoProjectConfig } from './environment/ExpoGo';
5
5
  export { EventEmitter, SharedObject, SharedRef, NativeModule, UnavailabilityError, CodedError, requireNativeModule, requireOptionalNativeModule, requireNativeViewManager as requireNativeView, registerWebModule, reloadAppAsync, installOnUIRuntime, Platform, uuid, createSnapshotFriendlyRef, useReleasingSharedObject, useReleasingSharedObjectWithLifecycle, } from 'expo-modules-core';
6
- export type { EventSubscription, TypedArray, IntBasedTypedArray, UintBasedTypedArray, FloatBasedTypedArray, } from 'expo-modules-core';
6
+ export type { EventSubscription, TypedArray, IntBasedTypedArray, UintBasedTypedArray, FloatBasedTypedArray, ReleasingSharedObjectLifecycle, ReleasingSharedObjectLifecycleContext, } from 'expo-modules-core';
7
7
  export type {
8
8
  /** @deprecated Move to `SharedRef` with a type-only import instead */
9
9
  SharedRef as SharedRefType,
package/build/Expo.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["Expo.ts"],"sourcesContent":[null],"names":["disableErrorHandling","default","registerRootComponent","isRunningInExpoGo","getExpoGoProjectConfig","EventEmitter","SharedObject","SharedRef","NativeModule","UnavailabilityError","CodedError","requireNativeModule","requireOptionalNativeModule","requireNativeViewManager","requireNativeView","registerWebModule","reloadAppAsync","installOnUIRuntime","Platform","uuid","createSnapshotFriendlyRef","useReleasingSharedObject","useReleasingSharedObjectWithLifecycle","PermissionStatus","createPermissionHook","useEvent","useEventListener"],"mappings":"AAAA,OAAO,YAAY;AAEnB,SAASA,oBAAoB,QAAQ,4BAA4B;AACjE,SAASC,WAAWC,qBAAqB,QAAQ,iCAAiC;AAElF,SAASC,iBAAiB,EAAEC,sBAAsB,QAAQ,uBAAuB;AAEjF,SACE,eAAe;AACfC,YAAY,EACZC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,mBAAmB,EACnBC,UAAU,EAEV,UAAU;AACVC,mBAAmB,EACnBC,2BAA2B,EAC3BC,4BAA4BC,iBAAiB,EAC7CC,iBAAiB,EACjBC,cAAc,EAEd,WAAW;AACXC,kBAAkB,EAElB,QAAQ;AACRC,QAAQ,EACRC,IAAI,EACJC,yBAAyB,EACzBC,wBAAwB,EACxBC,qCAAqC,QAChC,oBAAoB;AAqB3B,SACEC,gBAAgB,QAIX,oBAAoB;AAE3B,SAASC,oBAAoB,QAAQ,oBAAoB;AAEzD,SAASC,QAAQ,EAAEC,gBAAgB,QAAQ,mBAAmB"}
1
+ {"version":3,"sources":["Expo.ts"],"sourcesContent":[null],"names":["disableErrorHandling","default","registerRootComponent","isRunningInExpoGo","getExpoGoProjectConfig","EventEmitter","SharedObject","SharedRef","NativeModule","UnavailabilityError","CodedError","requireNativeModule","requireOptionalNativeModule","requireNativeViewManager","requireNativeView","registerWebModule","reloadAppAsync","installOnUIRuntime","Platform","uuid","createSnapshotFriendlyRef","useReleasingSharedObject","useReleasingSharedObjectWithLifecycle","PermissionStatus","createPermissionHook","useEvent","useEventListener"],"mappings":"AAAA,OAAO,YAAY;AAEnB,SAASA,oBAAoB,QAAQ,4BAA4B;AACjE,SAASC,WAAWC,qBAAqB,QAAQ,iCAAiC;AAElF,SAASC,iBAAiB,EAAEC,sBAAsB,QAAQ,uBAAuB;AAEjF,SACE,eAAe;AACfC,YAAY,EACZC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,mBAAmB,EACnBC,UAAU,EAEV,UAAU;AACVC,mBAAmB,EACnBC,2BAA2B,EAC3BC,4BAA4BC,iBAAiB,EAC7CC,iBAAiB,EACjBC,cAAc,EAEd,WAAW;AACXC,kBAAkB,EAElB,QAAQ;AACRC,QAAQ,EACRC,IAAI,EACJC,yBAAyB,EACzBC,wBAAwB,EACxBC,qCAAqC,QAChC,oBAAoB;AAuB3B,SACEC,gBAAgB,QAIX,oBAAoB;AAE3B,SAASC,oBAAoB,QAAQ,oBAAoB;AAEzD,SAASC,QAAQ,EAAEC,gBAAgB,QAAQ,mBAAmB"}
@@ -1,2 +1,2 @@
1
- import 'react-native/Libraries/Core/InitializeCore';
1
+ import 'react-native/setup-env';
2
2
  import '../../types';
@@ -2,7 +2,7 @@
2
2
  // https://wintercg.org/
3
3
  // WARN(@kitten): We must ensure that the core react-native globals are initialized before ours
4
4
  // Otherwise we're relying on `getModulesRunBeforeMainModule` which is unstable or can be missing
5
- import 'react-native/Libraries/Core/InitializeCore';
5
+ import 'react-native/setup-env';
6
6
  import '../../types';
7
7
  import { installAbortSignalPatch } from './AbortSignal';
8
8
  import { installFormDataPatch } from './FormData';
@@ -1 +1 @@
1
- {"version":3,"sources":["winter/runtime.native.ts"],"sourcesContent":[null],"names":["installAbortSignalPatch","installFormDataPatch","installGlobal","install","require","TextDecoder","TextDecoderStream","TextEncoderStream","URL","URLSearchParams","DOMException","ImportMetaRegistry","default","FormData","AbortSignal","Symbol","asyncIterator","for","useRnFetch","process","env","EXPO_PUBLIC_USE_RN_FETCH","globalThis","Headers","Error","fetch"],"mappings":"AAAA,wFAAwF;AACxF,wBAAwB;AAExB,+FAA+F;AAC/F,iGAAiG;AACjG,OAAO,6CAA6C;AACpD,OAAO,cAAc;AACrB,SAASA,uBAAuB,QAAQ,gBAAgB;AACxD,SAASC,oBAAoB,QAAQ,aAAa;AAClD,SAASC,iBAAiBC,OAAO,QAAQ,kBAAkB;AAE3D,gDAAgD;AAChDA,QAAQ,eAAe,IAAMC,QAAQ,iBAAiBC,WAAW;AACjE,gEAAgE;AAChEF,QAAQ,qBAAqB,IAAMC,QAAQ,uBAAuBE,iBAAiB;AACnF,gEAAgE;AAChEH,QAAQ,qBAAqB,IAAMC,QAAQ,uBAAuBG,iBAAiB;AACnF,mCAAmC;AACnCJ,QAAQ,OAAO,IAAMC,QAAQ,SAASI,GAAG;AACzC,+CAA+C;AAC/CL,QAAQ,mBAAmB,IAAMC,QAAQ,SAASK,eAAe;AACjE,mDAAmD;AACnDN,QAAQ,gBAAgB,IAAMC,QAAQ,kBAAkBM,YAAY;AACpE,sCAAsC;AACtC,kDAAkD;AAElDP,QAAQ,4BAA4B,IAAMC,QAAQ,wBAAwBO,kBAAkB;AAE5F,8EAA8E;AAC9ER,QAAQ,mBAAmB,IAAMC,QAAQ,2BAA2BQ,OAAO;AAE3EX,qBAAqBY;AACrBb,wBAAwBc;AAExB,6CAA6C;AAC7C,+EAA+E;AAC/EC,OAAOC,aAAa,KAAKD,OAAOE,GAAG,CAAC;AAEpC,MAAMC,aACJC,QAAQC,GAAG,CAACC,wBAAwB,KAAK,OAAOF,QAAQC,GAAG,CAACC,wBAAwB,KAAK;AAE3F,IAAI,CAACH,YAAY;IACf,sEAAsE;IACtE,sEAAsE;IACtE,IAAI,CAACI,WAAWC,OAAO,EAAE;QACvB,MAAM,IAAIC,MACR;IAEJ;IACArB,QAAQ,SAAS,IAAMC,QAAQ,WAAWqB,KAAK;AACjD"}
1
+ {"version":3,"sources":["winter/runtime.native.ts"],"sourcesContent":[null],"names":["installAbortSignalPatch","installFormDataPatch","installGlobal","install","require","TextDecoder","TextDecoderStream","TextEncoderStream","URL","URLSearchParams","DOMException","ImportMetaRegistry","default","FormData","AbortSignal","Symbol","asyncIterator","for","useRnFetch","process","env","EXPO_PUBLIC_USE_RN_FETCH","globalThis","Headers","Error","fetch"],"mappings":"AAAA,wFAAwF;AACxF,wBAAwB;AAExB,+FAA+F;AAC/F,iGAAiG;AACjG,OAAO,yBAAyB;AAChC,OAAO,cAAc;AACrB,SAASA,uBAAuB,QAAQ,gBAAgB;AACxD,SAASC,oBAAoB,QAAQ,aAAa;AAClD,SAASC,iBAAiBC,OAAO,QAAQ,kBAAkB;AAE3D,gDAAgD;AAChDA,QAAQ,eAAe,IAAMC,QAAQ,iBAAiBC,WAAW;AACjE,gEAAgE;AAChEF,QAAQ,qBAAqB,IAAMC,QAAQ,uBAAuBE,iBAAiB;AACnF,gEAAgE;AAChEH,QAAQ,qBAAqB,IAAMC,QAAQ,uBAAuBG,iBAAiB;AACnF,mCAAmC;AACnCJ,QAAQ,OAAO,IAAMC,QAAQ,SAASI,GAAG;AACzC,+CAA+C;AAC/CL,QAAQ,mBAAmB,IAAMC,QAAQ,SAASK,eAAe;AACjE,mDAAmD;AACnDN,QAAQ,gBAAgB,IAAMC,QAAQ,kBAAkBM,YAAY;AACpE,sCAAsC;AACtC,kDAAkD;AAElDP,QAAQ,4BAA4B,IAAMC,QAAQ,wBAAwBO,kBAAkB;AAE5F,8EAA8E;AAC9ER,QAAQ,mBAAmB,IAAMC,QAAQ,2BAA2BQ,OAAO;AAE3EX,qBAAqBY;AACrBb,wBAAwBc;AAExB,6CAA6C;AAC7C,+EAA+E;AAC/EC,OAAOC,aAAa,KAAKD,OAAOE,GAAG,CAAC;AAEpC,MAAMC,aACJC,QAAQC,GAAG,CAACC,wBAAwB,KAAK,OAAOF,QAAQC,GAAG,CAACC,wBAAwB,KAAK;AAE3F,IAAI,CAACH,YAAY;IACf,sEAAsE;IACtE,sEAAsE;IACtE,IAAI,CAACI,WAAWC,OAAO,EAAE;QACvB,MAAM,IAAIC,MACR;IAEJ;IACArB,QAAQ,SAAS,IAAMC,QAAQ,WAAWqB,KAAK;AACjD"}
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "@expo/dom-webview": "~58.0.0",
3
- "@expo/fingerprint": "~0.21.0",
4
- "@expo/metro-runtime": "~58.0.0",
3
+ "@expo/fingerprint": "~0.21.1",
4
+ "@expo/metro-runtime": "~58.0.2",
5
5
  "@expo/vector-icons": "^15.0.2",
6
- "@expo/ui": "~58.0.0",
6
+ "@expo/ui": "~58.0.2",
7
7
  "@react-native-async-storage/async-storage": "2.2.0",
8
8
  "@react-native-community/datetimepicker": "9.2.1",
9
9
  "@react-native-masked-view/masked-view": "0.3.2",
@@ -13,95 +13,95 @@
13
13
  "@react-native-picker/picker": "2.11.4",
14
14
  "@react-native-segmented-control/segmented-control": "2.5.7",
15
15
  "@stripe/stripe-react-native": "0.76.0",
16
- "eslint-config-expo": "~58.0.0",
16
+ "eslint-config-expo": "~58.0.1",
17
17
  "expo-analytics-amplitude": "~11.3.0",
18
18
  "expo-app-auth": "~11.1.0",
19
- "expo-app-intents": "~0.2.0",
19
+ "expo-app-intents": "~0.4.0",
20
20
  "expo-app-loader-provider": "~8.0.0",
21
- "expo-app-metrics": "~58.0.0",
21
+ "expo-app-metrics": "~58.0.2",
22
22
  "expo-apple-authentication": "~58.0.0",
23
- "expo-application": "~58.0.0",
24
- "expo-asset": "~58.0.0",
23
+ "expo-application": "~58.0.1",
24
+ "expo-asset": "~58.0.2",
25
25
  "expo-audio": "~58.0.0",
26
- "expo-auth-session": "~58.0.0",
27
- "expo-background-fetch": "~58.0.0",
28
- "expo-background-task": "~58.0.0",
26
+ "expo-auth-session": "~58.0.1",
27
+ "expo-background-fetch": "~58.0.2",
28
+ "expo-background-task": "~58.0.2",
29
29
  "expo-battery": "~58.0.0",
30
30
  "expo-blur": "~58.0.0",
31
31
  "expo-brightness": "~58.0.0",
32
- "expo-brownfield": "~58.0.0",
33
- "expo-build-properties": "~58.0.0",
34
- "expo-calendar": "~58.0.0",
35
- "expo-camera": "~58.0.0",
32
+ "expo-brownfield": "~58.0.2",
33
+ "expo-build-properties": "~58.0.2",
34
+ "expo-calendar": "~58.0.1",
35
+ "expo-camera": "~58.0.1",
36
36
  "expo-cellular": "~58.0.0",
37
37
  "expo-checkbox": "~58.0.0",
38
38
  "expo-clipboard": "~58.0.0",
39
- "expo-constants": "~58.0.0",
40
- "expo-contacts": "~58.0.0",
41
- "expo-crypto": "~58.0.0",
42
- "expo-dev-client": "~58.0.0",
39
+ "expo-constants": "~58.0.2",
40
+ "expo-contacts": "~58.0.1",
41
+ "expo-crypto": "~58.0.1",
42
+ "expo-dev-client": "~58.0.2",
43
43
  "expo-device": "~58.0.0",
44
44
  "expo-document-picker": "~58.0.0",
45
45
  "expo-eas-client": "~58.0.0",
46
46
  "expo-file-system": "~58.0.0",
47
- "expo-font": "~58.0.0",
47
+ "expo-font": "~58.0.1",
48
48
  "expo-gl": "~58.0.0",
49
49
  "expo-glass-effect": "~58.0.0",
50
50
  "expo-google-app-auth": "~8.3.0",
51
- "expo-haptics": "~58.0.0",
52
- "expo-image": "~58.0.0",
53
- "expo-image-loader": "~58.0.0",
54
- "expo-image-manipulator": "~58.0.0",
55
- "expo-image-picker": "~58.0.0",
51
+ "expo-haptics": "~58.0.1",
52
+ "expo-image": "~58.0.2",
53
+ "expo-image-loader": "~58.0.1",
54
+ "expo-image-manipulator": "~58.0.3",
55
+ "expo-image-picker": "~58.0.2",
56
56
  "expo-intent-launcher": "~58.0.0",
57
- "expo-insights": "~58.0.0",
57
+ "expo-insights": "~58.0.2",
58
58
  "expo-keep-awake": "~58.0.0",
59
59
  "expo-linear-gradient": "~58.0.0",
60
- "expo-linking": "~58.0.0",
60
+ "expo-linking": "~58.0.2",
61
61
  "expo-local-authentication": "~58.0.0",
62
62
  "expo-localization": "~58.0.0",
63
- "expo-location": "~58.0.0",
63
+ "expo-location": "~58.0.2",
64
64
  "expo-mail-composer": "~58.0.0",
65
65
  "expo-manifests": "~58.0.0",
66
- "expo-maps": "~58.0.0",
66
+ "expo-maps": "~58.0.1",
67
67
  "expo-mcp": "~0.2.1",
68
68
  "expo-media-library": "~58.0.0",
69
69
  "expo-mesh-gradient": "~58.0.0",
70
- "expo-module-template": "~58.0.0",
71
- "expo-modules-core": "~58.0.0",
70
+ "expo-module-template": "~58.0.1",
71
+ "expo-modules-core": "~58.0.2",
72
72
  "expo-navigation-bar": "~58.0.0",
73
73
  "expo-network": "~58.0.0",
74
- "expo-notifications": "~58.0.0",
75
- "expo-observe": "~58.0.0",
74
+ "expo-notifications": "~58.0.2",
75
+ "expo-observe": "~58.0.4",
76
76
  "expo-print": "~58.0.0",
77
77
  "expo-live-photo": "~58.0.0",
78
- "expo-router": "~58.0.0",
78
+ "expo-router": "~58.0.3",
79
79
  "expo-screen-capture": "~58.0.0",
80
80
  "expo-screen-orientation": "~58.0.0",
81
81
  "expo-secure-store": "~58.0.0",
82
82
  "expo-sensors": "~58.0.0",
83
83
  "expo-server": "~58.0.0",
84
- "expo-sharing": "~58.0.0",
84
+ "expo-sharing": "~58.0.4",
85
85
  "expo-sms": "~58.0.0",
86
86
  "expo-speech": "~58.0.0",
87
87
  "expo-splash-screen": "~58.0.0",
88
- "expo-sqlite": "~58.0.0",
88
+ "expo-sqlite": "~58.0.3",
89
89
  "expo-status-bar": "~58.0.0",
90
90
  "expo-store-review": "~58.0.0",
91
- "expo-symbols": "~58.0.0",
92
- "expo-system-ui": "~58.0.0",
93
- "expo-task-manager": "~58.0.0",
91
+ "expo-symbols": "~58.0.1",
92
+ "expo-system-ui": "~58.0.1",
93
+ "expo-task-manager": "~58.0.3",
94
94
  "expo-tracking-transparency": "~58.0.0",
95
- "expo-updates": "~58.0.0",
95
+ "expo-updates": "~58.0.4",
96
96
  "expo-video-thumbnails": "~58.0.0",
97
- "expo-video": "~58.0.0",
97
+ "expo-video": "~58.0.1",
98
98
  "expo-web-browser": "~58.0.0",
99
- "expo-widgets": "~58.0.0",
100
- "jest-expo": "~58.0.0",
99
+ "expo-widgets": "~58.0.2",
100
+ "jest-expo": "~58.0.2",
101
101
  "lottie-react-native": "~7.5.0",
102
102
  "react": "19.2.3",
103
103
  "react-dom": "19.2.3",
104
- "react-native": "0.87.1",
104
+ "react-native": "0.88.0-rc.0",
105
105
  "react-native-web": "~0.21.0",
106
106
  "react-native-gesture-handler": "~3.2.1",
107
107
  "react-native-keyboard-controller": "1.22.4",
@@ -1,7 +1,6 @@
1
1
  // Copyright 2015-present 650 Industries. All rights reserved.
2
2
 
3
3
  import Foundation
4
- import ExpoModulesCore
5
4
  import React
6
5
  #if canImport(ExpoObjC)
7
6
  import ExpoObjC
@@ -15,14 +14,18 @@ import ExpoObjC
15
14
 
16
15
  Responsibilities:
17
16
  - Create the `UIWindow` from the connecting `UIWindowScene` and start React Native into it.
18
- - Re-feed scene life-cycle, URL, and user-activity events to the existing
19
- `ExpoAppDelegateSubscriberManager`, so app delegate subscribers keep working unchanged.
17
+ - Re-feed scene life-cycle, URL, user-activity, and quick-action events to the app delegate.
18
+
19
+ Requires the app delegate to be an `ExpoAppDelegate`; it forwards every event to the subscribers,
20
+ so one call reaches both subscribers and `AppDelegate` overrides.
20
21
  */
21
22
  @available(iOSApplicationExtension, unavailable)
22
23
  @objc(EXExpoAppSceneDelegate)
23
24
  open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
24
25
  open var window: UIWindow?
25
26
 
27
+ let forwarder = SceneEventForwarder()
28
+
26
29
  open func scene(
27
30
  _ scene: UIScene,
28
31
  willConnectTo session: UISceneSession,
@@ -31,12 +34,14 @@ open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
31
34
  guard let windowScene = scene as? UIWindowScene else {
32
35
  return
33
36
  }
34
- guard let provider = UIApplication.shared.delegate as? ExpoReactNativeFactoryProvider,
37
+ guard let appDelegate = UIApplication.shared.delegate as? ExpoAppDelegate,
38
+ let provider = appDelegate as? ExpoReactNativeFactoryProvider,
35
39
  let factory = provider.reactNativeFactory else {
36
40
  fatalError(
37
- "ExpoAppSceneDelegate couldn't start React Native because the app delegate doesn't provide a "
38
- + "React Native factory. Make sure your AppDelegate conforms to ExpoReactNativeFactoryProvider and "
39
- + "creates its RCTReactNativeFactory in application(_:didFinishLaunchingWithOptions:)."
41
+ "ExpoAppSceneDelegate couldn't start React Native because the app delegate isn't an "
42
+ + "ExpoAppDelegate that provides a React Native factory. Make sure your AppDelegate subclasses "
43
+ + "ExpoAppDelegate, conforms to ExpoReactNativeFactoryProvider and creates its "
44
+ + "RCTReactNativeFactory in application(_:didFinishLaunchingWithOptions:)."
40
45
  )
41
46
  }
42
47
 
@@ -64,43 +69,76 @@ open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
64
69
  )
65
70
 
66
71
  // Deep links / universal links.
67
- Self.route(urlContexts: connectionOptions.urlContexts)
68
- connectionOptions.userActivities.forEach { Self.route(userActivity: $0) }
72
+ connectionOptions.urlContexts.forEach {
73
+ forwarder.open(url: $0.url, options: Self.openURLOptions(from: $0.options))
74
+ }
75
+ connectionOptions.userActivities.forEach { forwarder.continue($0) }
76
+
77
+ #if os(iOS)
78
+ // A quick action that cold-starts the app arrives here instead of in
79
+ // `windowScene(_:performActionFor:completionHandler:)`, which UIKit only calls while running.
80
+ if let shortcutItem = connectionOptions.shortcutItem {
81
+ forwarder.perform(shortcutItem) { _ in }
82
+ }
83
+ #endif
69
84
  }
70
85
 
71
86
  open func sceneDidDisconnect(_ scene: UIScene) {
72
87
  window = nil
73
88
  }
74
89
 
75
- // In the scene lifecycle UIKit no longer calls the app delegate equivalents, so we forward
76
- // these to the subscriber manager to preserve existing subscriber behavior.
77
-
78
90
  open func sceneDidBecomeActive(_ scene: UIScene) {
79
- ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(UIApplication.shared)
91
+ forwarder.didBecomeActive()
80
92
  }
81
93
 
82
94
  open func sceneWillResignActive(_ scene: UIScene) {
83
- ExpoAppDelegateSubscriberManager.applicationWillResignActive(UIApplication.shared)
95
+ forwarder.willResignActive()
84
96
  }
85
97
 
86
98
  open func sceneWillEnterForeground(_ scene: UIScene) {
87
- ExpoAppDelegateSubscriberManager.applicationWillEnterForeground(UIApplication.shared)
99
+ forwarder.willEnterForeground()
88
100
  }
89
101
 
90
102
  open func sceneDidEnterBackground(_ scene: UIScene) {
91
- ExpoAppDelegateSubscriberManager.applicationDidEnterBackground(UIApplication.shared)
103
+ forwarder.didEnterBackground()
92
104
  }
93
105
 
94
106
  open func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
95
- Self.route(urlContexts: URLContexts)
107
+ URLContexts.forEach { forwarder.open(url: $0.url, options: Self.openURLOptions(from: $0.options)) }
108
+ }
109
+
110
+ open func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
111
+ forwarder.willContinueUserActivity(withType: userActivityType)
96
112
  }
97
113
 
98
114
  open func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
99
- Self.route(userActivity: userActivity)
115
+ forwarder.continue(userActivity)
116
+ }
117
+
118
+ open func scene(
119
+ _ scene: UIScene,
120
+ didFailToContinueUserActivityWithType userActivityType: String,
121
+ error: Error
122
+ ) {
123
+ forwarder.didFailToContinueUserActivity(withType: userActivityType, error: error)
100
124
  }
125
+
126
+ open func scene(_ scene: UIScene, didUpdate userActivity: NSUserActivity) {
127
+ forwarder.didUpdate(userActivity)
128
+ }
129
+
130
+ #if os(iOS)
131
+ open func windowScene(
132
+ _ windowScene: UIWindowScene,
133
+ performActionFor shortcutItem: UIApplicationShortcutItem,
134
+ completionHandler: @escaping (Bool) -> Void
135
+ ) {
136
+ forwarder.perform(shortcutItem, completionHandler: completionHandler)
137
+ }
138
+ #endif
101
139
  }
102
140
 
103
- // MARK: - Launch options & routing helpers
141
+ // MARK: - Launch options
104
142
 
105
143
  @available(iOSApplicationExtension, unavailable)
106
144
  extension ExpoAppSceneDelegate {
@@ -132,29 +170,6 @@ extension ExpoAppSceneDelegate {
132
170
  return launchOptions.isEmpty ? nil : launchOptions
133
171
  }
134
172
 
135
- /// Pass incoming URL contexts to both the subscriber manager and `RCTLinkingManager`.
136
- public static func route(urlContexts: Set<UIOpenURLContext>) {
137
- for context in urlContexts {
138
- let options = openURLOptions(from: context.options)
139
- _ = ExpoAppDelegateSubscriberManager.application(UIApplication.shared, open: context.url, options: options)
140
- RCTLinkingManager.application(UIApplication.shared, open: context.url, options: options)
141
- }
142
- }
143
-
144
- /// Passes an incoming `NSUserActivity` to both the subscriber manager and `RCTLinkingManager`.
145
- public static func route(userActivity: NSUserActivity) {
146
- _ = ExpoAppDelegateSubscriberManager.application(
147
- UIApplication.shared,
148
- continue: userActivity,
149
- restorationHandler: { _ in }
150
- )
151
- RCTLinkingManager.application(
152
- UIApplication.shared,
153
- continue: userActivity,
154
- restorationHandler: { _ in }
155
- )
156
- }
157
-
158
173
  private static func openURLOptions(
159
174
  from sceneOptions: UIScene.OpenURLOptions
160
175
  ) -> [UIApplication.OpenURLOptionsKey: Any] {