Skip to content

Commit 5aaae32

Browse files
example: add fs-experiment to test security boundaries
1 parent f448adf commit 5aaae32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+4713
-0
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
import React, { useState } from 'react';
2+
import {
3+
SafeAreaView,
4+
ScrollView,
5+
StatusBar,
6+
StyleSheet,
7+
Text,
8+
TextInput,
9+
TouchableOpacity,
10+
View,
11+
useColorScheme,
12+
Platform,
13+
} from 'react-native';
14+
15+
import SandboxReactNativeView from 'react-native-multinstance';
16+
17+
// File system imports
18+
import RNFS from 'react-native-fs';
19+
20+
const SHARED_FILE_PATH = `${RNFS.DocumentDirectoryPath}/shared_test_file.txt`;
21+
22+
function App(): React.JSX.Element {
23+
const isDarkMode = useColorScheme() === 'dark';
24+
const [textContent, setTextContent] = useState<string>('');
25+
const [status, setStatus] = useState<string>('Ready');
26+
27+
const theme = {
28+
background: isDarkMode ? '#000000' : '#ffffff',
29+
surface: isDarkMode ? '#1c1c1e' : '#f2f2f7',
30+
primary: isDarkMode ? '#007aff' : '#007aff',
31+
secondary: isDarkMode ? '#34c759' : '#34c759',
32+
text: isDarkMode ? '#ffffff' : '#000000',
33+
textSecondary: isDarkMode ? '#8e8e93' : '#3c3c43',
34+
border: isDarkMode ? '#38383a' : '#c6c6c8',
35+
success: '#34c759',
36+
error: '#ff3b30',
37+
};
38+
39+
const writeFile = async () => {
40+
try {
41+
setStatus('Writing file...');
42+
await RNFS.writeFile(SHARED_FILE_PATH, textContent, 'utf8');
43+
setStatus(`Successfully wrote: "${textContent}"`);
44+
} catch (error) {
45+
setStatus(`Write error: ${(error as Error).message}`);
46+
}
47+
};
48+
49+
const readFile = async () => {
50+
try {
51+
setStatus('Reading file...');
52+
const content = await RNFS.readFile(SHARED_FILE_PATH, 'utf8');
53+
setTextContent(content);
54+
setStatus(`Successfully read: "${content}"`);
55+
} catch (error) {
56+
setStatus(`Read error: ${(error as Error).message}`);
57+
}
58+
};
59+
60+
const getStatusStyle = () => {
61+
if (status.includes('error')) {
62+
return { color: theme.error };
63+
}
64+
if (status.includes('Successfully')) {
65+
return { color: theme.success };
66+
}
67+
return { color: theme.textSecondary };
68+
};
69+
70+
return (
71+
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
72+
<StatusBar
73+
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
74+
backgroundColor={theme.background}
75+
/>
76+
<ScrollView
77+
contentInsetAdjustmentBehavior="automatic"
78+
style={{ backgroundColor: theme.background }}
79+
showsVerticalScrollIndicator={false}>
80+
81+
{/* Header */}
82+
<View style={[styles.header, { backgroundColor: theme.surface }]}>
83+
<Text style={[styles.headerTitle, { color: theme.text }]}>
84+
File System Sandbox Demo
85+
</Text>
86+
<Text style={[styles.headerSubtitle, { color: theme.textSecondary }]}>
87+
Multi-instance file system access testing
88+
</Text>
89+
</View>
90+
91+
<View style={styles.content}>
92+
{/* Host Application Section */}
93+
<View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
94+
<View style={styles.cardHeader}>
95+
<Text style={[styles.cardTitle, { color: theme.text }]}>
96+
Host Application
97+
</Text>
98+
<View style={[styles.badge, { backgroundColor: theme.primary }]}>
99+
<Text style={styles.badgeText}>Primary</Text>
100+
</View>
101+
</View>
102+
103+
<TextInput
104+
style={[styles.textInput, {
105+
color: theme.text,
106+
backgroundColor: theme.background,
107+
borderColor: theme.border,
108+
}]}
109+
value={textContent}
110+
onChangeText={setTextContent}
111+
placeholder="Enter text to write to file..."
112+
placeholderTextColor={theme.textSecondary}
113+
multiline
114+
/>
115+
116+
<View style={styles.buttonGroup}>
117+
<TouchableOpacity
118+
style={[styles.button, styles.primaryButton, { backgroundColor: theme.primary }]}
119+
onPress={writeFile}>
120+
<Text style={styles.buttonText}>Write File</Text>
121+
</TouchableOpacity>
122+
123+
<TouchableOpacity
124+
style={[styles.button, styles.secondaryButton, { backgroundColor: theme.secondary }]}
125+
onPress={readFile}>
126+
<Text style={styles.buttonText}>Read File</Text>
127+
</TouchableOpacity>
128+
</View>
129+
130+
<View style={[styles.statusContainer, { backgroundColor: theme.background }]}>
131+
<Text style={[styles.statusLabel, { color: theme.textSecondary }]}>Status:</Text>
132+
<Text style={[styles.statusText, getStatusStyle()]}>
133+
{status}
134+
</Text>
135+
</View>
136+
137+
<Text style={[styles.pathText, { color: theme.textSecondary }]}>
138+
{SHARED_FILE_PATH}
139+
</Text>
140+
</View>
141+
142+
{/* Sandbox Sections */}
143+
<View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
144+
<View style={styles.cardHeader}>
145+
<Text style={[styles.cardTitle, { color: theme.text }]}>
146+
Sandbox: react-native-fs
147+
</Text>
148+
<View style={[styles.badge, styles.sandboxBadge]}>
149+
<Text style={styles.badgeText}>Sandbox</Text>
150+
</View>
151+
</View>
152+
<SandboxReactNativeView
153+
style={[styles.sandbox, { backgroundColor: theme.background, borderColor: theme.border }]}
154+
moduleName={'AppFS'}
155+
jsBundleSource="sandbox-fs"
156+
onMessage={message => {
157+
console.log('Host received message from sandbox:', message);
158+
}}
159+
onError={error => {
160+
console.log('Host received error from sandbox:', error);
161+
}}
162+
/>
163+
</View>
164+
165+
<View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
166+
<View style={styles.cardHeader}>
167+
<Text style={[styles.cardTitle, { color: theme.text }]}>
168+
Sandbox: react-native-file-access
169+
</Text>
170+
<View style={[styles.badge, styles.sandboxBadge]}>
171+
<Text style={styles.badgeText}>Sandbox</Text>
172+
</View>
173+
</View>
174+
<SandboxReactNativeView
175+
style={[styles.sandbox, { backgroundColor: theme.background, borderColor: theme.border }]}
176+
moduleName={'AppFileAccess'}
177+
jsBundleSource="sandbox-file-access"
178+
onMessage={message => {
179+
console.log('Host received message from sandbox:', message);
180+
}}
181+
onError={error => {
182+
console.log('Host received error from sandbox:', error);
183+
}}
184+
/>
185+
</View>
186+
</View>
187+
</ScrollView>
188+
</SafeAreaView>
189+
);
190+
}
191+
192+
const styles = StyleSheet.create({
193+
container: {
194+
flex: 1,
195+
},
196+
header: {
197+
paddingHorizontal: 20,
198+
paddingVertical: 24,
199+
...Platform.select({
200+
ios: {
201+
shadowColor: '#000',
202+
shadowOffset: { width: 0, height: 1 },
203+
shadowOpacity: 0.1,
204+
shadowRadius: 4,
205+
},
206+
android: {
207+
elevation: 2,
208+
},
209+
}),
210+
},
211+
headerTitle: {
212+
fontSize: 28,
213+
fontWeight: '700',
214+
letterSpacing: -0.5,
215+
},
216+
headerSubtitle: {
217+
fontSize: 16,
218+
marginTop: 4,
219+
fontWeight: '400',
220+
},
221+
content: {
222+
padding: 16,
223+
},
224+
card: {
225+
marginBottom: 20,
226+
borderRadius: 12,
227+
padding: 20,
228+
borderWidth: 1,
229+
...Platform.select({
230+
ios: {
231+
shadowColor: '#000',
232+
shadowOffset: { width: 0, height: 2 },
233+
shadowOpacity: 0.1,
234+
shadowRadius: 8,
235+
},
236+
android: {
237+
elevation: 3,
238+
},
239+
}),
240+
},
241+
cardHeader: {
242+
flexDirection: 'row',
243+
justifyContent: 'space-between',
244+
alignItems: 'center',
245+
marginBottom: 16,
246+
},
247+
cardTitle: {
248+
fontSize: 18,
249+
fontWeight: '600',
250+
flex: 1,
251+
},
252+
badge: {
253+
paddingHorizontal: 8,
254+
paddingVertical: 4,
255+
borderRadius: 6,
256+
},
257+
badgeText: {
258+
color: '#ffffff',
259+
fontSize: 12,
260+
fontWeight: '600',
261+
textTransform: 'uppercase',
262+
},
263+
sandboxBadge: {
264+
backgroundColor: '#ff6b35',
265+
},
266+
textInput: {
267+
borderWidth: 1,
268+
borderRadius: 8,
269+
padding: 16,
270+
marginBottom: 16,
271+
minHeight: 100,
272+
textAlignVertical: 'top',
273+
fontSize: 16,
274+
lineHeight: 22,
275+
},
276+
buttonGroup: {
277+
flexDirection: 'row',
278+
gap: 12,
279+
marginBottom: 16,
280+
},
281+
button: {
282+
flex: 1,
283+
paddingVertical: 14,
284+
paddingHorizontal: 20,
285+
borderRadius: 8,
286+
alignItems: 'center',
287+
justifyContent: 'center',
288+
},
289+
primaryButton: {
290+
// backgroundColor set dynamically
291+
},
292+
secondaryButton: {
293+
// backgroundColor set dynamically
294+
},
295+
buttonText: {
296+
color: '#ffffff',
297+
fontWeight: '600',
298+
fontSize: 16,
299+
},
300+
statusContainer: {
301+
padding: 12,
302+
borderRadius: 8,
303+
marginBottom: 12,
304+
},
305+
statusLabel: {
306+
fontSize: 14,
307+
fontWeight: '500',
308+
marginBottom: 4,
309+
},
310+
statusText: {
311+
fontSize: 14,
312+
fontStyle: 'italic',
313+
lineHeight: 20,
314+
},
315+
pathText: {
316+
fontSize: 12,
317+
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
318+
opacity: 0.8,
319+
lineHeight: 16,
320+
},
321+
sandbox: {
322+
height: 320,
323+
borderWidth: 1,
324+
borderRadius: 8,
325+
},
326+
});
327+
328+
export default App;

0 commit comments

Comments
 (0)