File size: 3,534 Bytes
ec8acdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import type { Clerk } from '@clerk/clerk-js';

export type LoadedClerk = InstanceType<typeof Clerk>;

const MONO_FONT = "'SF Mono', Monaco, 'Cascadia Code', 'Fira Code', monospace";

let clerk: LoadedClerk | null = null;
let clerkLoadPromise: Promise<LoadedClerk> | null = null;
let scheduledClerkLoadPromise: Promise<LoadedClerk> | null = null;
const clerkLoadSubscribers = new Set<(clerk: LoadedClerk) => void>();

export async function ensureClerk(): Promise<LoadedClerk> {
  if (clerk) return clerk;
  if (clerkLoadPromise) return clerkLoadPromise;
  clerkLoadPromise = loadClerk().catch((err) => {
    clerkLoadPromise = null;
    throw err;
  });
  return clerkLoadPromise;
}

export function scheduleClerkLoad(): Promise<LoadedClerk> | null {
  if (clerk) return Promise.resolve(clerk);
  if (clerkLoadPromise) return clerkLoadPromise;
  if (scheduledClerkLoadPromise) return scheduledClerkLoadPromise;
  if (typeof window === 'undefined') return null;
  if (!import.meta.env.VITE_CLERK_PUBLISHABLE_KEY) return null;

  scheduledClerkLoadPromise = new Promise((resolve, reject) => {
    const startLoad = (): void => {
      ensureClerk().then((loadedClerk) => {
        scheduledClerkLoadPromise = null;
        resolve(loadedClerk);
      }, (err) => {
        scheduledClerkLoadPromise = null;
        reject(err);
      });
    };

    const ric = (window as unknown as {
      requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number;
    }).requestIdleCallback;
    if (typeof ric === 'function') {
      ric(startLoad, { timeout: 4000 });
      return;
    }

    if (document.readyState === 'complete') {
      setTimeout(startLoad, 0);
    } else {
      window.addEventListener('load', () => setTimeout(startLoad, 0), { once: true });
    }
  });

  return scheduledClerkLoadPromise;
}

export function subscribeClerkLoaded(cb: (clerk: LoadedClerk) => void): () => void {
  clerkLoadSubscribers.add(cb);
  if (clerk) cb(clerk);
  return () => {
    clerkLoadSubscribers.delete(cb);
  };
}

function publishClerkLoaded(instance: LoadedClerk): void {
  for (const cb of clerkLoadSubscribers) {
    try {
      cb(instance);
    } catch (err) {
      console.error('[auth] Clerk load subscriber threw:', err);
    }
  }
}

async function loadClerk(): Promise<LoadedClerk> {
  const { Clerk: C } = await import('@clerk/clerk-js');
  const key = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
  if (!key) throw new Error('VITE_CLERK_PUBLISHABLE_KEY not set');
  const instance = new C(key);
  await instance.load({
    appearance: {
      variables: {
        colorBackground: '#0f0f0f',
        colorInputBackground: '#141414',
        colorInputText: '#e8e8e8',
        colorText: '#e8e8e8',
        colorTextSecondary: '#aaaaaa',
        colorPrimary: '#44ff88',
        colorNeutral: '#e8e8e8',
        colorDanger: '#ff4444',
        borderRadius: '4px',
        fontFamily: MONO_FONT,
        fontFamilyButtons: MONO_FONT,
      },
      elements: {
        card: { backgroundColor: '#111111', border: '1px solid #2a2a2a', boxShadow: '0 8px 32px rgba(0,0,0,0.6)' },
        formButtonPrimary: { color: '#000000', fontWeight: '600' },
        footerActionLink: { color: '#44ff88' },
        socialButtonsBlockButton: { borderColor: '#2a2a2a', color: '#e8e8e8', backgroundColor: '#141414' },
      },
    },
  });

  // Only publish the instance after load() succeeds, so a failed load does not
  // wedge ensureClerk()'s retry path.
  clerk = instance;
  publishClerkLoaded(clerk);
  return clerk;
}