import * as React from 'react'; import { WebView } from 'react-native-webview'; import { Linking,BackHandler, View, Text, StyleSheet,ActivityIndicator } from 'react-native'; import UriBuilderService from '../services/UriBuilderService'; import { UserService } from '../services/UserService'; import { RateLimitService } from '../services/RatelimitService'; import type { ConnectivityProvider } from '../services/ConnectivityService'; import { getDefaultNetInfoProvider } from '../services/ConnectivityService'; import { CommandDispatcher } from '../controller/CommandDispatcher'; // Props and ref handle types exposed for external use (minimal change, fully backward compatible) type Props = { adunitId: string; connectivityProvider?: ConnectivityProvider; }; export type MCOfferwallViewHandle = { /** Triggers the same logic as the Android hardware back button; * returns true if handled internally, false if the host app should handle it */ requestBack: () => boolean; }; // to allow external access via ref export const MCOfferwallView = React.forwardRef(function MCOfferwallView( { adunitId, connectivityProvider, // external connectivity provider can be injected }: Props, ref ) { //about:blank url will break compilation on ios const [currentUrl, setCurrentUrl] = React.useState(''); // State for the current URL const [isConnected, setIsConnected] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true); // MODIFIED: darkMode true = Black Loading screen;false/null = White loading screen // true = dark, false/null = light const [darkMode, setDarkMode] = React.useState(null); // SDK container/loading/WebView background const backgroundColor = darkMode === true ? '#17171C' : '#FFFFFF'; // loading indicator color const loaderColor = darkMode === true ? '#EAEAEA' : '#000000'; const mychipsDomain = 'mychips.io'; const webViewRef = React.useRef(null); const currentUrlRef = React.useRef(currentUrl); // Ref to keep track of the current URL const commandDispatcherRef = React.useRef(null); if (!commandDispatcherRef.current) { commandDispatcherRef.current = new CommandDispatcher((script: string) => { webViewRef.current?.injectJavaScript(script); }); } const getUserData = async () => { const userService = new UserService(); const userId = await userService.getOrCreateId(); const gaid = await userService.getGaid(); const idfa = await userService.getIdfa(); const age = await userService.getAge(); const gender = await userService.getGender(); const totalVirtualCurrency = await userService.getCurrentTotalCurrency(); const darkMode = await userService.getDarkMode(); const aff_sub1 = await userService.getAffSub1(); const aff_sub2 = await userService.getAffSub2(); const aff_sub3 = await userService.getAffSub3(); const aff_sub4 = await userService.getAffSub4(); const aff_sub5 = await userService.getAffSub5(); return { userId, gaid, idfa, age, gender, totalVirtualCurrency, darkMode, aff_sub1, aff_sub2, aff_sub3, aff_sub4, aff_sub5 }; }; const buildUrl = async () => { const userData = await getUserData(); const uriBuilderService = new UriBuilderService(); return uriBuilderService.buildOfferwallUrl( adunitId, userData.userId, userData.gaid, userData.idfa, userData.gender, userData.age, userData.totalVirtualCurrency, userData.darkMode, userData.aff_sub1, userData.aff_sub2, userData.aff_sub3, userData.aff_sub4, userData.aff_sub5 ); }; const fetchUserDataAndBuildUrl = async () => { try { const url = await buildUrl(); setCurrentUrl(url); // keep ref synced currentUrlRef.current = url; } catch (error) { console.error('Failed to build offerwall URL', error); } }; const parseMychipsUri = (url: string): { method: string; params: string | null } | null => { try { if (!url.startsWith('mychips://')) { return null; } const withoutScheme = url.replace('mychips://', ''); const queryIndex = withoutScheme.indexOf('?'); const method = queryIndex >= 0 ? withoutScheme.substring(0, queryIndex) : withoutScheme; const queryString = queryIndex >= 0 ? withoutScheme.substring(queryIndex + 1) : ''; let params: string | null = null; if (queryString) { const pairs = queryString.split('&'); for (const pair of pairs) { const [rawKey, rawValue = ''] = pair.split('='); const key = decodeURIComponent(rawKey || ''); if (key === 'params') { params = decodeURIComponent(rawValue); break; } } } return { method, params }; } catch (error) { console.error('Failed to parse mychips uri', error); return null; } }; const handleMychipsUri = (url: string): void => { const parsed = parseMychipsUri(url); if (!parsed) { return; } void commandDispatcherRef.current?.dispatch(parsed.method, parsed.params); }; ///get the current navigation url and keep it updated const onNavigationStateChange = (navState: any) => { const { url } = navState; if (url !== currentUrlRef.current) { setCurrentUrl(url); currentUrlRef.current = url; console.log('onNavigationStateChange:' + url); } }; ///handle logic for specific link const onShouldStartLoadWithRequest = (request: any) => { const { url } = request; console.log("onShouldStartLoadWithRequest:" + url); if (url.includes('blank') || url.includes('file:') ) { return false; } if (url.includes('/redirect')) { Linking.openURL(url).catch((err) => console.error("Couldn't load page", err)); return false; // Prevent the WebView from loading the URL } if (url.startsWith('mychips://')) { handleMychipsUri(url); return false; } return true; // Allow the WebView to load the URL }; const onLoadStart = () => { //console.log("WebView Load Started"); }; const onLoad = () => { //console.log("WebView Loaded"); }; const onLoadEnd = () => { if(currentUrl=='' || currentUrl=='about:blank'){ fetchUserDataAndBuildUrl(); console.log("onLoadEnd: fetchUserDataAndBuildUrl"); } console.log("WebView Load Ended" ); if(isLoading && currentUrl.includes(mychipsDomain)){ setIsLoading(false); } }; const onError = (error:any) => { console.error("WebView Error: ", error); }; //handle navigation logic const onBackPress = () => { const url = currentUrlRef.current; // Use if (url.includes('page=home') || !url.includes('page=')) { return false; // Allow default back button behavior if URL contains 'page=home' } else { webViewRef.current?.goBack(); return true; // Prevent default back button behavior } }; // Expose methods via ref (same as hardware back button, no behavior change) React.useImperativeHandle( ref, () => ({ requestBack: onBackPress, }), [onBackPress] ); React.useEffect(() => { RateLimitService.enableRequest(); // load dark mode first when component starts // true = dark, false/null = light const loadTheme = async () => { const userService = new UserService(); const darkMode = await userService.getDarkMode(); setDarkMode(darkMode); }; loadTheme(); setTimeout(() => { if(currentUrlRef.current == 'about:blank' || currentUrlRef.current == ''){ fetchUserDataAndBuildUrl(); console.log("01"); } }, 1000); // Delay to ensure WebView is ready setTimeout(() => { if(isLoading) setIsLoading(false); },5000) /** * Connectivity strategy (explicit & backward compatible) * 1) Use externally injected connectivityProvider if provided. * 2) Otherwise, try dynamic NetInfo (if installed in the host app). * 3) If neither is available, THROW an error (publisher must choose one). */ const provider = connectivityProvider ?? getDefaultNetInfoProvider(); let unsubscribe: (() => void) | undefined; // explicit error if no provider at all if (!provider) { throw new Error( '[MyChips SDK] No connectivity provider available. Please either install "@react-native-community/netinfo" or provide a custom connectivityProvider.' ); } (async () => { try { if (provider.getCurrentStatus) { const s = await provider.getCurrentStatus(); if (typeof s === 'boolean') setIsConnected(s); } } catch { // ignore one-off fetch errors; live subscription below will update the state } })(); unsubscribe = provider.subscribe((online) => { setIsConnected(Boolean(online)); }); const backHandlerSubscription = BackHandler.addEventListener('hardwareBackPress', onBackPress); return () => { backHandlerSubscription.remove(); if (typeof unsubscribe === 'function') unsubscribe(); }; }, []); return ( // Parent background fixes iOS top extra space / overscroll {isLoading && ( // MODIFIED: SDK loading screen follows darkMode )} {!isConnected && ( No Internet Connection )} ); }); const styles = StyleSheet.create({ // MODIFIED container: { flex: 1, }, offlineContainer: { position: 'absolute', top: 0, left: 0, right: 0, height: 30, justifyContent: 'center', alignItems: 'center', backgroundColor: '#b52424', zIndex: 1, }, offlineText: { color: '#fff', }, loaderContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', position: 'absolute', width: '100%', height: '100%', zIndex: 1, }, loader: {}, });