/** * Navigation Service Interface * * Provides an abstraction layer for browser navigation operations, * enabling better testability and dependency injection for OAuth flows. */ export interface NavigationService { /** * Redirects the browser to the specified URL * @param url - The URL to navigate to */ redirectTo(url: string): void; /** * Gets the current URL search parameters * @returns URLSearchParams object containing query parameters */ getSearchParams(): URLSearchParams; /** * Gets the current origin (protocol + hostname + port) * @returns The current origin string */ getOrigin(): string; /** * Gets the current pathname * @returns The current pathname string */ getPathname(): string; /** * Replaces the current URL without triggering navigation * @param url - The new URL to replace with */ replaceUrl(url: string): void; } /** * Browser Navigation Service Implementation * * Real implementation that uses the browser's window.location API */ export class BrowserNavigationService implements NavigationService { redirectTo(url: string): void { if (typeof window !== 'undefined') { window.location.href = url; } } getSearchParams(): URLSearchParams { if (typeof window !== 'undefined') { return new URLSearchParams(window.location.search); } return new URLSearchParams(); } getOrigin(): string { if (typeof window !== 'undefined') { return window.location.origin; } return 'http://localhost:3000'; } getPathname(): string { if (typeof window !== 'undefined') { return window.location.pathname; } return '/'; } replaceUrl(url: string): void { if (typeof window !== 'undefined' && window.history) { window.history.replaceState(null, '', url); } } } /** * Mock Navigation Service Implementation * * Test implementation that captures navigation calls for testing */ export class MockNavigationService implements NavigationService { public redirectCalls: string[] = []; public mockSearchParams: URLSearchParams; public mockOrigin: string; public mockPathname: string; public replaceCalls: string[] = []; constructor( searchParams: string = '', origin: string = 'http://localhost:3000', pathname: string = '/' ) { this.mockSearchParams = new URLSearchParams(searchParams); this.mockOrigin = origin; this.mockPathname = pathname; } redirectTo(url: string): void { this.redirectCalls.push(url); } getSearchParams(): URLSearchParams { return this.mockSearchParams; } getOrigin(): string { return this.mockOrigin; } getPathname(): string { return this.mockPathname; } replaceUrl(url: string): void { this.replaceCalls.push(url); } // Test utilities setSearchParams(searchParams: string): void { this.mockSearchParams = new URLSearchParams(searchParams); } getLastRedirect(): string | undefined { return this.redirectCalls[this.redirectCalls.length - 1]; } clear(): void { this.redirectCalls = []; this.replaceCalls = []; this.mockSearchParams = new URLSearchParams(); } } // Default instance for production use export const navigationService = new BrowserNavigationService();