Help Needed, @capacitor/browser, appurllistener

Hey so were building a decoupled checkout for a wordpress/woocommerce application i got the flow set-up and functional at this point but when the appurllisterner captures the url i echo from the complete checkout its redirecting me back to the cart page, i’ve turned import { Browser } from ‘@capacitor/browser’; into a hooke called useBrowserOpen.tsx which is what you’ll see in the code to ensure the browser listener opens and closes, but im not sure why its taking me to cart page before it goes to the link the appurllistener actual calls, i also see it the first execution in the console log before executing the remaining queries needed and then its called again which then redirects after the logic in the browser close runs, which is a query used to get the user jwt token based on if the user logged in from that page.


import { useHistory } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { App, URLOpenListenerEvent } from '@capacitor/app';
import { PluginResultData } from '@capacitor/core';
import { useDispatch } from 'react-redux';
import { resetCart } from '../store/cartSlice';
import useBrowserOpen from '../hooks/useBrowserOpen'; // Adjust the path to your hook

const createHandleAppUrlOpen = ({
	history,
	location,
}: {
	history: ReturnType<typeof useHistory>;
	location: typeof window.location;
}) => (data: PluginResultData) => {
	console.debug('[====AppUrlListener.tsx====][appUrlOpen] App opened with URL:', data);

	const checkDataForUrl = (data: PluginResultData): data is URLOpenListenerEvent => data.url !== undefined;

	if (!checkDataForUrl(data)) {
		console.debug('[====AppUrlListener.tsx====][appUrlOpen] Data does not include url property! Listener not triggered.');
		return;
	}

	const event = data;
	const hostname = location.hostname;

	if (!hostname || !event.url.includes(hostname)) return;

	const slug = event.url.split(hostname).pop();

	if (!slug) return;

	// const addCapacitorListeners = ({ history, location }: { history: ReturnType<typeof useHistory>, location: typeof window.location }) => {
	// 	const handleAppUrlOpen = createHandleAppUrlOpen({ history, location })
	//
	// 	App.addListener('appUrlOpen', handleAppUrlOpen)
	//
	// 	App.addListener('backButton', () => {
	// 		console.debug('[====AppUrlListener.tsx====][backButton] App back button pressed')
	// 	})
	// 	App.addListener('pause', () => {
	// 		console.debug('[====AppUrlListener.tsx====][pause] App paused')
	// 	})
	// 	App.addListener('resume', () => {
	// 		console.debug('[====AppUrlListener.tsx====][resume] App resumed')
	// 	})
	// 	App.addListener('appStateChange', ({ isActive }) => {
	// 		console.debug(`[====AppUrlListener.tsx====][appStateChange] App state changed to ${isActive ? 'ACTIVE' : 'NOT ACTIVE'}`)
	// 	})
	// 	App.addListener('appRestoredResult', data => {
	// 		console.debug('[====AppUrlListener.tsx====][appRestoredResult] Restored state:', data)
	// 	})
	// }

	history.push(slug);
};

const AppUrlListener: React.FC = () => {
	const history = useHistory();
	const location = window.location;
	const dispatch = useDispatch();
	const { closeBrowser } = useBrowserOpen('', async () => {}); // Adjust as needed for your use case

	const [processedUrls, setProcessedUrls] = useState<string[]>([]); // State to track processed URLs

	useEffect(() => {
		console.debug('[====AppUrlListener.tsx====][useEffect] component mounted', { history, location });

		const handleDeepLink = (url: string) => {
			if (processedUrls.includes(url)) {
				console.debug('[====AppUrlListener.tsx====] URL already processed:', url);
				return;
			}

			let path;
			const urlObject = new URL(url);

			if (url.startsWith('https:') || url.startsWith('http:')) {
				// Handle universal links (HTTP/HTTPS)
				path = urlObject.pathname;
			} else {
				// Handle custom URL schemes by extracting the path after the scheme
				const slug = url.split('://').pop();
				path = slug || '';
			}

			// Special handling for password reset links
			if (path.includes('wp-login.php')) {
				// Check if it's the password reset action
				const action = urlObject.searchParams.get('action');
				const key = urlObject.searchParams.get('key');
				const login = urlObject.searchParams.get('login');

				if (action === 'rp' && key && login) {
					// Redirect to the reset-password route with key and login
					history.push(`/reset-password/${key}/${encodeURIComponent(login)}`);
					console.log('Redirecting to reset-password:', key, login);
					setProcessedUrls([...processedUrls, url]); // Mark URL as processed
					return;
				}
			}

			// Normalize the path to remove any leading slashes
			if (path.startsWith('/')) {
				path = path.slice(1);
			}

			if (path.includes('order-confirmed')) {
				const orderId = urlObject.searchParams.get('order');
				const order_confirmation_path = '/order-confirmed/' + orderId;
				history.push(order_confirmation_path);
				setProcessedUrls([...processedUrls, url]); // Update processedUrls immediately
				closeBrowser();
				dispatch(resetCart());

				// Ensure navigation and operations happen sequentially
				// setTimeout(() => {
				// 	dispatch(resetCart());
				// 	closeBrowser();
				// }, 0);

				return;
			}

			// Combine path and query parameters to form the full path
			const fullPath = '/' + path + urlObject.search;
			console.debug('[====AppUrlListener.tsx====] Navigating to:', fullPath);
			history.push(fullPath); // Navigate using React Router
			setProcessedUrls([...processedUrls, url]); // Mark URL as processed
		};

		const handleAppUrlOpen = createHandleAppUrlOpen({ history, location });

		App.addListener('appUrlOpen', (event: URLOpenListenerEvent) => {
			console.debug('[====AppUrlListener.tsx====] Deep link URL received:', event.url);
			if (event.url.includes('wp-login.php') && event.url.includes('action=rp')) {
				// Handle the specific URL with 'wp-login.php' and 'action=rp'
				handleDeepLink(event.url);
			} else {
				// Handle other deep links using existing logic
				handleDeepLink(event.url);
			}
		});

		// Handle deep links on app start or cold start
		App.addListener('appRestoredResult', (data) => {
			console.debug('[====AppUrlListener.tsx====] App restored with result:', data);
			if (data.url) {
				handleDeepLink(data.url);
			}
		});

		// Cleanup listeners when the component is unmounted
		return () => {
			console.debug('[====AppUrlListener.tsx====] Component unmounted.');
			App.removeAllListeners();
		};
	}, [history, location, closeBrowser, dispatch, processedUrls]);

	return null;
};

export default AppUrlListener;


import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Browser } from '@capacitor/browser';
import {
	setJwtToken,
	setCustomer,
	setAuthTokenExpiration,
	setRefreshToken,
	setWooSessionToken,
	setRefreshTokenExpiration,
	updateCustomerId,
} from '../store/authSlice';
import { useQuery } from '@apollo/client';
import { GET_SESSION_DATA } from '../queries/authQueries';
import { getUserWithAuthData } from '../queries/authAPI';
import { RootState } from '../store/store';
import { decodeJwt, CustomJwtPayload } from "../utils/decodeJWT";
import { Customer } from '../store/authSlice'; // Ensure this is the correct import path

const useBrowserOpen = (checkoutUrl: string, updateSession: () => Promise<any>) => {
	const dispatch = useDispatch();
	const authState = useSelector((state: RootState) => state.auth);

	useEffect(() => {
		console.log("[log] - Current auth state: ", JSON.stringify(authState));
	}, [authState]);

	const { data: sessionData, error: sessionError, refetch: refetchSessionData } = useQuery(GET_SESSION_DATA);

	const handleBrowserFinished = async () => {
		try {
			await updateSession();

			const refetchResponse = await refetchSessionData(); // Fetch session data after updating
			// console.log("[log] - Refetch Response: ", refetchResponse);

			const jwtToken = refetchResponse.data?.guestSessionData;
			// console.log("[log] - JWT Token: ", jwtToken);

			if (jwtToken && jwtToken !== "Valid session token not found.") {
				dispatch(setJwtToken(jwtToken));
				// console.log("[log] - JWT Token set in Redux: ", jwtToken);

				const decodedToken: CustomJwtPayload | null = jwtToken ? decodeJwt(jwtToken) : null;
				// console.log("[log] - Decoded Token: ", decodedToken);

				if (decodedToken) {
					const customerId = decodedToken.data.user.id;
					const exp = decodedToken.exp;

					// console.log("[log] - Decoded Customer ID: ", customerId);
					// console.log("[log] - Decoded Expiration Time: ", exp);

					if (customerId) {
						dispatch(updateCustomerId(customerId));

						if (exp) {
							dispatch(setAuthTokenExpiration(exp * 1000)); // Convert exp to milliseconds
							// console.log("[log] - Token Expiration Time set in Redux: ", exp * 1000);
						} else {
							// console.warn("[warn] - Expiration time not found in JWT token");
						}

						// console.log("[log] - Fetching user data with customer ID: ", customerId);
						const userData = await getUserWithAuthData(customerId, "DATABASE_ID");
						// console.log("[log] - User data is: ", userData);

						if (userData) {
							const {
								email,
								firstName,
								lastName,
								databaseId,
								auth: {
									authToken,
									authTokenExpiration,
									refreshToken,
									refreshTokenExpiration,
									wooSessionToken,
								}
							} = userData;

							// Save the refresh token in the Redux store
							dispatch(setRefreshToken(refreshToken));

							// console.log("[log] - Constructing customer object with user data", userData);
							const customer: Customer = {
								firstName,
								lastName,
								email,
								databaseId,
								accountNonce: "",
								cartNonce: "",
								checkoutNonce: "",
								checkoutUrl: "",
								cartUrl: "",
								role: "",
								id: databaseId.toString(),
								birthYear: "",
								sunSign: "",
								phone: "",
								fcmToken: null,
								externalId: null,
								orders: { nodes: [] },
								shipping: {
									firstName,
									lastName,
									phone: "",
									postcode: "",
									state: "",
									email,
									city: "",
									address1: "",
									address2: "",
									country: ""
								},
								billing: undefined
							};

							// console.log("[log] - Customer object constructed: ", customer);
							// console.log("[log] - Dispatching customer data to Redux");

							dispatch(setCustomer(customer));
							dispatch(setJwtToken(authToken));
							dispatch(setRefreshToken(refreshToken));
							dispatch(setAuthTokenExpiration(Number(authTokenExpiration) * 1000)); // Convert exp to milliseconds
							dispatch(setRefreshTokenExpiration(Number(refreshTokenExpiration) * 1000));
							dispatch(setWooSessionToken(wooSessionToken));

							// console.log("[log] - User data refreshed and customer updated in Redux: ", customer);
							// console.log("authstate at the end :", authState);
						} else {
							console.error("[error] - User data not found or malformed", userData);
						}
					} else {
						console.error("[error] - Customer ID not found in JWT token");
					}
				} else {
					console.error("[error] - Error decoding JWT token, decodedToken is null");
				}
			} else {
				console.error("[error] - JWT token not found in GET_SESSION_DATA");
				return;
			}
		} catch (error) {
			console.log("[error] - Error updating customer state:", error);
		}
	};

	const openBrowser = async () => {
		// console.log("[log] - Attempting to open browser with URL: ", checkoutUrl);
		if (checkoutUrl) {
			await Browser.open({ url: checkoutUrl });
		} else {
			console.error("[error] - Checkout URL not set");
		}
	};

	const closeBrowser = async () => {
		// console.log("[log] - Attempting to close browser");
		await Browser.close();
	};

	const setListener = async () => {
		// console.log("[log] - Setting browser listeners");
		await Browser.removeAllListeners();
		await Browser.addListener('browserFinished', async () => {
			try {
				// console.log("[log] - browserFinished event received");
				await handleBrowserFinished();
				// console.log("[log] - browserFinished: Finished handleBrowserFinished");
			} catch (error) {
				// console.log("[error] - browserFinished: Error: ", error);
			}
		});
	};

	useEffect(() => {
		setListener();

		return () => {
			// console.log("[log] - Removing all browser listeners");
			handleBrowserFinished();
			Browser.removeAllListeners();
		};
	}, []);

	return { openBrowser, closeBrowser };
};

export default useBrowserOpen;

any help or direction is appreciated as im new to ionic and can potentially be doing something incorrectly.

1 Like