Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(shared,react-router,tanstack-start): Create shared environment variable retrieval function #4985

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
23db855
feat: Create shared environment variable retrieval function
wobsoriano Jan 23, 2025
f756662
reuse logic within tanstack-start
wobsoriano Jan 23, 2025
029dce0
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 24, 2025
7f808fa
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 24, 2025
f1ce19b
chore: pass context directly
wobsoriano Jan 24, 2025
9f486ca
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 27, 2025
a876fa7
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 28, 2025
2a1bd48
remove unused catch var
wobsoriano Jan 24, 2025
d85c375
test nextjs env vars
wobsoriano Jan 29, 2025
78804b9
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 29, 2025
a7cb5a9
revert
wobsoriano Jan 29, 2025
585ff10
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 29, 2025
b8f584a
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 31, 2025
ca4aafa
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Jan 31, 2025
fa68dae
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Feb 3, 2025
0cb3adb
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Feb 3, 2025
58c1fdd
chore: add changeset
wobsoriano Feb 3, 2025
df3533b
add env var manually to tanstack router integration
wobsoriano Feb 3, 2025
5152f78
temporarily add tsup config to ts config
wobsoriano Feb 3, 2025
7b44219
remove from tsconfig
wobsoriano Feb 3, 2025
d09261d
update changeset
wobsoriano Feb 3, 2025
2ea0a3e
remove duplicate util export
wobsoriano Feb 3, 2025
f8305f6
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Feb 3, 2025
15b7417
improve types
wobsoriano Feb 3, 2025
8431042
chore: update changeset
wobsoriano Feb 4, 2025
1f6bff3
chore: update changeset
wobsoriano Feb 4, 2025
db1427e
chore: update changesets
wobsoriano Feb 4, 2025
01ba102
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Feb 4, 2025
71ff504
Merge branch 'main' into rob/eco-302-move-environment-variable-logic-…
wobsoriano Feb 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/eleven-cougars-film.md
LekoArts marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@clerk/shared": minor
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the introduction of es2020 target, would it make sense to publish a minor version?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine 👍

---

Introduce unified environment variable handling across all supported platforms

Usage:

```ts
import { getEnvVariable } from '@clerk/shared/getEnvVariable'

const publishableKey = getEnvVariable('CLERK_PUBLISHABLE_KEY')
```
6 changes: 6 additions & 0 deletions .changeset/unlucky-gifts-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/tanstack-start': patch
'@clerk/react-router': patch
---

Internal changes to use new `getEnvVariable` utility from `@clerk/shared`
3 changes: 2 additions & 1 deletion packages/react-router/src/ssr/loadOptions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createClerkRequest } from '@clerk/backend/internal';
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import { getPublicEnvVariables } from '../utils/env';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
import { patchRequest } from './utils';
Expand Down
54 changes: 1 addition & 53 deletions packages/react-router/src/utils/env.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,7 @@
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { isTruthy } from '@clerk/shared/underscore';
import type { AppLoadContext } from 'react-router';

type CloudflareEnv = { env: Record<string, string> };

const hasCloudflareProxyContext = (context: any): context is { cloudflare: CloudflareEnv } => {
return !!context?.cloudflare?.env;
};

const hasCloudflareContext = (context: any): context is CloudflareEnv => {
return !!context?.env;
};

/**
*
* Utility function to get env variables across Node and Edge runtimes.
*
* @param name
* @returns string
*/
export const getEnvVariable = (name: string, context: AppLoadContext | undefined): string => {
// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name];
}

// @ts-expect-error - Vite specific
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

if (hasCloudflareProxyContext(context)) {
return context.cloudflare.env[name] || '';
}

// Cloudflare
if (hasCloudflareContext(context)) {
return context.env[name] || '';
}

// Check whether the value exists in the context object directly
if (context && typeof context[name] === 'string') {
return context[name];
}

// Cloudflare workers
try {
return globalThis[name as keyof typeof globalThis];
} catch {
// This will raise an error in Cloudflare Pages
}

return '';
};

export const getPublicEnvVariables = (context: AppLoadContext | undefined) => {
return {
publishableKey:
Expand Down
8 changes: 8 additions & 0 deletions packages/shared/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@ declare const PACKAGE_NAME: string;
declare const PACKAGE_VERSION: string;
declare const JS_PACKAGE_VERSION: string;
declare const __DEV__: boolean;

interface ImportMetaEnv {
readonly [key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"object",
"oauth",
"web3",
"getEnvVariable",
"pathMatcher"
],
"scripts": {
Expand Down
50 changes: 50 additions & 0 deletions packages/shared/src/getEnvVariable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
type CloudflareEnv = { env: Record<string, string> };

const hasCloudflareProxyContext = (context: any): context is { cloudflare: CloudflareEnv } => {
return !!context?.cloudflare?.env;
};

const hasCloudflareContext = (context: any): context is CloudflareEnv => {
return !!context?.env;
};

/**
* Retrieves an environment variable across runtime environments.
* @param name - The environment variable name to retrieve
* @param context - Optional context object that may contain environment values
* @returns The environment variable value or empty string if not found
*/
export const getEnvVariable = (name: string, context?: Record<string, any>): string => {
// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name];
}

// Vite specific
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
return import.meta.env[name];
}

if (hasCloudflareProxyContext(context)) {
return context.cloudflare.env[name] || '';
}

// Cloudflare
if (hasCloudflareContext(context)) {
return context.env[name] || '';
}

// Check whether the value exists in the context object directly
if (context && typeof context[name] === 'string') {
return context[name];
}

// Cloudflare workers
try {
return globalThis[name as keyof typeof globalThis];
} catch {
// This will raise an error in Cloudflare Pages
}

return '';
};
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export * from './object';
export * from './logger';
export { createWorkerTimers } from './workerTimers';
export { DEV_BROWSER_JWT_KEY, extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from './devBrowser';
export { getEnvVariable } from './getEnvVariable';
export * from './pathMatcher';
1 change: 1 addition & 0 deletions packages/shared/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default defineConfig(overrideOptions => {
minify: false,
sourcemap: true,
dts: true,
target: 'es2020',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support import.meta.env, esbuild needs a target of es2020 or above

external: ['react', 'react-dom'],
esbuildPlugins: [WebWorkerMinifyPlugin as any],
define: {
Expand Down
17 changes: 9 additions & 8 deletions packages/tanstack-start/src/server/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import { getPublicEnvVariables } from '../utils/env';

export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);
const publicEnvs = getPublicEnvVariables(event.context);

return {
// Public environment variables
Expand All @@ -21,16 +22,16 @@ export const commonEnvs = () => {
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),
API_VERSION: getEnvVariable('CLERK_API_VERSION', event.context) || 'v1',
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', event.context),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', event.context),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', event.context),
API_URL: getEnvVariable('CLERK_API_URL', event.context) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
environment: getEnvVariable('NODE_ENV', event.context),
},
} as const;
};
9 changes: 5 additions & 4 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { createClerkRequest } from '@clerk/backend/internal';
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import { getPublicEnvVariables } from '../utils/env';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';
Expand All @@ -18,14 +19,14 @@ export const loadOptions = (request: Request, overrides: LoaderOptions = {}) =>
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const apiUrl = getEnvVariable('CLERK_API_URL', event.context) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event.context).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event.context).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
28 changes: 19 additions & 9 deletions packages/tanstack-start/src/server/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { RequestState } from '@clerk/backend/internal';
import { debugRequestState } from '@clerk/backend/internal';
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { isTruthy } from '@clerk/shared/underscore';
import { getEvent } from 'vinxi/http';

import { getEnvVariable } from '../../utils/env';
import type { AdditionalStateOptions } from '../types';

/**
Expand All @@ -22,6 +23,7 @@ export const wrapWithClerkState = (data: any) => {
*/
export function getResponseClerkState(requestState: RequestState, additionalStateOptions: AdditionalStateOptions = {}) {
const { reason, message, isSignedIn, ...rest } = requestState;
const event = getEvent();

const clerkInitialState = wrapWithClerkState({
__clerk_ssr_state: rest.toAuth(),
Expand All @@ -34,18 +36,26 @@ export function getResponseClerkState(requestState: RequestState, additionalStat
__afterSignInUrl: requestState.afterSignInUrl,
__afterSignUpUrl: requestState.afterSignUpUrl,
__clerk_debug: debugRequestState(requestState),
__clerkJSUrl: getEnvVariable('CLERK_JS'),
__clerkJSVersion: getEnvVariable('CLERK_JS_VERSION'),
__telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
__telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
__clerkJSUrl: getEnvVariable('CLERK_JS', event.context),
__clerkJSVersion: getEnvVariable('CLERK_JS_VERSION', event.context),
__telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED', event.context)),
__telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG', event.context)),
__signInForceRedirectUrl:
additionalStateOptions.signInForceRedirectUrl || getEnvVariable('CLERK_SIGN_IN_FORCE_REDIRECT_URL') || '',
additionalStateOptions.signInForceRedirectUrl ||
getEnvVariable('CLERK_SIGN_IN_FORCE_REDIRECT_URL', event.context) ||
'',
__signUpForceRedirectUrl:
additionalStateOptions.signUpForceRedirectUrl || getEnvVariable('CLERK_SIGN_UP_FORCE_REDIRECT_URL') || '',
additionalStateOptions.signUpForceRedirectUrl ||
getEnvVariable('CLERK_SIGN_UP_FORCE_REDIRECT_URL', event.context) ||
'',
__signInFallbackRedirectUrl:
additionalStateOptions.signInFallbackRedirectUrl || getEnvVariable('CLERK_SIGN_IN_FALLBACK_REDIRECT_URL') || '',
additionalStateOptions.signInFallbackRedirectUrl ||
getEnvVariable('CLERK_SIGN_IN_FALLBACK_REDIRECT_URL', event.context) ||
'',
__signUpFallbackRedirectUrl:
additionalStateOptions.signUpFallbackRedirectUrl || getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL') || '',
additionalStateOptions.signUpFallbackRedirectUrl ||
getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL', event.context) ||
'',
});

return {
Expand Down
38 changes: 4 additions & 34 deletions packages/tanstack-start/src/utils/env.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,10 @@
import { getEnvVariable } from '@clerk/shared/getEnvVariable';
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';
import type { H3EventContext } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}

// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name];
}

// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
return import.meta.env[name];
}

return defaultValue;
};

export const getPublicEnvVariables = (event?: HTTPEvent) => {
export const getPublicEnvVariables = (context?: H3EventContext) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
return getEnvVariable(`VITE_${name}`, context) || getEnvVariable(name, context);
};

return {
Expand Down