Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
95 lines
3.4 KiB
JavaScript
95 lines
3.4 KiB
JavaScript
const { withAppDelegate, withInfoPlist } = require("expo/config-plugins");
|
|
|
|
const LAUNCHING_BLOCK = ` reactNativeDelegate = delegate
|
|
reactNativeFactory = factory
|
|
|
|
#if os(iOS) || os(tvOS)
|
|
window = UIWindow(frame: UIScreen.main.bounds)
|
|
factory.startReactNative(
|
|
withModuleName: "main",
|
|
in: window,
|
|
launchOptions: launchOptions)
|
|
#endif
|
|
|
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)`;
|
|
|
|
const LAUNCHING_BLOCK_REPLACEMENT = ` reactNativeDelegate = delegate
|
|
reactNativeFactory = factory
|
|
|
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)`;
|
|
|
|
const CLASS_DECLARATION = "class AppDelegate: ExpoAppDelegate {";
|
|
const CLASS_DECLARATION_REPLACEMENT =
|
|
"class AppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider {";
|
|
|
|
// The ObjC runtime name of Expo's `ExpoAppSceneDelegate`; referencing it by
|
|
// runtime name avoids Swift module-name mangling in the generated Info.plist.
|
|
const SCENE_DELEGATE_CLASS_NAME = "EXExpoAppSceneDelegate";
|
|
|
|
/**
|
|
* iOS 26 refuses to launch apps built with the iOS 26 SDK unless they adopt
|
|
* the UIScene life cycle. Expo's SDK 57 AppDelegate template still creates a
|
|
* bare UIWindow in didFinishLaunching, so a prebuild from a current Xcode
|
|
* crashes at startup on iOS 26 devices. Point the scene manifest at Expo's
|
|
* own scene delegate and let it own window creation: it starts React Native
|
|
* from the factory the app delegate builds, and re-feeds deep links,
|
|
* universal links, and quick actions through Expo's linking path (which a
|
|
* hand-rolled delegate forwarding to RCTLinkingManager would bypass).
|
|
*/
|
|
function withSceneLifecycle(config) {
|
|
config = withAppDelegate(config, (config) => {
|
|
config.modResults.contents = applySceneLifecycleToAppDelegate(config.modResults.contents);
|
|
return config;
|
|
});
|
|
|
|
config = withInfoPlist(config, (config) => {
|
|
applySceneManifest(config.modResults);
|
|
return config;
|
|
});
|
|
|
|
return config;
|
|
}
|
|
|
|
function applySceneLifecycleToAppDelegate(contents) {
|
|
// Only treat the file as already migrated when no legacy window startup is
|
|
// left; a file with both would double-start React Native (the app delegate
|
|
// and the scene delegate each call startReactNative).
|
|
if (contents.includes("ExpoReactNativeFactoryProvider")) {
|
|
if (contents.includes(LAUNCHING_BLOCK)) {
|
|
throw new Error(
|
|
"with-scene-lifecycle: AppDelegate already conforms to ExpoReactNativeFactoryProvider but still has legacy window startup; refusing to guess.",
|
|
);
|
|
}
|
|
return contents;
|
|
}
|
|
|
|
if (!contents.includes(LAUNCHING_BLOCK) || !contents.includes(CLASS_DECLARATION)) {
|
|
throw new Error(
|
|
"with-scene-lifecycle: Expo AppDelegate template changed; update the plugin's anchor strings.",
|
|
);
|
|
}
|
|
|
|
return contents
|
|
.replace(CLASS_DECLARATION, CLASS_DECLARATION_REPLACEMENT)
|
|
.replace(LAUNCHING_BLOCK, LAUNCHING_BLOCK_REPLACEMENT);
|
|
}
|
|
|
|
function applySceneManifest(infoPlist) {
|
|
infoPlist.UIApplicationSceneManifest = {
|
|
UIApplicationSupportsMultipleScenes: false,
|
|
UISceneConfigurations: {
|
|
UIWindowSceneSessionRoleApplication: [
|
|
{
|
|
UISceneConfigurationName: "main",
|
|
UISceneDelegateClassName: SCENE_DELEGATE_CLASS_NAME,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
return infoPlist;
|
|
}
|
|
|
|
module.exports = withSceneLifecycle;
|
|
module.exports.applySceneLifecycleToAppDelegate = applySceneLifecycleToAppDelegate;
|
|
module.exports.applySceneManifest = applySceneManifest;
|