export const OUTDATED_INSTALLATION_ROUTE_NAME = 'outdated-installation'; export const OUTDATED_GATEWAY_ROUTE_NAME = 'outdated-gateway'; const EXEMPT_ROUTE_NAMES = new Set([ OUTDATED_INSTALLATION_ROUTE_NAME, OUTDATED_GATEWAY_ROUTE_NAME, ]); const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']); const isLoopbackOrigin = (currentOrigin) => { if (!currentOrigin) return false; try { const url = new URL(currentOrigin); return LOOPBACK_HOSTNAMES.has(url.hostname); } catch (_error) { return false; } }; const normalizeAllowedOrigins = (allowedOrigins = []) => { return new Set( allowedOrigins .map((origin) => { try { return new URL(origin).origin; } catch (_error) { return origin; } }) .filter(Boolean), ); }; export const isOriginAllowed = (currentOrigin, allowedOrigins = []) => { if (!currentOrigin) return true; if (isLoopbackOrigin(currentOrigin)) return true; const allowedOriginSet = normalizeAllowedOrigins(allowedOrigins); return allowedOriginSet.has(currentOrigin); }; export const isStandaloneContext = (win) => { const resolvedWindow = win || (typeof window !== 'undefined' ? window : undefined); if (!resolvedWindow) return false; const iosStandalone = resolvedWindow.navigator?.standalone === true; const mediaStandalone = typeof resolvedWindow.matchMedia === 'function' && resolvedWindow.matchMedia('(display-mode: standalone)').matches; return iosStandalone || mediaStandalone; }; export const shouldBypassOriginGate = (route) => { return EXEMPT_ROUTE_NAMES.has(route?.name); }; export const resolveOriginMigrationRoute = ({ to, currentOrigin, allowedOrigins, standalone, }) => { if (shouldBypassOriginGate(to)) { return null; } if (isOriginAllowed(currentOrigin, allowedOrigins)) { return null; } if (standalone) { return { name: OUTDATED_INSTALLATION_ROUTE_NAME }; } return { name: OUTDATED_GATEWAY_ROUTE_NAME, query: { redirect: to?.fullPath || '/', }, }; }; export const sanitizeRedirectPath = (redirectPath) => { if (typeof redirectPath !== 'string') return '/'; if (!redirectPath.startsWith('/')) return '/'; return redirectPath; }; export const buildMigrationUrl = (migrationOrigin, redirectPath) => { const safePath = sanitizeRedirectPath(redirectPath); try { return new URL(safePath, migrationOrigin).toString(); } catch (_error) { return migrationOrigin; } }; export const startRedirectCountdown = ({ seconds = 5, onTick, onRedirect, setIntervalFn = setInterval, clearIntervalFn = clearInterval, setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout, }) => { let remainingSeconds = seconds; const intervalId = setIntervalFn(() => { remainingSeconds = Math.max(remainingSeconds - 1, 0); if (typeof onTick === 'function') { onTick(remainingSeconds); } if (remainingSeconds === 0) { clearIntervalFn(intervalId); } }, 1000); const timeoutId = setTimeoutFn(() => { if (typeof onRedirect === 'function') { onRedirect(); } }, seconds * 1000); return () => { clearIntervalFn(intervalId); clearTimeoutFn(timeoutId); }; };