import axios from 'axios'; /** * Unwraps known link shims (Gmail, Outlook, etc.) and follows redirects * to find the final destination URL. */ export async function resolveUrl(url: string): Promise { let currentUrl = url; try { const parsedUrl = new URL(url); // 1. Unwrap known shims // Google/Gmail Shim if (/google\.[a-z.]*$/.test(parsedUrl.hostname) && parsedUrl.pathname === '/url' && parsedUrl.searchParams.has('q')) { currentUrl = parsedUrl.searchParams.get('q') || currentUrl; } // Outlook Safelinks if (parsedUrl.hostname.includes('safelinks.protection.outlook.com') && parsedUrl.searchParams.has('url')) { currentUrl = parsedUrl.searchParams.get('url') || currentUrl; } // LinkedIn Shim if (parsedUrl.hostname === 'lnkd.in' || (parsedUrl.hostname === 'www.linkedin.com' && parsedUrl.pathname.startsWith('/Check/'))) { // These are harder to unwrap without following } // 2. Follow redirects (optional, but good for bit.ly etc.) // We only follow if it's a known shortener or if we want to be thorough. const shorteners = ['bit.ly', 't.co', 'goo.gl', 'tinyurl.com', 'is.gd', 'buff.ly', 'adf.ly', 'lnkd.in']; const isShortener = shorteners.some(s => parsedUrl.hostname.includes(s)); if (isShortener) { const response = await axios.head(currentUrl, { maxRedirects: 5, validateStatus: (status) => status >= 200 && status < 400 }); if (response.request.res.responseUrl) { currentUrl = response.request.res.responseUrl; } } return currentUrl; } catch (error) { console.error('Error resolving URL:', error); return url; // Return original if error } }