Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function HostApp() {
<View>
<Button onPress={sendMessageToSandbox} title="Send to Sandbox" />
<SandboxReactNativeView ref={sandboxRef}
moduleName={"ModuleName"} // The name of your JS module
componentName={"ModuleName"} // The name of your JS component
jsBundleSource={"sandbox"} // The JS bundle: file name or URL
onMessage={handleMessage}
onError={handleError}
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const SideBySideDemo: React.FC = () => {
<SandboxReactNativeView
style={styles.sandboxView}
jsBundleSource={'sandbox'}
moduleName={'CrashIfYouCanDemo'}
componentName={'CrashIfYouCanDemo'}
onError={error => {
const message = `Got ${error.isFatal ? 'fatal' : 'non-fatal'} error from sandbox`
console.warn(message, error)
Expand Down
4 changes: 2 additions & 2 deletions apps/fs-experiment/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ function App(): React.JSX.Element {
styles.sandbox,
{backgroundColor: theme.background, borderColor: theme.border},
]}
moduleName={'AppFS'}
componentName={'AppFS'}
jsBundleSource="sandbox-fs"
allowedTurboModules={['RNFSManager', 'FileReaderModule']}
onMessage={message => {
Expand Down Expand Up @@ -207,7 +207,7 @@ function App(): React.JSX.Element {
styles.sandbox,
{backgroundColor: theme.background, borderColor: theme.border},
]}
moduleName={'AppFileAccess'}
componentName={'AppFileAccess'}
allowedTurboModules={['FileAccess']}
jsBundleSource="sandbox-file-access"
onMessage={message => {
Expand Down
2 changes: 1 addition & 1 deletion apps/recursive/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function App({depth = 1}: AppProps): React.JSX.Element {
<SandboxReactNativeView
style={styles.recursiveSandbox}
jsBundleSource="index"
moduleName="App" // Recursively load App itself
componentName="App" // Recursively load App itself
initialProperties={{depth: depth + 1}}
onMessage={msg => console.log('message', msg)}
onError={e => console.error('error', e)}
Expand Down
2 changes: 1 addition & 1 deletion apps/recursive/RecursiveDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const RecursiveDisplay = ({depth}: Props) => {
{depth < MAX_DEPTH ? (
<SandboxReactNativeView
style={styles.flex}
moduleName="RecursiveDisplay"
componentName="RecursiveDisplay"
initialProperties={{depth: depth + 1}}
/>
) : (
Expand Down
2 changes: 1 addition & 1 deletion apps/side-by-side/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function SandboxDemoView({initialProperties}: {initialProperties: any}) {
<SandboxReactNativeView
ref={sandboxRef}
jsBundleSource={'sandbox'}
moduleName={'SandboxApp'}
componentName={'SandboxApp'}
style={styles.sandboxView}
initialProperties={initialProperties}
onMessage={msg => {
Expand Down
4 changes: 2 additions & 2 deletions docs/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ On API level you will deal with `SandboxReactNativeView` component in your JSX:
<SandboxReactNativeView
style={styles.sandboxContainer}
jsBundleSource={'my-plugin'}
moduleName={'PluginComponent'}
componentName={'PluginComponent'}
onMessage={(message) => {
console.log(`Message: ${message} received through safe API`)
}}
Expand Down Expand Up @@ -105,7 +105,7 @@ On the left, you'll see the "Main App" column running a component directly in th
<SandboxReactNativeView
style={styles.sandboxView}
jsBundleSource={'sandbox'}
moduleName={'CrashIfYouCanDemo'}
componentName={'CrashIfYouCanDemo'}
onError={error => {
Toast.show({...})
return false
Expand Down
4 changes: 2 additions & 2 deletions docs/presentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function App() {
...
<SandboxReactNativeView ref={sandbox1Ref}
jsBundleSource={"sandbox"}
moduleName={"SandboxApp"}
componentName={"SandboxApp"}
onMessage={onMessage}
onError={onError}
initialProperties={...} launchOptions={...} />
Expand Down Expand Up @@ -179,7 +179,7 @@ function App() {
...
<SandboxReactNativeView ref={sandbox1Ref}
jsBundleSource={"sandbox"}
moduleName={"SandboxApp"}
componentName={"SandboxApp"}
onMessage={onMessage}
onError={onError}
initialProperties={...} launchOptions={...} />
Expand Down
9 changes: 5 additions & 4 deletions packages/react-native-sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The package uses **autolinking** and supports the **React Native New Architectur
import SandboxReactNativeView from '@callstack/react-native-sandbox';

<SandboxReactNativeView
moduleName="YourSandboxModule"
componentName="YourSandboxModule"
jsBundleSource="sandbox" // bundle file name
onMessage={(data) => console.log('From sandbox:', data)}
onError={(error) => console.error('Sandbox error:', error)}
Expand All @@ -46,7 +46,8 @@ import SandboxReactNativeView from '@callstack/react-native-sandbox';

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `moduleName` | `string` | :ballot_box_with_check: | - | Name of the registered component to load from bundle specified in `jsBundleSource` |
| `componentName` | `string` | :ballot_box_with_check: | - | Name of the registered component to load from bundle specified in `jsBundleSource` |
| `moduleName` | `string` | :white_large_square: | - | **⚠️ Deprecated**: Use `componentName` instead. Will be removed in a future version. |
| `jsBundleSource` | `string` | :ballot_box_with_check: | - | Name on file storage or URL to the JavaScript bundle to load |
| `initialProperties` | `object` | :white_large_square: | `{}` | Initial props for the sandboxed app |
| `launchOptions` | `object` | :white_large_square: | `{}` | Launch configuration options |
Expand Down Expand Up @@ -157,7 +158,7 @@ useEffect(() => {

return (
<SandboxReactNativeView
moduleName="DynamicApp"
componentName="DynamicApp"
jsBundleSource={bundleUrl}
initialProperties={{
userId: currentUser.id,
Expand Down Expand Up @@ -262,7 +263,7 @@ Enable additional logging:
const isDebug = __DEV__;

<SandboxReactNativeView
moduleName="DebugApp"
componentName="DebugApp"
launchOptions={{ debug: isDebug }}
onError={(error) => {
if (isDebug) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &

bool shouldReload = false;

if (oldViewProps.moduleName != newViewProps.moduleName ||
if (oldViewProps.componentName != newViewProps.componentName ||
oldViewProps.jsBundleSource != newViewProps.jsBundleSource ||
oldViewProps.initialProperties != newViewProps.initialProperties ||
oldViewProps.launchOptions != newViewProps.launchOptions ||
Expand Down Expand Up @@ -93,10 +93,10 @@ - (void)loadReactNativeView
{
const auto &props = *std::static_pointer_cast<const SandboxReactNativeViewProps>(_props);

NSString *moduleName = RCTNSStringFromString(props.moduleName);
NSString *componentName = RCTNSStringFromString(props.componentName);
NSString *jsBundleSource = RCTNSStringFromString(props.jsBundleSource);

if (moduleName.length == 0 || jsBundleSource.length == 0) {
if (componentName.length == 0 || jsBundleSource.length == 0) {
return;
}

Expand Down Expand Up @@ -131,7 +131,7 @@ - (void)loadReactNativeView
delegate.hasOnErrorHandler = props.hasOnErrorHandler;

RCTReactNativeFactory *factory = [[RCTReactNativeFactory alloc] initWithDelegate:delegate];
UIView *rnView = [factory.rootViewFactory viewWithModuleName:moduleName
UIView *rnView = [factory.rootViewFactory viewWithModuleName:componentName
initialProperties:initialProperties
launchOptions:launchOptions];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export interface MessageEvent {
*/
export interface NativeProps extends ViewProps {
/**
* The name of the React Native module to load in the sandbox.
* The name of the React Native component to load in the sandbox.
* Must match with the registered component name from the JavaScript bundle
*/
moduleName: string
componentName: string

/** Name on file storage or URL to the JavaScript bundle to load */
jsBundleSource: string
Expand Down
177 changes: 111 additions & 66 deletions packages/react-native-sandbox/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,17 @@ type GenericProps = {
*/
export type SandboxReactNativeViewProps = ViewProps & {
/**
* The name of the React Native component to load in the sandbox.
* This should match the component name registered in your JavaScript bundle.
*/
componentName: string

/**
* @deprecated Use componentName instead. Will be removed in a future version.
* The name of the React Native module to load in the sandbox.
* This should match the component name registered in your JavaScript bundle.
*/
moduleName: string
moduleName?: string

/**
* Optional path or URL to the JavaScript bundle to load.
Expand Down Expand Up @@ -158,7 +165,7 @@ export interface SandboxReactNativeViewRef {
* return (
* <SandboxReactNativeView
* ref={sandboxRef}
* moduleName="MyDynamicApp"
* componentName="MyDynamicApp"
* jsBundleSource="https://example.com/app.bundle.js"
* initialProperties={{ userId: '123', theme: 'dark' }}
* onMessage={handleMessage}
Expand All @@ -173,7 +180,7 @@ export interface SandboxReactNativeViewRef {
* Advanced usage with custom TurboModules:
* ```tsx
* <SandboxReactNativeView
* moduleName="SecureApp"
* componentName="SecureApp"
* allowedTurboModules={['MyCustomModule', 'CryptoModule']}
* launchOptions={{ debugMode: false, securityLevel: 'high' }}
* onMessage={(data) => {
Expand All @@ -188,78 +195,116 @@ export interface SandboxReactNativeViewRef {
const SandboxReactNativeView = forwardRef<
SandboxReactNativeViewRef,
SandboxReactNativeViewProps
>(({onMessage, onError, allowedTurboModules, style, ...rest}, ref) => {
const nativeRef =
useRef<React.ElementRef<NativeSandboxReactNativeViewComponentType> | null>(
null
>(
(
{
onMessage,
onError,
allowedTurboModules,
style,
componentName,
moduleName,
...rest
},
ref
) => {
const nativeRef =
useRef<React.ElementRef<NativeSandboxReactNativeViewComponentType> | null>(
null
)

const postMessage = useCallback((message: any) => {
if (nativeRef.current) {
Commands.postMessage(nativeRef.current, JSON.stringify(message))
}
}, [])

const _onError = useCallback(
(e: NativeSyntheticEvent<ErrorEvent>) => {
// @ts-ignore
onError?.(e.nativeEvent as ErrorEvent)
},
[onError]
)

const _onMessage = useCallback(
(e: NativeSyntheticEvent<MessageEvent>) => {
// @ts-ignore
onMessage?.(e.nativeEvent.data)
},
[onMessage]
)

const postMessage = useCallback((message: any) => {
if (nativeRef.current) {
Commands.postMessage(nativeRef.current, JSON.stringify(message))
}
}, [])
useImperativeHandle(
ref,
() => ({
postMessage,
}),
[postMessage]
)

const _onError = useCallback(
(e: NativeSyntheticEvent<ErrorEvent>) => {
// @ts-ignore
onError?.(e.nativeEvent as ErrorEvent)
},
[onError]
)
const _renderOverlay = useCallback(() => {
// TODO implement some loading/error/handling screen
return null
}, [])

const _onMessage = useCallback(
(e: NativeSyntheticEvent<MessageEvent>) => {
// @ts-ignore
onMessage?.(e.nativeEvent.data)
},
[onMessage]
)
const _style: StyleProp<ViewStyle> = useMemo(
() => ({
...StyleSheet.absoluteFillObject,
}),
[]
)

useImperativeHandle(
ref,
() => ({
postMessage,
}),
[postMessage]
)
const _allowedTurboModules = [
...new Set([
...(allowedTurboModules ?? []),
...SANDBOX_TURBOMODULES_WHITELIST,
]),
]

const _renderOverlay = useCallback(() => {
// TODO implement some loading/error/handling screen
return null
}, [])
// Handle backward compatibility for moduleName -> componentName
const resolvedComponentName = useMemo(() => {
if (componentName && moduleName) {
console.warn(
'Both componentName and moduleName are provided. Using componentName and ignoring moduleName. ' +
'Please migrate to using componentName only as moduleName is deprecated.'
)
return componentName
}

const _style: StyleProp<ViewStyle> = useMemo(
() => ({
...StyleSheet.absoluteFillObject,
}),
[]
)
if (moduleName) {
console.warn(
'moduleName is deprecated. Please use componentName instead. moduleName will be removed in a future version.'
)
return moduleName
}

const _allowedTurboModules = [
...new Set([
...(allowedTurboModules ?? []),
...SANDBOX_TURBOMODULES_WHITELIST,
]),
]
if (!componentName) {
throw new Error('Either componentName or moduleName must be provided')
}

return (
<View style={style}>
<NativeSandboxReactNativeView
// @ts-ignore
ref={nativeRef}
hasOnMessageHandler={!!onMessage}
hasOnErrorHandler={!!onError}
onError={onError ? _onError : undefined}
onMessage={onMessage ? _onMessage : undefined}
allowedTurboModules={_allowedTurboModules}
style={_style}
{...rest}
/>
{_renderOverlay()}
</View>
)
})
return componentName
}, [componentName, moduleName])

return (
<View style={style}>
<NativeSandboxReactNativeView
// @ts-ignore
ref={nativeRef}
componentName={resolvedComponentName}
hasOnMessageHandler={!!onMessage}
hasOnErrorHandler={!!onError}
onError={onError ? _onError : undefined}
onMessage={onMessage ? _onMessage : undefined}
allowedTurboModules={_allowedTurboModules}
style={_style}
{...rest}
/>
{_renderOverlay()}
</View>
)
}
)

SandboxReactNativeView.displayName = 'SandboxReactNativeView'
export default SandboxReactNativeView
Loading