forked from clerk/javascript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate-integration.ts
More file actions
188 lines (165 loc) · 8.8 KB
/
create-integration.ts
File metadata and controls
188 lines (165 loc) · 8.8 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import type { ClerkOptions } from '@clerk/shared/types';
import type { AstroIntegration } from 'astro';
import { envField } from 'astro/config';
import { name as packageName, version as packageVersion } from '../../package.json';
import type { AstroClerkIntegrationParams } from '../types';
import { vitePluginAstroConfig } from './vite-plugin-astro-config';
const buildEnvVarFromOption = (valueToBeStored: unknown, envName: keyof InternalEnv) => {
return valueToBeStored ? { [`import.meta.env.${envName}`]: JSON.stringify(valueToBeStored) } : {};
};
type HotloadAstroClerkIntegrationParams = AstroClerkIntegrationParams & {
enableEnvSchema?: boolean;
};
function createIntegration<Params extends HotloadAstroClerkIntegrationParams>() {
return (params?: Params): AstroIntegration => {
const { proxyUrl, isSatellite, domain, signInUrl, signUpUrl, enableEnvSchema = true } = params || {};
// These are not provided when the "bundled" integration is used
const clerkJSUrl = (params as any)?.clerkJSUrl as string | undefined;
const clerkJSVersion = (params as any)?.clerkJSVersion as string | undefined;
const clerkUIVersion = (params as any)?.clerkUIVersion as string | undefined;
const prefetchUI = (params as any)?.prefetchUI as boolean | undefined;
const hasUI = !!(params as any)?.ui;
return {
name: '@clerk/astro/integration',
hooks: {
'astro:config:setup': ({ config, injectScript, updateConfig, logger, command }) => {
if (['server', 'hybrid'].includes(config.output) && !config.adapter) {
logger.error('Missing adapter, please update your Astro config to use one.');
}
const internalParams: ClerkOptions = {
...params,
sdkMetadata: {
version: packageVersion,
name: packageName,
environment: command === 'dev' ? 'development' : 'production',
},
};
const buildImportPath = `${packageName}/internal`;
// Set params as envs so backend code has access to them
updateConfig({
vite: {
plugins: [vitePluginAstroConfig(config)],
define: {
/**
* Convert the integration params to environment variable in order for it to be readable from the server
*/
...buildEnvVarFromOption(signInUrl, 'PUBLIC_CLERK_SIGN_IN_URL'),
...buildEnvVarFromOption(signUpUrl, 'PUBLIC_CLERK_SIGN_UP_URL'),
...buildEnvVarFromOption(isSatellite, 'PUBLIC_CLERK_IS_SATELLITE'),
...buildEnvVarFromOption(proxyUrl, 'PUBLIC_CLERK_PROXY_URL'),
...buildEnvVarFromOption(domain, 'PUBLIC_CLERK_DOMAIN'),
...buildEnvVarFromOption(clerkJSUrl, 'PUBLIC_CLERK_JS_URL'),
...buildEnvVarFromOption(clerkJSVersion, 'PUBLIC_CLERK_JS_VERSION'),
...buildEnvVarFromOption(clerkUIVersion, 'PUBLIC_CLERK_UI_VERSION'),
...buildEnvVarFromOption(
prefetchUI === false || hasUI ? 'false' : undefined,
'PUBLIC_CLERK_PREFETCH_UI',
),
},
ssr: {
external: ['node:async_hooks'],
},
// We need this for top-level await
optimizeDeps: {
esbuildOptions: {
target: 'es2022',
},
},
build: {
target: 'es2022',
},
},
env: {
schema: {
...(enableEnvSchema ? createClerkEnvSchema() : {}),
},
},
});
/**
* ------------- Script Injection --------------------------
* Below we are injecting the same script twice. `runInjectionScript` is build in such way in order to instanciate and load Clerk only once.
* We need both scripts in order to support applications with or without UI frameworks.
*/
/**
* The above script will run before client frameworks like React hydrate.
* This makes sure that we have initialized a Clerk instance and populated stores in order to avoid hydration issues.
*/
injectScript(
'before-hydration',
`
${command === 'dev' ? `console.log('${packageName}',"Initialize Clerk: before-hydration")` : ''}
import { runInjectionScript } from "${buildImportPath}";
await runInjectionScript(${JSON.stringify(internalParams)});`,
);
/**
* The above script only executes if a client framework like React needs to hydrate.
* We need to run the same script again for each page in order to initialize Clerk even if no UI framework is used in the client
* If no UI framework is used in the client, the above script with `before-hydration` will never run
*/
injectScript(
'page',
`
${command === 'dev' ? `console.log("${packageName}","Initialize Clerk: page")` : ''}
import { runInjectionScript, swapDocument } from "${buildImportPath}";
// Taken from https://github.com/withastro/astro/blob/e10b03e88c22592fbb42d7245b65c4f486ab736d/packages/astro/src/transitions/router.ts#L39.
// Importing it directly from astro:transitions/client breaks custom client-side routing
// even when View Transitions is disabled.
const transitionEnabledOnThisPage = () => {
return !!document.querySelector('[name="astro-view-transitions-enabled"]');
}
if (transitionEnabledOnThisPage()) {
// We must do the dynamic imports within the event listeners because otherwise we may race and miss initial astro:page-load
document.addEventListener('astro:before-swap', async (e) => {
const { swapFunctions } = await import('astro:transitions/client');
const clerkComponents = document.querySelector('#clerk-components');
// Keep the div element added by Clerk
if (clerkComponents) {
const clonedEl = clerkComponents.cloneNode(true);
e.newDocument.body.appendChild(clonedEl);
}
e.swap = () => swapDocument(swapFunctions, e.newDocument);
});
document.addEventListener('astro:page-load', async (e) => {
const { navigate } = await import('astro:transitions/client');
await runInjectionScript({
...${JSON.stringify(internalParams)},
routerPush: navigate,
routerReplace: (url) => navigate(url, { history: 'replace' }),
});
})
} else {
await runInjectionScript(${JSON.stringify(internalParams)});
}`,
);
},
'astro:config:done': ({ injectTypes }) => {
injectTypes({
filename: 'types.d.ts',
content: `/// <reference types="@clerk/astro/env" />`,
});
},
},
};
};
}
function createClerkEnvSchema() {
return {
PUBLIC_CLERK_PUBLISHABLE_KEY: envField.string({ context: 'client', access: 'public' }),
PUBLIC_CLERK_SIGN_IN_URL: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_SIGN_UP_URL: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_IS_SATELLITE: envField.boolean({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_PROXY_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }),
PUBLIC_CLERK_DOMAIN: envField.string({ context: 'client', access: 'public', optional: true, url: true }),
PUBLIC_CLERK_JS_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }),
PUBLIC_CLERK_JS_VERSION: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_UI_VERSION: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_PREFETCH_UI: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_UI_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }),
PUBLIC_CLERK_TELEMETRY_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_TELEMETRY_DEBUG: envField.boolean({ context: 'client', access: 'public', optional: true }),
CLERK_SECRET_KEY: envField.string({ context: 'server', access: 'secret' }),
CLERK_MACHINE_SECRET_KEY: envField.string({ context: 'server', access: 'secret', optional: true }),
CLERK_JWT_KEY: envField.string({ context: 'server', access: 'secret', optional: true }),
};
}
export { createIntegration };