mirror of
https://github.com/documenso/documenso.git
synced 2025-11-23 05:01:54 +10:00
feat: runtime env
Support runtime environment variables using server components. This will mean docker images can change env vars for runtime as required.
This commit is contained in:
@ -4,7 +4,7 @@ import {
|
||||
TFeatureFlagValue,
|
||||
ZFeatureFlagValueSchema,
|
||||
} from '@documenso/lib/client-only/providers/feature-flag.types';
|
||||
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { appBaseUrl } from '@documenso/lib/constants/app';
|
||||
import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags';
|
||||
|
||||
/**
|
||||
@ -24,7 +24,7 @@ export const getFlag = async (
|
||||
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/get`);
|
||||
const url = new URL(`${appBaseUrl()}/api/feature-flag/get`);
|
||||
url.searchParams.set('flag', flag);
|
||||
|
||||
const response = await fetch(url, {
|
||||
@ -57,7 +57,7 @@ export const getAllFlags = async (
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/all`);
|
||||
const url = new URL(`${appBaseUrl()}/api/feature-flag/all`);
|
||||
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
@ -82,7 +82,7 @@ export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFla
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/all`);
|
||||
const url = new URL(`${appBaseUrl()}/api/feature-flag/all`);
|
||||
|
||||
return fetch(url, {
|
||||
next: {
|
||||
|
||||
26
packages/lib/universal/runtime-env/client.tsx
Normal file
26
packages/lib/universal/runtime-env/client.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { PublicEnv } from './types';
|
||||
|
||||
export type RuntimeEnvClientProviderProps = {
|
||||
value: PublicEnv;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const RuntimeEnvContext = React.createContext<PublicEnv | null>(null);
|
||||
|
||||
export const useRuntimeEnv = () => {
|
||||
const context = useContext(RuntimeEnvContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useRuntimeEnv must be used within a RuntimeEnvProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export const RuntimeEnvClientProvider = ({ value, children }: RuntimeEnvClientProviderProps) => {
|
||||
return <RuntimeEnvContext.Provider value={value}>{children}</RuntimeEnvContext.Provider>;
|
||||
};
|
||||
22
packages/lib/universal/runtime-env/get-runtime-env.ts
Normal file
22
packages/lib/universal/runtime-env/get-runtime-env.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { PublicEnv } from './types';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__unstable_runtimeEnv: PublicEnv;
|
||||
}
|
||||
}
|
||||
|
||||
export const getRuntimeEnv = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith('NEXT_PUBLIC_'))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) as PublicEnv;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.__unstable_runtimeEnv) {
|
||||
return window.__unstable_runtimeEnv;
|
||||
}
|
||||
|
||||
throw new Error('RuntimeEnv is not available');
|
||||
};
|
||||
1
packages/lib/universal/runtime-env/index.ts
Normal file
1
packages/lib/universal/runtime-env/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { RuntimeEnvProvider, type RuntimeEnvProviderProps } from './server';
|
||||
29
packages/lib/universal/runtime-env/server.tsx
Normal file
29
packages/lib/universal/runtime-env/server.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { RuntimeEnvClientProvider } from './client';
|
||||
import { PublicEnv } from './types';
|
||||
|
||||
export type RuntimeEnvProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RuntimeEnvProvider = ({ children }: RuntimeEnvProviderProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const publicEnv = Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith('NEXT_PUBLIC_'))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) as PublicEnv;
|
||||
|
||||
return (
|
||||
<RuntimeEnvClientProvider value={publicEnv}>
|
||||
{children}
|
||||
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.__unstable_runtimeEnv = ${JSON.stringify(publicEnv)}`,
|
||||
}}
|
||||
/>
|
||||
</RuntimeEnvClientProvider>
|
||||
);
|
||||
};
|
||||
3
packages/lib/universal/runtime-env/types.ts
Normal file
3
packages/lib/universal/runtime-env/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { PickStartsWith } from '../../types/pick-starts-with';
|
||||
|
||||
export type PublicEnv = PickStartsWith<typeof process.env, 'NEXT_PUBLIC_'>;
|
||||
@ -4,6 +4,7 @@ import { match } from 'ts-pattern';
|
||||
import { DocumentDataType } from '@documenso/prisma/client';
|
||||
|
||||
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||
import { getRuntimeEnv } from '../runtime-env/get-runtime-env';
|
||||
|
||||
type File = {
|
||||
name: string;
|
||||
@ -12,7 +13,9 @@ type File = {
|
||||
};
|
||||
|
||||
export const putFile = async (file: File) => {
|
||||
const { type, data } = await match(process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT)
|
||||
const { NEXT_PUBLIC_UPLOAD_TRANSPORT } = getRuntimeEnv();
|
||||
|
||||
const { type, data } = await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
|
||||
.with('s3', async () => putFileInS3(file))
|
||||
.otherwise(async () => putFileInDatabase(file));
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import path from 'node:path';
|
||||
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
||||
import { getServerComponentSession } from '../../next-auth/get-server-session';
|
||||
import { alphaid } from '../id';
|
||||
import { getRuntimeEnv } from '../runtime-env/get-runtime-env';
|
||||
|
||||
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
|
||||
const client = getS3Client();
|
||||
@ -103,7 +104,9 @@ export const deleteS3File = async (key: string) => {
|
||||
};
|
||||
|
||||
const getS3Client = () => {
|
||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
const { NEXT_PUBLIC_UPLOAD_TRANSPORT } = getRuntimeEnv();
|
||||
|
||||
if (NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
throw new Error('Invalid upload transport');
|
||||
}
|
||||
|
||||
|
||||
20
packages/lib/universal/use-base-url.ts
Normal file
20
packages/lib/universal/use-base-url.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useRuntimeEnv } from './runtime-env/client';
|
||||
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
export const useBaseUrl = () => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = useRuntimeEnv();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (process.env.VERCEL_URL) {
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
}
|
||||
|
||||
if (NEXT_PUBLIC_WEBAPP_URL) {
|
||||
return NEXT_PUBLIC_WEBAPP_URL;
|
||||
}
|
||||
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
};
|
||||
Reference in New Issue
Block a user