-
Notifications
You must be signed in to change notification settings - Fork 454
Expand file tree
/
Copy pathcreate-clerk-instance.ts
More file actions
147 lines (124 loc) · 5.14 KB
/
create-clerk-instance.ts
File metadata and controls
147 lines (124 loc) · 5.14 KB
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import {
loadClerkJSScript,
loadClerkUIScript,
setClerkJSLoadingErrorPackageName,
} from '@clerk/shared/loadClerkJsScript';
import type { BrowserClerkConstructor, ClerkOptions } from '@clerk/shared/types';
import type { ClerkUIConstructor } from '@clerk/shared/ui';
import type { Ui } from '@clerk/ui/internal';
import { $clerkStore } from '../stores/external';
import { $clerk, $csrState } from '../stores/internal';
import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions } from '../types';
import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions';
import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components';
import { runOnce } from './run-once';
let initOptions: ClerkOptions | undefined;
setClerkJSLoadingErrorPackageName(PACKAGE_NAME);
function createNavigationHandler(
windowNav: typeof window.history.pushState | typeof window.history.replaceState,
): Exclude<ClerkOptions['routerPush'], undefined> | Exclude<ClerkOptions['routerReplace'], undefined> {
return (to, opts) => {
if (opts?.__internal_metadata?.navigationType === 'internal') {
windowNav(history.state, '', to);
} else {
opts?.windowNavigate(to);
}
};
}
/**
* Prevents firing clerk.load() multiple times
*/
const createClerkInstance = runOnce(createClerkInstanceInternal);
async function createClerkInstanceInternal<TUi extends Ui = Ui>(options?: AstroClerkCreateInstanceParams<TUi>) {
// Load clerk-js and clerk-ui in parallel.
// Both functions return early if the scripts are already loaded
// (e.g., via middleware-injected script tags in the HTML head).
const clerkJsChunk = getClerkJsEntryChunk(options);
const ClerkUI = getClerkUIEntryChunk(options);
await clerkJsChunk;
if (!window.Clerk) {
throw new Error('Failed to download latest ClerkJS. Contact support@clerk.com.');
}
const clerkJSInstance = window.Clerk;
if (!$clerk.get()) {
$clerk.set(clerkJSInstance);
}
const clerkOptions = {
routerPush: createNavigationHandler(window.history.pushState.bind(window.history)),
routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)),
...options,
// Pass the clerk-ui constructor promise to clerk.load()
ui: { ...options?.ui, ClerkUI },
} as unknown as ClerkOptions;
initOptions = clerkOptions;
return clerkJSInstance
.load(clerkOptions)
.then(() => {
$csrState.setKey('isLoaded', true);
// Notify subscribers that $clerkStore has been loaded.
// We're doing this because nanostores uses `===` for equality
// and just by setting the value to `window.Clerk` again won't trigger an update.
// We notify only once as this store is for advanced users.
$clerkStore.notify();
mountAllClerkAstroJSComponents();
invokeClerkAstroJSFunctions();
clerkJSInstance.addListener(payload => {
$csrState.setKey('client', payload.client);
$csrState.setKey('user', payload.user);
$csrState.setKey('session', payload.session);
$csrState.setKey('organization', payload.organization);
});
})
.catch(() => {});
}
function updateClerkOptions<TUi extends Ui = Ui>(options: AstroClerkUpdateOptions<TUi>) {
const clerk = $clerk.get();
if (!clerk) {
throw new Error('Missing clerk instance');
}
const updateOptions = {
options: { ...initOptions, ...options },
appearance: { ...initOptions?.appearance, ...options.appearance },
} as unknown as { options: ClerkOptions; appearance?: any };
// `__internal_updateProps` is not exposed as public API from `@clerk/types`
void (clerk as any).__internal_updateProps(updateOptions);
}
/**
* Loads clerk-js script if not already loaded.
* Uses bundled ClerkJS constructor when js.ClerkJS is present.
* Returns early if window.Clerk already exists.
*/
async function getClerkJsEntryChunk<TUi extends Ui = Ui>(options?: AstroClerkCreateInstanceParams<TUi>): Promise<void> {
const jsProp = options as { js?: { ClerkJS?: BrowserClerkConstructor } } | undefined;
if (jsProp?.js?.ClerkJS) {
window.Clerk = new jsProp.js.ClerkJS(options?.publishableKey as string, {
proxyUrl: options?.proxyUrl as string,
domain: options?.domain as string,
}) as any;
return;
}
await loadClerkJSScript(options);
}
/**
* Gets the ClerkUI constructor, either from options or by loading the script.
* Returns early if window.__internal_ClerkUICtor already exists.
* Returns undefined when prefetchUI={false} (no UI needed).
*/
async function getClerkUIEntryChunk<TUi extends Ui = Ui>(
options?: AstroClerkCreateInstanceParams<TUi>,
): Promise<ClerkUIConstructor | undefined> {
// Support bundled UI via ui.ClerkUI prop
if (options?.ui?.ClerkUI) {
return options.ui.ClerkUI;
}
// Skip CDN prefetch when ui prop is passed (bundled UI) or prefetchUI is false
if (options?.ui || options?.prefetchUI === false) {
return undefined;
}
await loadClerkUIScript(options as any);
if (!window.__internal_ClerkUICtor) {
throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.');
}
return window.__internal_ClerkUICtor;
}
export { createClerkInstance, updateClerkOptions };