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.