Skip to content

Commit

Permalink
chore(shared,react-router,tanstack-start): Create shared environment …
Browse files Browse the repository at this point in the history
…variable retrieval function (#4985)
  • Loading branch information
wobsoriano authored Feb 6, 2025
1 parent 6ce705c commit f41081c
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 109 deletions.
13 changes: 13 additions & 0 deletions .changeset/eleven-cougars-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@clerk/shared": minor
---

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',

This comment has been minimized.

Copy link
@mboudreau

mboudreau Feb 20, 2025

@wobsoriano This broke everyone using an es5 browser (ios 12 for instance) because of optional chaining, since version 5.52.2 of clerk-js. I can confirm that using 5.52.1 is working fine and it had no optional chaining in the built javascript.

This comment has been minimized.

Copy link
@wobsoriano

wobsoriano Feb 20, 2025

Author Member

Hi @mboudreau, sorry for the inconvenience! Please use clerk-js 5.52.1 for now. We'll make sure to update once we have a proper solution in place.

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

0 comments on commit f41081c

Please sign in to comment.