File size: 2,537 Bytes
ce8f04a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * Shared auth gate so axios 401 handler does not fight Clerk session state.
 * ApiInterceptor writes; client interceptor reads.
 */
let clerkLoaded = false;
let clerkSignedIn = false;
let hardRedirectArmed = false;
let lastRedirectAt = 0;

export function setClerkAuthState(opts: { isLoaded: boolean; isSignedIn: boolean }) {
  clerkLoaded = opts.isLoaded;
  clerkSignedIn = Boolean(opts.isSignedIn);
  // Allow a hard redirect only after Clerk finished loading and user is truly out
  hardRedirectArmed = clerkLoaded && !clerkSignedIn;
}

export function getClerkAuthState() {
  return { clerkLoaded, clerkSignedIn, hardRedirectArmed };
}

/**
 * Should a 401 response force navigation to /sign-in?
 * - Never while Clerk still loading
 * - Never while Clerk says the user IS signed in (token race / backend lag)
 * - Throttle hard redirects to avoid bounce loops
 */
export function shouldHardRedirectOn401(): boolean {
  if (!clerkLoaded) return false;
  if (clerkSignedIn) return false;
  if (!hardRedirectArmed) return false;
  const now = Date.now();
  if (now - lastRedirectAt < 2500) return false;
  lastRedirectAt = now;
  return true;
}

export function safeSignInRedirectPath(currentPath: string, search = ''): string {
  const path = currentPath || '/projects';
  if (
    path === '/' ||
    path.startsWith('/sign-in') ||
    path.startsWith('/sign-up') ||
    path.startsWith('/pricing') ||
    path.startsWith('/cennik') ||
    path.startsWith('/regulamin') ||
    path.startsWith('/polityka')
  ) {
    return '/sign-in';
  }
  const returnTo = encodeURIComponent(path + (search || ''));
  return `/sign-in?redirect_url=${returnTo}`;
}

/** Resolve post-login landing: query redirect_url → fallback /projects (not /dashboard). */
export function resolvePostAuthRedirect(search: string): string {
  try {
    const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
    const raw = params.get('redirect_url') || params.get('redirectUrl') || '';
    if (raw) {
      const decoded = decodeURIComponent(raw);
      // Only same-origin relative paths
      if (decoded.startsWith('/') && !decoded.startsWith('//') && !decoded.includes('://')) {
        // Avoid bounce: /dashboard is an alias of /projects
        if (decoded === '/dashboard' || decoded.startsWith('/dashboard?')) {
          return '/projects' + (decoded.includes('?') ? decoded.slice(decoded.indexOf('?')) : '');
        }
        return decoded;
      }
    }
  } catch {
    /* ignore */
  }
  return '/projects';
}