mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-08-03 20:26:04 +00:00
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import React, {
|
|
forwardRef,
|
|
useCallback,
|
|
useImperativeHandle,
|
|
useRef,
|
|
} from 'react';
|
|
import { Linking, Platform } from 'react-native';
|
|
import { WebView, type WebViewNavigation } from 'react-native-webview';
|
|
import type {
|
|
WebViewErrorEvent,
|
|
WebViewMessageEvent,
|
|
ShouldStartLoadRequest,
|
|
} from 'react-native-webview/lib/WebViewTypes';
|
|
import { handleBridgeMessage } from '../services/bridge';
|
|
import { buildInjectedJavaScript } from '../injection/inject';
|
|
import { CONFIG } from '../config';
|
|
|
|
export interface WebViewBrowserRef {
|
|
goBack: () => void;
|
|
goForward: () => void;
|
|
reload: () => void;
|
|
loadUrl: (url: string) => void;
|
|
injectJavaScript: (script: string) => void;
|
|
}
|
|
|
|
interface WebViewBrowserProps {
|
|
url: string;
|
|
onNavigationStateChange?: (state: WebViewNavigation) => void;
|
|
onError?: (error: string) => void;
|
|
onLoadEnd?: () => void;
|
|
}
|
|
|
|
const injectedJS = buildInjectedJavaScript();
|
|
|
|
const WebViewBrowser = forwardRef<WebViewBrowserRef, WebViewBrowserProps>(
|
|
({ url, onNavigationStateChange, onError, onLoadEnd }, ref) => {
|
|
const webViewRef = useRef<WebView>(null);
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
goBack: () => webViewRef.current?.goBack(),
|
|
goForward: () => webViewRef.current?.goForward(),
|
|
reload: () => webViewRef.current?.reload(),
|
|
loadUrl: (newUrl: string) => {
|
|
webViewRef.current?.injectJavaScript(
|
|
`window.location.href = ${JSON.stringify(newUrl)}; true;`,
|
|
);
|
|
},
|
|
injectJavaScript: (script: string) => {
|
|
webViewRef.current?.injectJavaScript(script);
|
|
},
|
|
}));
|
|
|
|
const onMessage = useCallback((event: WebViewMessageEvent) => {
|
|
handleBridgeMessage(event.nativeEvent.data, webViewRef);
|
|
}, []);
|
|
|
|
const onHttpError = useCallback(
|
|
(event: any) => {
|
|
onError?.(
|
|
`HTTP ${event.nativeEvent.statusCode}: ${event.nativeEvent.url}`,
|
|
);
|
|
},
|
|
[onError],
|
|
);
|
|
|
|
const onShouldStartLoadWithRequest = useCallback(
|
|
(request: ShouldStartLoadRequest) => {
|
|
const { url, navigationType } = request;
|
|
if (
|
|
url.startsWith('https://') ||
|
|
url.startsWith('http://') ||
|
|
url.startsWith('about:') ||
|
|
url.startsWith('blob:')
|
|
) {
|
|
return true;
|
|
}
|
|
// Ouvre uniquement les deep links déclenchés par un vrai clic utilisateur.
|
|
// Les redirections automatiques (pubs, iframes) sont silencieusement bloquées.
|
|
if (navigationType === 'click') {
|
|
Linking.openURL(url).catch(() => {});
|
|
}
|
|
return false;
|
|
},
|
|
[],
|
|
);
|
|
|
|
const onWebViewError = useCallback(
|
|
(event: WebViewErrorEvent) => {
|
|
onError?.(event.nativeEvent.description);
|
|
},
|
|
[onError],
|
|
);
|
|
|
|
const userAgent =
|
|
Platform.OS === 'ios' ? CONFIG.USER_AGENT_IOS : CONFIG.USER_AGENT;
|
|
|
|
return (
|
|
<WebView
|
|
ref={webViewRef}
|
|
source={{ uri: url }}
|
|
style={{ flex: 1, backgroundColor: '#0a0a0a' }}
|
|
// Injection du bridge + userscript avant le chargement
|
|
injectedJavaScriptBeforeContentLoaded={injectedJS}
|
|
// Réinjection après chaque navigation
|
|
injectedJavaScriptBeforeContentLoadedForMainFrameOnly={true}
|
|
// Bridge messages
|
|
onMessage={onMessage}
|
|
// Navigation
|
|
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
|
onNavigationStateChange={onNavigationStateChange}
|
|
// Errors
|
|
onError={onWebViewError}
|
|
onHttpError={onHttpError}
|
|
onLoadEnd={onLoadEnd}
|
|
// Config
|
|
userAgent={userAgent}
|
|
javaScriptEnabled={true}
|
|
domStorageEnabled={true}
|
|
mediaPlaybackRequiresUserAction={false}
|
|
allowsInlineMediaPlayback={true}
|
|
allowsFullscreenVideo={true}
|
|
allowsBackForwardNavigationGestures={true}
|
|
// Sécurité
|
|
originWhitelist={['https://*', 'http://*', 'about:*', 'blob:*']}
|
|
mixedContentMode="compatibility"
|
|
// Cache
|
|
cacheEnabled={true}
|
|
// Désactive le zoom pour un rendu app-like
|
|
scalesPageToFit={true}
|
|
// Android
|
|
overScrollMode="never"
|
|
thirdPartyCookiesEnabled={true}
|
|
// iOS
|
|
sharedCookiesEnabled={true}
|
|
contentMode="mobile"
|
|
/>
|
|
);
|
|
},
|
|
);
|
|
|
|
WebViewBrowser.displayName = 'WebViewBrowser';
|
|
export default WebViewBrowser;
|