From 3189a4f08233fbf1727bf0cd383d48167f4df0d6 Mon Sep 17 00:00:00 2001 From: Ryan Karn Date: Thu, 14 May 2026 14:02:08 -0700 Subject: [PATCH 1/4] fix: sandbox msging bugs (shared origin #29, isPermittedFrom #35) Fixing issues for handling messages and errors when sandboxes use the same origin. Namely, this approach defines and passes an id that is passed to the sandbox, and provides a hook that allows sandboxes to use this for messaging. This is to distinguish views on the same origin that share the same runtime and globals. This also addresses an issue there allowOrigin checks where checking sender side allows rather than receiver side. ref: https://github.com/callstackincubator/react-native-sandbox/issues/29 ref: https://github.com/callstackincubator/react-native-sandbox/issues/35 --- apps/origin-pooling/App.tsx | 57 +- apps/origin-pooling/README.md | 78 + apps/origin-pooling/SandboxApp.tsx | 39 +- apps/origin-pooling/SandboxAppConvention.tsx | 162 + apps/origin-pooling/ios/Podfile.lock | 2465 + .../LogBox/UI/LogBoxImages/alert-triangle.png | Bin 0 -> 609 bytes .../LogBox/UI/LogBoxImages/chevron-left.png | Bin 0 -> 126 bytes .../LogBox/UI/LogBoxImages/chevron-right.png | Bin 0 -> 123 bytes .../LogBox/UI/LogBoxImages/close.png | Bin 0 -> 187 bytes .../LogBox/UI/LogBoxImages/loader.png | Bin 0 -> 282 bytes apps/origin-pooling/ios/sandbox.jsbundle | 99314 ++++++++++++++++ apps/origin-pooling/sandbox.js | 5 + apps/p2p-counter/App.tsx | 14 +- ...stancePOC.xcscheme => P2PCounter.xcscheme} | 19 +- apps/p2p-counter/ios/Podfile.lock | 4 +- .../rnsandbox/SandboxJSIInstaller.kt | 46 + .../rnsandbox/SandboxReactNativeDelegate.kt | 137 +- .../SandboxReactNativeViewManager.kt | 26 +- .../src/main/jni/SandboxJSIInstaller.cpp | 514 +- .../cxx/ISandboxDelegate.h | 13 + .../cxx/SandboxDelegateWrapper.h | 5 + .../cxx/SandboxRegistry.cpp | 28 +- .../cxx/SandboxRegistry.h | 15 + .../ios/SandboxDelegateWrapper.mm | 11 + .../ios/SandboxReactNativeDelegate.h | 36 + .../ios/SandboxReactNativeDelegate.mm | 292 +- .../SandboxReactNativeViewComponentView.mm | 44 + packages/react-native-sandbox/src/index.tsx | 1 + .../src/useSurfaceMessaging.ts | 75 + .../tests/MockSandboxDelegate.h | 8 + .../tests/SandboxRegistryTest.cpp | 20 +- 31 files changed, 103240 insertions(+), 188 deletions(-) create mode 100644 apps/origin-pooling/SandboxAppConvention.tsx create mode 100644 apps/origin-pooling/ios/Podfile.lock create mode 100644 apps/origin-pooling/ios/assets/__node_modules/react-native/Libraries/LogBox/UI/LogBoxImages/alert-triangle.png create mode 100644 apps/origin-pooling/ios/assets/__node_modules/react-native/Libraries/LogBox/UI/LogBoxImages/chevron-left.png create mode 100644 apps/origin-pooling/ios/assets/__node_modules/react-native/Libraries/LogBox/UI/LogBoxImages/chevron-right.png create mode 100644 apps/origin-pooling/ios/assets/__node_modules/react-native/Libraries/LogBox/UI/LogBoxImages/close.png create mode 100644 apps/origin-pooling/ios/assets/__node_modules/react-native/Libraries/LogBox/UI/LogBoxImages/loader.png create mode 100644 apps/origin-pooling/ios/sandbox.jsbundle rename apps/p2p-counter/ios/P2PCounter.xcodeproj/xcshareddata/xcschemes/{MultiInstancePOC.xcscheme => P2PCounter.xcscheme} (79%) create mode 100644 packages/react-native-sandbox/src/useSurfaceMessaging.ts diff --git a/apps/origin-pooling/App.tsx b/apps/origin-pooling/App.tsx index e3ff4b0..dfcf3a5 100644 --- a/apps/origin-pooling/App.tsx +++ b/apps/origin-pooling/App.tsx @@ -2,8 +2,13 @@ * 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. + * plus isolated sandboxes (each gets a unique origin and its own VM). + * Same-origin sandboxes share a ReactHost / Hermes VM; removing the last + * one triggers the idle TTL. + * + * Access control demo: alpha and beta only accept messages from "isolated-1". + * Other isolated sandboxes (isolated-2, isolated-3, …) will get + * AccessDeniedError when trying to ping alpha or beta. * * Messaging is handled inside the sandbox widget via globalThis.postMessage. * The host only logs messages received via onMessage. @@ -25,13 +30,21 @@ type SandboxEntry = {key: string; label: string; origin: string} type LogEntry = {source: string; text: string; ts: number} let nextId = 0 +let nextIsolatedId = 0 const ORIGIN_ALPHA = 'alpha' const ORIGIN_BETA = 'beta' +const ISOLATED_PREFIX = 'isolated-' const COLOR_ALPHA = '#8232ff' const COLOR_BETA = '#e67e22' const COLOR_ISOLATED = '#6c757d' +/** + * Only "isolated-1" is permitted to send messages to alpha/beta. + * All other isolated origins will be denied. + */ +const PERMITTED_ISOLATED = `${ISOLATED_PREFIX}1` + /** Alpha uses a function-based TTL (4 seconds) */ const ALPHA_TTL = () => 4000 /** Beta and isolated use a static TTL (2 seconds) */ @@ -48,7 +61,14 @@ export default function App() { const addSandbox = useCallback((origin: string) => { const id = String(++nextId) - setSandboxes(prev => [...prev, {key: id, label: `#${id}`, origin}]) + const actualOrigin = + origin === ISOLATED_PREFIX + ? `${ISOLATED_PREFIX}${++nextIsolatedId}` + : origin + setSandboxes(prev => [ + ...prev, + {key: id, label: `#${id}`, origin: actualOrigin}, + ]) }, []) const removeSandbox = useCallback((key: string) => { @@ -59,14 +79,15 @@ export default function App() { 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 === '') + const isolated = sandboxes.filter(s => s.origin.startsWith(ISOLATED_PREFIX)) return ( Origin Pooling Demo - Same-origin sandboxes share a VM. Alpha: function-based TTL (4s). Beta: - static TTL (2s). + Same-origin sandboxes share a VM. Isolated sandboxes each get a unique + origin (own VM). Only isolated-1 can message alpha/beta — others get + AccessDeniedError. @@ -83,7 +104,7 @@ export default function App() {