feat: Expo managed workflow support (iOS + Android isolation) - #42
Draft
CAMOBAP wants to merge 7 commits into
Draft
feat: Expo managed workflow support (iOS + Android isolation)#42CAMOBAP wants to merge 7 commits into
CAMOBAP wants to merge 7 commits into
Conversation
- Add runtime ObjC dispatch via objc_getClass/performSelector to derive the sandbox bundle URL from EXDevLauncherController.sourceUrl without a hard compile-time dependency on expo-dev-launcher headers - Add KVO on EXDevLauncherController.sourceUrl in SandboxReactNativeViewComponentView to retry sandbox load once the Expo host bundle URL becomes available (set asynchronously after deep-link network check, ~1-2s after launch) - Expose bundleURL as a public method on SandboxReactNativeDelegate - Add apps/expo-demo: a minimal Expo SDK 54 / RN 0.81.4 managed-workflow app that exercises the sandbox inside an expo-dev-client build - Remove orphaned apps/expo/ (abandoned WIP native build output, no package.json), replace with apps/expo-demo using managed workflow - Pin metro extraNodeModules.react to workspace root to avoid bun hoisting a stale react@19.0.0 into the app's local node_modules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix stale comment referencing RCTJavaScriptDidLoadNotification (the implementation uses KVO, not the notification) - Forward unhandled KVO observations to super in observeValueForKeyPath:ofObject:change:context: - Remove duplicate #import <objc/runtime.h> in SandboxReactNativeDelegate.mm - Use a typed static context pointer (kExpoSourceUrlKVOContext) for KVO registration/removal instead of nil, preventing accidental dispatch of superclass observations into our handler - Correct infiniteLoop comment: hangs sandbox JS thread only, host app stays responsive (each sandbox runs in its own Hermes instance) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 tasks
Auto-detects expo-modules-core at build time so vanilla RN projects are unaffected:
- iOS (podspec): File.exist? check sets RNS_HAS_EXPO_MODULES=1 via pod_target_xcconfig
and pulls in ios/Expo/** sources + expo-modules-core/expo dependencies only when present
- Android (build.gradle): rootProject.file("node_modules/expo-modules-core").exists()
gates the src/expo/java source set, expo-modules-core dependency, and HAS_EXPO_MODULES
BuildConfig field
iOS isolation (gated by #if RNS_HAS_EXPO_MODULES):
- SandboxExpoFactory.mm: creates ExpoReactNativeFactory instead of RCTReactNativeFactory,
enabling Expo module registration inside the sandbox RCTHost
- SandboxAppContextBridge.swift: Swift bridge that creates EXAppContext with a scoped
AppContextConfig (documentDirectory/cacheDirectory under SandboxData/<sanitized-origin>/)
- SandboxReactNativeDelegate.mm: injects the scoped EXAppContext via
ExpoBridgeModule.initWithAppContext: in getModuleInstanceFromClass: so Expo modules
(FileSystem, SecureStore, etc.) read/write origin-namespaced directories
Android isolation:
- SandboxContextWrapper already scopes getFilesDir/getCacheDir/getSharedPreferences to the
origin; AppContext receives this wrapped context so Expo modules are automatically scoped
- ExpoIntegration.kt provides a ModuleRegistryAdapter with the host project's Expo packages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… into Android sandbox - SandboxExpoFactory.mm: use correct ObjC class name EXReactNativeFactory and import <Expo/ExpoReactNativeFactory.h> (ObjC header, not Swift-generated) - SandboxAppContextBridge.swift: fix return type to AppContext (Swift name), not EXAppContext (ObjC alias) - ExpoIntegration.kt: replace legacy ModuleRegistryAdapter with ExpoModulesPackage - SandboxReactNativeDelegate.kt: wire Expo packages into sandbox host via reflection-guarded getExpoPackages() so the main source set never directly references the conditionally-compiled ExpoIntegration class - React-Sandbox.podspec: refactor source_files to declarative ternary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CAMOBAP
force-pushed
the
feat/expo-managed-workflow-support
branch
from
July 29, 2026 08:32
764ba56 to
b1c314e
Compare
…ft header import ExpoReactNativeFactory is public but not open, so it cannot be subclassed from Swift outside the Expo module. Replace the subclass approach with a static factory method on SandboxExpoFactory that constructs and returns an ExpoReactNativeFactory — composition instead of inheritance. Also fix React_Sandbox-Swift.h import in SandboxReactNativeDelegate.mm (was using wrong hyphenated name; CocoaPods generates underscore form). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ft header import Implement iOS scoped AppContext via ObjC subclass of EXReactNativeFactory. Override host:didInitializeRuntime: to replace [[EXAppContext alloc] init] with [SandboxAppContextBridge createScopedAppContextForOrigin:origin] — all other lines (scheduler binding, setRuntime:, setHostWrapper:, registerNativeModules) are identical to Expo's implementation. Expo modules registered via registerNativeModules all share the scoped AppContext, giving them origin-namespaced document/cache directories automatically. Remove the now-redundant ExpoBridgeModule interception from SandboxReactNativeDelegate (it was creating a second, disconnected AppContext for just one module). The factory override covers the whole runtime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
EXDevLauncherController.sourceUrlvia KVO when the bundle URL is unavailable at startup, enabling the sandbox to load bundles served byexpo-dev-clientwithout any configurationexpo-modules-coreat pod install time; when present, usesEXReactNativeFactoryper sandbox so Expo modules are registered and anAppContextis created for each runtimeReactApplicationContextwithSandboxContextWrapper(scopesfilesDir,cacheDir,sharedPreferences); Expo modules (ExpoModulesPackage) are now added to the sandbox package list whenexpo-modules-coreis detected innode_modulesexpo-modules-coreis innode_modulesNotes
iOS scoped
AppContextConfig(per-origin document/cache dirs) is scaffolded inSandboxAppContextBridge.swiftbut not yet wired, becauseEXReactNativeFactoryhas no public hook to inject a custom config. Android already achieves equivalent scoping viaSandboxContextWrapper. iOS scoped dirs are a follow-up item.Test plan
apps/expo-demobuilds and sandbox loads in Expo dev-clientapps/expo) still builds and works unchangedpod installin a non-Expo project does not pullexpo-modules-core🤖 Generated with Claude Code