react-native-browser-with-polyfill 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build.js ADDED
@@ -0,0 +1,11 @@
1
+ const esbuild = require('esbuild');
2
+
3
+ esbuild.buildSync({
4
+ entryPoints: ['index.js'],
5
+ bundle: true,
6
+ outfile: 'dist/index.js',
7
+ format: 'cjs',
8
+ platform: 'neutral',
9
+ jsx: 'transform', // Convert JSX <View> -> React.createElement('View')
10
+ external: ['react', 'react-native', 'react-native-webview'],
11
+ });
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __commonJS = (cb, mod) => function __require() {
3
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4
+ };
5
+
6
+ // src/createBrowser.jsx
7
+ var require_createBrowser = __commonJS({
8
+ "src/createBrowser.jsx"(exports2, module2) {
9
+ function createBrowser2({ Webview, React, ReactNative }) {
10
+ const { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } = React;
11
+ const { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, SafeAreaView, StatusBar, Platform } = ReactNative;
12
+ function useWebViewConsole() {
13
+ const [logs, setLogs] = useState([]);
14
+ const addLog = useCallback((type, message) => {
15
+ const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
16
+ setLogs((prev) => [...prev, { type, message, timestamp, id: Date.now() + Math.random() }]);
17
+ }, []);
18
+ const clearLogs = useCallback(() => {
19
+ setLogs([]);
20
+ }, []);
21
+ const handleWebViewMessage = useCallback((event) => {
22
+ try {
23
+ const data = JSON.parse(event.nativeEvent.data);
24
+ if (data && data.__console) {
25
+ addLog(data.type || "log", data.message || "");
26
+ }
27
+ } catch (_) {
28
+ }
29
+ }, [addLog]);
30
+ return { logs, addLog, clearLogs, handleWebViewMessage };
31
+ }
32
+ const CONSOLE_INTERCEPT = `(function(){
33
+ var oL=console.log,oW=console.warn,oE=console.error;
34
+ function s(t,a){try{var m=Array.prototype.slice.call(a).map(function(x){
35
+ if(typeof x==="object"){try{return JSON.stringify(x)}catch(e){return String(x)}}
36
+ return String(x)}).join(" ");
37
+ if(window.ReactNativeWebView)window.ReactNativeWebView.postMessage(
38
+ JSON.stringify({__console:true,type:t,message:m}))}catch(e){}}
39
+ console.log=function(){s("log",arguments);oL.apply(console,arguments)};
40
+ console.warn=function(){s("warn",arguments);oW.apply(console,arguments)};
41
+ console.error=function(){s("error",arguments);oE.apply(console,arguments)}})();`;
42
+ function DevBar({ webViewRef, logs, clearLogs, currentUrl }) {
43
+ const [expanded, setExpanded] = useState(false);
44
+ const [showConsole, setShowConsole] = useState(false);
45
+ const [urlInput, setUrlInput] = useState(currentUrl || "");
46
+ const consoleScrollRef = useRef(null);
47
+ useEffect(() => {
48
+ setUrlInput(currentUrl || "");
49
+ }, [currentUrl]);
50
+ const handleGo = () => {
51
+ let url = urlInput.trim();
52
+ if (url && !url.startsWith("http")) url = "https://" + url;
53
+ if (url && webViewRef.current) webViewRef.current.loadUrl(url);
54
+ };
55
+ if (!expanded) {
56
+ return /* @__PURE__ */ React.createElement(TouchableOpacity, { style: styles.toggleBtn, onPress: () => setExpanded(true), activeOpacity: 0.7 }, /* @__PURE__ */ React.createElement(Text, { style: styles.toggleText }, "\u{1F6E0}"));
57
+ }
58
+ return /* @__PURE__ */ React.createElement(View, { style: styles.devbarContainer }, /* @__PURE__ */ React.createElement(View, { style: styles.devbarHeader }, /* @__PURE__ */ React.createElement(Text, { style: styles.devbarTitle }, "\u{1F527} Dev Toolbar"), /* @__PURE__ */ React.createElement(TouchableOpacity, { onPress: () => setExpanded(false) }, /* @__PURE__ */ React.createElement(Text, { style: styles.devbarClose }, "\u2715"))), /* @__PURE__ */ React.createElement(View, { style: styles.urlRow }, /* @__PURE__ */ React.createElement(
59
+ TextInput,
60
+ {
61
+ style: styles.urlInput,
62
+ value: urlInput,
63
+ onChangeText: setUrlInput,
64
+ onSubmitEditing: handleGo,
65
+ placeholder: "Enter URL...",
66
+ placeholderTextColor: "#888",
67
+ autoCapitalize: "none",
68
+ autoCorrect: false,
69
+ keyboardType: "url",
70
+ returnKeyType: "go"
71
+ }
72
+ ), /* @__PURE__ */ React.createElement(TouchableOpacity, { style: styles.goBtn, onPress: handleGo }, /* @__PURE__ */ React.createElement(Text, { style: styles.goBtnText }, "Go"))), /* @__PURE__ */ React.createElement(Text, { style: styles.currentUrl, numberOfLines: 1 }, currentUrl), /* @__PURE__ */ React.createElement(View, { style: styles.btnRow }, [
73
+ ["\u2190 Back", () => webViewRef.current?.goBack()],
74
+ ["\u2192 Fwd", () => webViewRef.current?.goForward()],
75
+ ["\u21BB Reload", () => webViewRef.current?.reload()],
76
+ ["\u{1F5D1} Clear", () => webViewRef.current?.clearCache?.()],
77
+ ["\u{1F4CB} Console", () => setShowConsole((v) => !v)]
78
+ ].map(([label, onPress]) => /* @__PURE__ */ React.createElement(TouchableOpacity, { key: label, style: [styles.navBtn, label.includes("Console") && showConsole && styles.navBtnActive], onPress }, /* @__PURE__ */ React.createElement(Text, { style: styles.navBtnText }, label)))), showConsole && /* @__PURE__ */ React.createElement(View, { style: styles.consolePanel }, /* @__PURE__ */ React.createElement(View, { style: styles.consoleHeader }, /* @__PURE__ */ React.createElement(Text, { style: styles.consoleTitle }, "Console (", logs.length, ")"), /* @__PURE__ */ React.createElement(TouchableOpacity, { onPress: clearLogs }, /* @__PURE__ */ React.createElement(Text, { style: styles.consoleClear }, "Clear"))), /* @__PURE__ */ React.createElement(
79
+ ScrollView,
80
+ {
81
+ ref: consoleScrollRef,
82
+ style: styles.consoleScroll,
83
+ onContentSizeChange: () => consoleScrollRef.current?.scrollToEnd({ animated: true })
84
+ },
85
+ logs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { style: styles.consoleEmpty }, "No messages yet.") : logs.map((log, i) => /* @__PURE__ */ React.createElement(View, { key: i, style: styles.logEntry }, /* @__PURE__ */ React.createElement(Text, { style: [styles.logType, { color: log.type === "error" ? "#F44336" : log.type === "warn" ? "#FF9800" : "#4CAF50" }] }, "[", log.type, "]"), /* @__PURE__ */ React.createElement(Text, { style: styles.logTime }, log.timestamp), /* @__PURE__ */ React.createElement(Text, { style: styles.logMsg, selectable: true }, log.message)))
86
+ )));
87
+ }
88
+ const WebViewScreen = forwardRef(function WebViewScreen2(props, ref) {
89
+ const { initialUrl, onUrlChange, onMessage, polyfillScript, keyboardScript } = props;
90
+ const webViewRef = useRef(null);
91
+ const [currentUrl, setCurrentUrl] = useState(initialUrl || "https://browserleaks.com/js");
92
+ const injectedJS = useMemo(() => {
93
+ const parts = [CONSOLE_INTERCEPT];
94
+ if (polyfillScript) parts.push(polyfillScript);
95
+ if (keyboardScript) parts.push(keyboardScript);
96
+ return parts.join("\n");
97
+ }, [polyfillScript, keyboardScript]);
98
+ useImperativeHandle(ref, () => ({
99
+ goBack: () => webViewRef.current?.goBack(),
100
+ goForward: () => webViewRef.current?.goForward(),
101
+ reload: () => webViewRef.current?.reload(),
102
+ loadUrl: (url) => {
103
+ const newUrl = url.startsWith("http") ? url : "https://" + url;
104
+ setCurrentUrl(newUrl);
105
+ onUrlChange?.(newUrl);
106
+ webViewRef.current?.injectJavaScript("window.location.href=" + JSON.stringify(newUrl) + ";true;");
107
+ },
108
+ clearCache: () => {
109
+ webViewRef.current?.injectJavaScript("if(window.caches){caches.keys().then(function(n){for(var i=0;i<n.length;i++)caches.delete(n[i])})}true;");
110
+ }
111
+ }));
112
+ return /* @__PURE__ */ React.createElement(
113
+ Webview,
114
+ {
115
+ ref: webViewRef,
116
+ source: { uri: currentUrl },
117
+ injectedJavaScriptBeforeContentLoaded: injectedJS,
118
+ onMessage,
119
+ onNavigationStateChange: (nav) => {
120
+ setCurrentUrl(nav.url);
121
+ onUrlChange?.(nav.url);
122
+ },
123
+ javaScriptEnabled: true,
124
+ domStorageEnabled: true,
125
+ startInLoadingState: true,
126
+ allowsInlineMediaPlayback: true,
127
+ applicationNameForUserAgent: "ExpoBrowser/1.0",
128
+ style: { flex: 1 }
129
+ }
130
+ );
131
+ });
132
+ function Browser({ initialUrl, polyfillScript, keyboardScript }) {
133
+ const webViewRef = useRef(null);
134
+ const { logs, clearLogs, handleWebViewMessage } = useWebViewConsole();
135
+ const [currentUrl, setCurrentUrl] = useState(initialUrl || "https://browserleaks.com/js");
136
+ return /* @__PURE__ */ React.createElement(View, { style: { flex: 1, backgroundColor: "#000" } }, /* @__PURE__ */ React.createElement(StatusBar, { barStyle: "light-content", backgroundColor: "#000" }), /* @__PURE__ */ React.createElement(
137
+ WebViewScreen,
138
+ {
139
+ ref: webViewRef,
140
+ initialUrl,
141
+ onUrlChange: setCurrentUrl,
142
+ onMessage: handleWebViewMessage,
143
+ polyfillScript,
144
+ keyboardScript
145
+ }
146
+ ), /* @__PURE__ */ React.createElement(
147
+ DevBar,
148
+ {
149
+ webViewRef,
150
+ logs,
151
+ clearLogs,
152
+ currentUrl
153
+ }
154
+ ));
155
+ }
156
+ const styles = StyleSheet.create({
157
+ toggleBtn: { position: "absolute", bottom: 20, right: 20, width: 48, height: 48, borderRadius: 24, backgroundColor: "rgba(30,30,30,0.9)", justifyContent: "center", alignItems: "center", borderWidth: 2, borderColor: "#4CAF50", elevation: 8 },
158
+ toggleText: { fontSize: 22 },
159
+ devbarContainer: { position: "absolute", bottom: 0, left: 0, right: 0, backgroundColor: "rgba(30,30,30,0.95)", borderTopLeftRadius: 12, borderTopRightRadius: 12, paddingHorizontal: 12, paddingTop: 8, paddingBottom: 4, borderWidth: 1, borderColor: "#444", borderBottomWidth: 0 },
160
+ devbarHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 },
161
+ devbarTitle: { color: "#fff", fontSize: 14, fontWeight: "bold" },
162
+ devbarClose: { color: "#aaa", fontSize: 18, fontWeight: "bold", padding: 4 },
163
+ urlRow: { flexDirection: "row", alignItems: "center", marginBottom: 4 },
164
+ urlInput: { flex: 1, backgroundColor: "#333", color: "#fff", fontSize: 12, paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6, borderWidth: 1, borderColor: "#555" },
165
+ goBtn: { marginLeft: 6, backgroundColor: "#4CAF50", paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6 },
166
+ goBtnText: { color: "#fff", fontSize: 12, fontWeight: "bold" },
167
+ currentUrl: { color: "#888", fontSize: 10, marginBottom: 6 },
168
+ btnRow: { flexDirection: "row", flexWrap: "wrap", marginBottom: 6 },
169
+ navBtn: { backgroundColor: "#444", paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6, marginRight: 4, marginBottom: 4 },
170
+ navBtnActive: { backgroundColor: "#1976D2" },
171
+ navBtnText: { color: "#fff", fontSize: 11 },
172
+ consolePanel: { maxHeight: 200, marginTop: 4, borderTopWidth: 1, borderTopColor: "#444", paddingTop: 4 },
173
+ consoleHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 4 },
174
+ consoleTitle: { color: "#aaa", fontSize: 12, fontWeight: "bold" },
175
+ consoleClear: { color: "#F44336", fontSize: 12, fontWeight: "bold" },
176
+ consoleScroll: { maxHeight: 160, backgroundColor: "#1a1a1a", borderRadius: 4, padding: 4 },
177
+ consoleEmpty: { color: "#666", fontSize: 11, fontStyle: "italic", padding: 8 },
178
+ logEntry: { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", marginBottom: 2, paddingVertical: 2, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: "#333" },
179
+ logType: { fontSize: 10, fontWeight: "bold", marginRight: 4 },
180
+ logTime: { color: "#666", fontSize: 9, marginRight: 6, marginTop: 1 },
181
+ logMsg: { color: "#ddd", fontSize: 10, flex: 1 }
182
+ });
183
+ return { Browser, WebViewScreen, DevBar, useWebViewConsole };
184
+ }
185
+ module2.exports = createBrowser2;
186
+ module2.exports.default = createBrowser2;
187
+ }
188
+ });
189
+
190
+ // index.js
191
+ var createBrowser = require_createBrowser();
192
+ module.exports = createBrowser;
193
+ module.exports.default = createBrowser;
194
+ module.exports.createBrowser = createBrowser;
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "react-native-browser-with-polyfill",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Polyfill-injecting WebView browser for React Native / Expo. Uses dependency injection to avoid SDK version conflicts.",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/lequanghuylc/react-native-browser-with-polyfill.git"
9
9
  },
10
10
  "author": "Huy Le",
11
- "keywords": ["react-native", "expo", "webview", "polyfill", "browser", "ipados"],
11
+ "keywords": [
12
+ "react-native",
13
+ "expo",
14
+ "webview",
15
+ "polyfill",
16
+ "browser",
17
+ "ipados"
18
+ ],
12
19
  "license": "MIT",
13
20
  "peerDependencies": {
14
21
  "react": ">=16.8.0",
@@ -16,7 +23,14 @@
16
23
  "react-native-webview": ">=11.0.0"
17
24
  },
18
25
  "files": [
19
- "index.js",
20
- "src/"
21
- ]
26
+ "dist/",
27
+ "build.js"
28
+ ],
29
+ "scripts": {
30
+ "build": "node build.js",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "devDependencies": {
34
+ "esbuild": "^0.28.0"
35
+ }
22
36
  }
package/index.js DELETED
@@ -1,29 +0,0 @@
1
- /**
2
- * expo-browser — Polyfill-injecting WebView browser for React Native / Expo
3
- *
4
- * Usage:
5
- * import createBrowser from "expo-browser";
6
- * import Webview from "react-native-webview";
7
- * import * as React from "react";
8
- * import * as ReactNative from "react-native";
9
- *
10
- * // Optional: import polyfill scripts as strings
11
- * import polyfillScript from "expo-browser/src/polyfills/ipados15-polyfill";
12
- * import keyboardScript from "expo-browser/src/polyfills/dev-keyboard-bar";
13
- *
14
- * const { Browser } = createBrowser({ Webview, React, ReactNative });
15
- *
16
- * export default function App() {
17
- * return (
18
- * <Browser
19
- * initialUrl="https://browserleaks.com/js"
20
- * polyfillScript={polyfillScript}
21
- * keyboardScript={keyboardScript}
22
- * />
23
- * );
24
- * }
25
- */
26
- const createBrowser = require("./src/createBrowser");
27
- module.exports = createBrowser;
28
- module.exports.default = createBrowser;
29
- module.exports.createBrowser = createBrowser;
@@ -1,258 +0,0 @@
1
- /**
2
- * createBrowser — factory that accepts peer dependencies via DI
3
- *
4
- * Usage:
5
- * import createBrowser from "expo-browser";
6
- * import Webview from "react-native-webview";
7
- * import * as React from "react";
8
- * import * as ReactNative from "react-native";
9
- *
10
- * const { Browser, useWebViewConsole } = createBrowser({
11
- * Webview, React, ReactNative,
12
- * });
13
- */
14
-
15
- function createBrowser({ Webview, React, ReactNative }) {
16
- const { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } = React;
17
- const { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, SafeAreaView, StatusBar, Platform } = ReactNative;
18
-
19
- // ── Console Hook ───────────────────────────────────────────────
20
- function useWebViewConsole() {
21
- const [logs, setLogs] = useState([]);
22
-
23
- const addLog = useCallback((type, message) => {
24
- const timestamp = new Date().toLocaleTimeString();
25
- setLogs((prev) => [...prev, { type, message, timestamp, id: Date.now() + Math.random() }]);
26
- }, []);
27
-
28
- const clearLogs = useCallback(() => { setLogs([]); }, []);
29
-
30
- const handleWebViewMessage = useCallback((event) => {
31
- try {
32
- const data = JSON.parse(event.nativeEvent.data);
33
- if (data && data.__console) {
34
- addLog(data.type || "log", data.message || "");
35
- }
36
- } catch (_) {}
37
- }, [addLog]);
38
-
39
- return { logs, addLog, clearLogs, handleWebViewMessage };
40
- }
41
-
42
- // ── Console Intercept Script ───────────────────────────────────
43
- const CONSOLE_INTERCEPT = `(function(){
44
- var oL=console.log,oW=console.warn,oE=console.error;
45
- function s(t,a){try{var m=Array.prototype.slice.call(a).map(function(x){
46
- if(typeof x==="object"){try{return JSON.stringify(x)}catch(e){return String(x)}}
47
- return String(x)}).join(" ");
48
- if(window.ReactNativeWebView)window.ReactNativeWebView.postMessage(
49
- JSON.stringify({__console:true,type:t,message:m}))}catch(e){}}
50
- console.log=function(){s("log",arguments);oL.apply(console,arguments)};
51
- console.warn=function(){s("warn",arguments);oW.apply(console,arguments)};
52
- console.error=function(){s("error",arguments);oE.apply(console,arguments)}})();`;
53
-
54
- // ── DevBar Component ───────────────────────────────────────────
55
- function DevBar({ webViewRef, logs, clearLogs, currentUrl }) {
56
- const [expanded, setExpanded] = useState(false);
57
- const [showConsole, setShowConsole] = useState(false);
58
- const [urlInput, setUrlInput] = useState(currentUrl || "");
59
- const consoleScrollRef = useRef(null);
60
-
61
- useEffect(() => { setUrlInput(currentUrl || ""); }, [currentUrl]);
62
-
63
- const handleGo = () => {
64
- let url = urlInput.trim();
65
- if (url && !url.startsWith("http")) url = "https://" + url;
66
- if (url && webViewRef.current) webViewRef.current.loadUrl(url);
67
- };
68
-
69
- if (!expanded) {
70
- return (
71
- <TouchableOpacity style={styles.toggleBtn} onPress={() => setExpanded(true)} activeOpacity={0.7}>
72
- <Text style={styles.toggleText}>🛠</Text>
73
- </TouchableOpacity>
74
- );
75
- }
76
-
77
- return (
78
- <View style={styles.devbarContainer}>
79
- <View style={styles.devbarHeader}>
80
- <Text style={styles.devbarTitle}>🔧 Dev Toolbar</Text>
81
- <TouchableOpacity onPress={() => setExpanded(false)}>
82
- <Text style={styles.devbarClose}>✕</Text>
83
- </TouchableOpacity>
84
- </View>
85
-
86
- <View style={styles.urlRow}>
87
- <TextInput
88
- style={styles.urlInput}
89
- value={urlInput}
90
- onChangeText={setUrlInput}
91
- onSubmitEditing={handleGo}
92
- placeholder="Enter URL..."
93
- placeholderTextColor="#888"
94
- autoCapitalize="none"
95
- autoCorrect={false}
96
- keyboardType="url"
97
- returnKeyType="go"
98
- />
99
- <TouchableOpacity style={styles.goBtn} onPress={handleGo}>
100
- <Text style={styles.goBtnText}>Go</Text>
101
- </TouchableOpacity>
102
- </View>
103
-
104
- <Text style={styles.currentUrl} numberOfLines={1}>{currentUrl}</Text>
105
-
106
- <View style={styles.btnRow}>
107
- {[
108
- ["← Back", () => webViewRef.current?.goBack()],
109
- ["→ Fwd", () => webViewRef.current?.goForward()],
110
- ["↻ Reload", () => webViewRef.current?.reload()],
111
- ["🗑 Clear", () => webViewRef.current?.clearCache?.()],
112
- ["📋 Console", () => setShowConsole(v => !v)],
113
- ].map(([label, onPress]) => (
114
- <TouchableOpacity key={label} style={[styles.navBtn, label.includes("Console") && showConsole && styles.navBtnActive]} onPress={onPress}>
115
- <Text style={styles.navBtnText}>{label}</Text>
116
- </TouchableOpacity>
117
- ))}
118
- </View>
119
-
120
- {showConsole && (
121
- <View style={styles.consolePanel}>
122
- <View style={styles.consoleHeader}>
123
- <Text style={styles.consoleTitle}>Console ({logs.length})</Text>
124
- <TouchableOpacity onPress={clearLogs}>
125
- <Text style={styles.consoleClear}>Clear</Text>
126
- </TouchableOpacity>
127
- </View>
128
- <ScrollView ref={consoleScrollRef} style={styles.consoleScroll}
129
- onContentSizeChange={() => consoleScrollRef.current?.scrollToEnd({ animated: true })}>
130
- {logs.length === 0 ? (
131
- <Text style={styles.consoleEmpty}>No messages yet.</Text>
132
- ) : (
133
- logs.map((log, i) => (
134
- <View key={i} style={styles.logEntry}>
135
- <Text style={[styles.logType, { color: log.type === "error" ? "#F44336" : log.type === "warn" ? "#FF9800" : "#4CAF50" }]}>
136
- [{log.type}]
137
- </Text>
138
- <Text style={styles.logTime}>{log.timestamp}</Text>
139
- <Text style={styles.logMsg} selectable>{log.message}</Text>
140
- </View>
141
- ))
142
- )}
143
- </ScrollView>
144
- </View>
145
- )}
146
- </View>
147
- );
148
- }
149
-
150
- // ── WebViewScreen Component ────────────────────────────────────
151
- const WebViewScreen = forwardRef(function WebViewScreen(props, ref) {
152
- const { initialUrl, onUrlChange, onMessage, polyfillScript, keyboardScript } = props;
153
- const webViewRef = useRef(null);
154
- const [currentUrl, setCurrentUrl] = useState(initialUrl || "https://browserleaks.com/js");
155
-
156
- const injectedJS = useMemo(() => {
157
- const parts = [CONSOLE_INTERCEPT];
158
- if (polyfillScript) parts.push(polyfillScript);
159
- if (keyboardScript) parts.push(keyboardScript);
160
- return parts.join("\n");
161
- }, [polyfillScript, keyboardScript]);
162
-
163
- useImperativeHandle(ref, () => ({
164
- goBack: () => webViewRef.current?.goBack(),
165
- goForward: () => webViewRef.current?.goForward(),
166
- reload: () => webViewRef.current?.reload(),
167
- loadUrl: (url) => {
168
- const newUrl = url.startsWith("http") ? url : "https://" + url;
169
- setCurrentUrl(newUrl);
170
- onUrlChange?.(newUrl);
171
- webViewRef.current?.injectJavaScript("window.location.href=" + JSON.stringify(newUrl) + ";true;");
172
- },
173
- clearCache: () => {
174
- webViewRef.current?.injectJavaScript("if(window.caches){caches.keys().then(function(n){for(var i=0;i<n.length;i++)caches.delete(n[i])})}true;");
175
- },
176
- }));
177
-
178
- return (
179
- <Webview
180
- ref={webViewRef}
181
- source={{ uri: currentUrl }}
182
- injectedJavaScriptBeforeContentLoaded={injectedJS}
183
- onMessage={onMessage}
184
- onNavigationStateChange={(nav) => {
185
- setCurrentUrl(nav.url);
186
- onUrlChange?.(nav.url);
187
- }}
188
- javaScriptEnabled
189
- domStorageEnabled
190
- startInLoadingState
191
- allowsInlineMediaPlayback
192
- applicationNameForUserAgent="ExpoBrowser/1.0"
193
- style={{ flex: 1 }}
194
- />
195
- );
196
- });
197
-
198
- // ── Browser (main component) ───────────────────────────────────
199
- function Browser({ initialUrl, polyfillScript, keyboardScript }) {
200
- const webViewRef = useRef(null);
201
- const { logs, clearLogs, handleWebViewMessage } = useWebViewConsole();
202
- const [currentUrl, setCurrentUrl] = useState(initialUrl || "https://browserleaks.com/js");
203
-
204
- return (
205
- <View style={{ flex: 1, backgroundColor: "#000" }}>
206
- <StatusBar barStyle="light-content" backgroundColor="#000" />
207
- <WebViewScreen
208
- ref={webViewRef}
209
- initialUrl={initialUrl}
210
- onUrlChange={setCurrentUrl}
211
- onMessage={handleWebViewMessage}
212
- polyfillScript={polyfillScript}
213
- keyboardScript={keyboardScript}
214
- />
215
- <DevBar
216
- webViewRef={webViewRef}
217
- logs={logs}
218
- clearLogs={clearLogs}
219
- currentUrl={currentUrl}
220
- />
221
- </View>
222
- );
223
- }
224
-
225
- // ── Styles ─────────────────────────────────────────────────────
226
- const styles = StyleSheet.create({
227
- toggleBtn: { position: "absolute", bottom: 20, right: 20, width: 48, height: 48, borderRadius: 24, backgroundColor: "rgba(30,30,30,0.9)", justifyContent: "center", alignItems: "center", borderWidth: 2, borderColor: "#4CAF50", elevation: 8 },
228
- toggleText: { fontSize: 22 },
229
- devbarContainer: { position: "absolute", bottom: 0, left: 0, right: 0, backgroundColor: "rgba(30,30,30,0.95)", borderTopLeftRadius: 12, borderTopRightRadius: 12, paddingHorizontal: 12, paddingTop: 8, paddingBottom: 4, borderWidth: 1, borderColor: "#444", borderBottomWidth: 0 },
230
- devbarHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 },
231
- devbarTitle: { color: "#fff", fontSize: 14, fontWeight: "bold" },
232
- devbarClose: { color: "#aaa", fontSize: 18, fontWeight: "bold", padding: 4 },
233
- urlRow: { flexDirection: "row", alignItems: "center", marginBottom: 4 },
234
- urlInput: { flex: 1, backgroundColor: "#333", color: "#fff", fontSize: 12, paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6, borderWidth: 1, borderColor: "#555" },
235
- goBtn: { marginLeft: 6, backgroundColor: "#4CAF50", paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6 },
236
- goBtnText: { color: "#fff", fontSize: 12, fontWeight: "bold" },
237
- currentUrl: { color: "#888", fontSize: 10, marginBottom: 6 },
238
- btnRow: { flexDirection: "row", flexWrap: "wrap", marginBottom: 6 },
239
- navBtn: { backgroundColor: "#444", paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6, marginRight: 4, marginBottom: 4 },
240
- navBtnActive: { backgroundColor: "#1976D2" },
241
- navBtnText: { color: "#fff", fontSize: 11 },
242
- consolePanel: { maxHeight: 200, marginTop: 4, borderTopWidth: 1, borderTopColor: "#444", paddingTop: 4 },
243
- consoleHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 4 },
244
- consoleTitle: { color: "#aaa", fontSize: 12, fontWeight: "bold" },
245
- consoleClear: { color: "#F44336", fontSize: 12, fontWeight: "bold" },
246
- consoleScroll: { maxHeight: 160, backgroundColor: "#1a1a1a", borderRadius: 4, padding: 4 },
247
- consoleEmpty: { color: "#666", fontSize: 11, fontStyle: "italic", padding: 8 },
248
- logEntry: { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", marginBottom: 2, paddingVertical: 2, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: "#333" },
249
- logType: { fontSize: 10, fontWeight: "bold", marginRight: 4 },
250
- logTime: { color: "#666", fontSize: 9, marginRight: 6, marginTop: 1 },
251
- logMsg: { color: "#ddd", fontSize: 10, flex: 1 },
252
- });
253
-
254
- return { Browser, WebViewScreen, DevBar, useWebViewConsole };
255
- }
256
-
257
- module.exports = createBrowser;
258
- module.exports.default = createBrowser;