const fs = require('node:fs') const path = require('node:path') const { withDangerousMod } = require('expo/config-plugins') // Why: Expo SDK 58's template adopts the UIScene lifecycle the iOS 27 SDK requires (TN3187), and // its ExpoAppSceneDelegate forwards URLs and user activities. It starts React Native on every // scene connect, though, and iOS may disconnect the app's only scene in the background and // reconnect it later. Reattach the running root then, so a reconnect keeps the app's state // instead of starting a second React Native instance. const MARKER = 'orca: UIScene reconnect' const EXTENSION_POINT = ' // Extension point for config plugins.\n' const TEMPLATE_CLASS = 'class SceneDelegate: ExpoAppSceneDelegate {\n' const RECONNECT_OVERRIDE = ` // ${MARKER}, generated by plugins/ios-scene-lifecycle.js. override func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { guard let windowScene = scene as? UIWindowScene, let provider = UIApplication.shared.delegate as? ExpoReactNativeFactoryProvider, let runningRoot = provider.window?.rootViewController else { super.scene(scene, willConnectTo: session, options: connectionOptions) return } let window = UIWindow(windowScene: windowScene) self.window = window provider.window = window window.rootViewController = runningRoot window.makeKeyAndVisible() if !connectionOptions.urlContexts.isEmpty { self.scene(scene, openURLContexts: connectionOptions.urlContexts) } for activity in connectionOptions.userActivities { self.scene(scene, continue: activity) } } ` function patchSceneDelegate(contents) { if (contents.includes(MARKER)) { return contents } // A changed template must fail prebuild rather than silently drop the reconnect behavior. if (!contents.includes(TEMPLATE_CLASS) || !contents.includes(EXTENSION_POINT)) { throw new Error( "ios-scene-lifecycle: Expo's SceneDelegate template changed; update the plugin before building." ) } return contents.replace(EXTENSION_POINT, `${EXTENSION_POINT}\n${RECONNECT_OVERRIDE}`) } module.exports = function withIosSceneLifecycle(config) { return withDangerousMod(config, [ 'ios', (cfg) => { const { platformProjectRoot, projectName } = cfg.modRequest const file = path.join(platformProjectRoot, projectName, 'SceneDelegate.swift') fs.writeFileSync(file, patchSceneDelegate(fs.readFileSync(file, 'utf8'))) return cfg } ]) }