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;
}
|