From f1b5de2acd564f7ae9d866eb1c9b7445fcb0d057 Mon Sep 17 00:00:00 2001 From: Ryan Karn Date: Wed, 13 May 2026 14:29:30 -0700 Subject: [PATCH 1/2] feat: add iOS origin sharing, idleTTL prop, and origin-pooling demo (#30, #28) - Add iOS support for shared-origin factory pooling (same-origin sandboxes reuse a single RCTReactNativeFactory) - Add idleTTL prop to defer ReactHost/factory cleanup after last surface unmounts, enabling warm starts for same-origin remounts - Add origin-pooling demo app demonstrating both features on iOS and Android Ref: https://github.com/callstackincubator/react-native-sandbox/issues/28 Ref: https://github.com/callstackincubator/react-native-sandbox/issues/30 --- apps/origin-pooling/App.tsx | 308 +++++++++++ apps/origin-pooling/README.md | 34 ++ apps/origin-pooling/SandboxApp.tsx | 142 +++++ apps/origin-pooling/android/app/build.gradle | 91 ++++ .../origin-pooling/android/app/debug.keystore | Bin 0 -> 2257 bytes .../android/app/proguard-rules.pro | 10 + .../android/app/src/debug/AndroidManifest.xml | 9 + .../android/app/src/main/AndroidManifest.xml | 26 + .../java/com/originpooling/MainActivity.kt | 22 + .../java/com/originpooling/MainApplication.kt | 43 ++ .../res/drawable/rn_edit_text_material.xml | 37 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../mipmap-hdpi/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../mipmap-mdpi/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../mipmap-xhdpi/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 9 + apps/origin-pooling/android/build.gradle | 21 + apps/origin-pooling/android/gradle.properties | 39 ++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + apps/origin-pooling/android/gradlew | 251 +++++++++ apps/origin-pooling/android/gradlew.bat | 99 ++++ apps/origin-pooling/android/settings.gradle | 6 + apps/origin-pooling/app.json | 4 + apps/origin-pooling/babel.config.js | 3 + apps/origin-pooling/index.js | 5 + apps/origin-pooling/ios/.xcode.env | 12 + .../OriginPooling.xcodeproj/project.pbxproj | 496 ++++++++++++++++++ .../xcschemes/OriginPooling.xcscheme | 88 ++++ .../contents.xcworkspacedata | 10 + .../ios/OriginPooling/AppDelegate.swift | 27 + .../AppIcon.appiconset/Contents.json | 53 ++ .../Images.xcassets/Contents.json | 6 + .../ios/OriginPooling/Info.plist | 53 ++ .../ios/OriginPooling/LaunchScreen.storyboard | 73 +++ .../ios/OriginPooling/PrivacyInfo.xcprivacy | 37 ++ apps/origin-pooling/ios/Podfile | 35 ++ apps/origin-pooling/metro.config.js | 21 + apps/origin-pooling/package.json | 30 ++ apps/origin-pooling/sandbox.js | 5 + bun.lock | 25 +- packages/react-native-sandbox/README.md | 24 + .../rnsandbox/SandboxReactNativeDelegate.kt | 38 +- .../SandboxReactNativeViewManager.kt | 8 + .../ios/SandboxReactNativeDelegate.mm | 40 +- .../ios/SandboxReactNativeViewComponentView.h | 2 +- .../SandboxReactNativeViewComponentView.mm | 142 ++++- .../specs/NativeSandboxReactNativeView.ts | 7 + packages/react-native-sandbox/src/index.tsx | 29 + 66 files changed, 2405 insertions(+), 25 deletions(-) create mode 100644 apps/origin-pooling/App.tsx create mode 100644 apps/origin-pooling/README.md create mode 100644 apps/origin-pooling/SandboxApp.tsx create mode 100644 apps/origin-pooling/android/app/build.gradle create mode 100644 apps/origin-pooling/android/app/debug.keystore create mode 100644 apps/origin-pooling/android/app/proguard-rules.pro create mode 100644 apps/origin-pooling/android/app/src/debug/AndroidManifest.xml create mode 100644 apps/origin-pooling/android/app/src/main/AndroidManifest.xml create mode 100644 apps/origin-pooling/android/app/src/main/java/com/originpooling/MainActivity.kt create mode 100644 apps/origin-pooling/android/app/src/main/java/com/originpooling/MainApplication.kt create mode 100644 apps/origin-pooling/android/app/src/main/res/drawable/rn_edit_text_material.xml create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-hdpi/mipmap-hdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-hdpi/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-mdpi/mipmap-mdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-mdpi/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xhdpi/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xhdpi/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxhdpi/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxhdpi/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxxhdpi/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/origin-pooling/android/app/src/main/res/mipmap-xxxhdpi/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/origin-pooling/android/app/src/main/res/values/strings.xml create mode 100644 apps/origin-pooling/android/app/src/main/res/values/styles.xml create mode 100644 apps/origin-pooling/android/build.gradle create mode 100644 apps/origin-pooling/android/gradle.properties create mode 100644 apps/origin-pooling/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 apps/origin-pooling/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 apps/origin-pooling/android/gradlew create mode 100644 apps/origin-pooling/android/gradlew.bat create mode 100644 apps/origin-pooling/android/settings.gradle create mode 100644 apps/origin-pooling/app.json create mode 100644 apps/origin-pooling/babel.config.js create mode 100644 apps/origin-pooling/index.js create mode 100644 apps/origin-pooling/ios/.xcode.env create mode 100644 apps/origin-pooling/ios/OriginPooling.xcodeproj/project.pbxproj create mode 100644 apps/origin-pooling/ios/OriginPooling.xcodeproj/xcshareddata/xcschemes/OriginPooling.xcscheme create mode 100644 apps/origin-pooling/ios/OriginPooling.xcworkspace/contents.xcworkspacedata create mode 100644 apps/origin-pooling/ios/OriginPooling/AppDelegate.swift create mode 100644 apps/origin-pooling/ios/OriginPooling/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 apps/origin-pooling/ios/OriginPooling/Images.xcassets/Contents.json create mode 100644 apps/origin-pooling/ios/OriginPooling/Info.plist create mode 100644 apps/origin-pooling/ios/OriginPooling/LaunchScreen.storyboard create mode 100644 apps/origin-pooling/ios/OriginPooling/PrivacyInfo.xcprivacy create mode 100644 apps/origin-pooling/ios/Podfile create mode 100644 apps/origin-pooling/metro.config.js create mode 100644 apps/origin-pooling/package.json create mode 100644 apps/origin-pooling/sandbox.js diff --git a/apps/origin-pooling/App.tsx b/apps/origin-pooling/App.tsx new file mode 100644 index 0000000..e3ff4b0 --- /dev/null +++ b/apps/origin-pooling/App.tsx @@ -0,0 +1,308 @@ +/** + * Origin Pooling Demo + * + * Dynamically add/remove sandboxes under two shared origins (alpha, beta) + * plus an isolated (no-origin) option. Same-origin sandboxes share a + * ReactHost / Hermes VM; removing the last one triggers the idle TTL. + * + * Messaging is handled inside the sandbox widget via globalThis.postMessage. + * The host only logs messages received via onMessage. + */ +import SandboxReactNativeView from '@callstack/react-native-sandbox' +import React, {useCallback, useRef, useState} from 'react' +import { + Button, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + View, +} from 'react-native' + +type SandboxEntry = {key: string; label: string; origin: string} +type LogEntry = {source: string; text: string; ts: number} + +let nextId = 0 + +const ORIGIN_ALPHA = 'alpha' +const ORIGIN_BETA = 'beta' +const COLOR_ALPHA = '#8232ff' +const COLOR_BETA = '#e67e22' +const COLOR_ISOLATED = '#6c757d' + +/** Alpha uses a function-based TTL (4 seconds) */ +const ALPHA_TTL = () => 4000 +/** Beta and isolated use a static TTL (2 seconds) */ +const DEFAULT_TTL = 2000 + +export default function App() { + const [sandboxes, setSandboxes] = useState([]) + const [log, setLog] = useState([]) + const logScrollRef = useRef(null) + + const addLog = useCallback((source: string, text: string) => { + setLog(prev => [...prev.slice(-49), {source, text, ts: Date.now()}]) + }, []) + + const addSandbox = useCallback((origin: string) => { + const id = String(++nextId) + setSandboxes(prev => [...prev, {key: id, label: `#${id}`, origin}]) + }, []) + + const removeSandbox = useCallback((key: string) => { + setSandboxes(prev => prev.filter(s => s.key !== key)) + }, []) + + const clearLog = useCallback(() => setLog([]), []) + + const alphas = sandboxes.filter(s => s.origin === ORIGIN_ALPHA) + const betas = sandboxes.filter(s => s.origin === ORIGIN_BETA) + const isolated = sandboxes.filter(s => s.origin === '') + + return ( + + Origin Pooling Demo + + Same-origin sandboxes share a VM. Alpha: function-based TTL (4s). Beta: + static TTL (2s). + + + +