mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
Compare commits
1 Commits
v1.12.2-rc
...
feat/runti
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cd60e1abb |
@ -8,10 +8,13 @@ import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-se
|
||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
||||
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||
|
||||
export const createBillingPortal = async () => {
|
||||
const { user } = await getRequiredServerComponentSession();
|
||||
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
||||
|
||||
let stripeCustomer: Stripe.Customer | null = null;
|
||||
@ -43,6 +46,6 @@ export const createBillingPortal = async () => {
|
||||
|
||||
return getPortalSession({
|
||||
customerId: stripeCustomer.id,
|
||||
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
});
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-se
|
||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||
import { Stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
||||
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||
|
||||
export type CreateCheckoutOptions = {
|
||||
priceId: string;
|
||||
@ -18,6 +19,8 @@ export type CreateCheckoutOptions = {
|
||||
export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
||||
const { user } = await getRequiredServerComponentSession();
|
||||
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
||||
|
||||
let stripeCustomer: Stripe.Customer | null = null;
|
||||
@ -32,7 +35,7 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
||||
|
||||
return getPortalSession({
|
||||
customerId: stripeCustomer.id,
|
||||
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,6 +56,6 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
||||
return getCheckoutSession({
|
||||
customerId: stripeCustomer.id,
|
||||
priceId,
|
||||
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,7 +2,8 @@ import { Metadata } from 'next';
|
||||
import { headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { appBaseUrl } from '@documenso/lib/constants/app';
|
||||
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||
|
||||
type SharePageProps = {
|
||||
params: { slug: string };
|
||||
@ -16,12 +17,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
|
||||
title: 'Documenso - Join the open source signing revolution',
|
||||
description: 'I just signed with Documenso!',
|
||||
type: 'website',
|
||||
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
|
||||
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
|
||||
},
|
||||
twitter: {
|
||||
site: '@documenso',
|
||||
card: 'summary_large_image',
|
||||
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
|
||||
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
|
||||
description: 'I just signed with Documenso!',
|
||||
},
|
||||
} satisfies Metadata;
|
||||
@ -30,10 +31,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
|
||||
export default function SharePage() {
|
||||
const userAgent = headers().get('User-Agent') ?? '';
|
||||
|
||||
const { NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||
|
||||
// https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-user-agent
|
||||
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
|
||||
redirect(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/featur
|
||||
import { LocaleProvider } from '@documenso/lib/client-only/providers/locale';
|
||||
import { getServerComponentAllFlags } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
|
||||
import { getLocale } from '@documenso/lib/server-only/headers/get-locale';
|
||||
import { RuntimeEnvProvider } from '@documenso/lib/universal/runtime-env';
|
||||
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||
import { TrpcProvider } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Toaster } from '@documenso/ui/primitives/toaster';
|
||||
@ -17,31 +19,39 @@ import { PostHogPageview } from '~/providers/posthog';
|
||||
|
||||
import './globals.css';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const fontInter = Inter({ subsets: ['latin'], variable: '--font-sans' });
|
||||
const fontCaveat = Caveat({ subsets: ['latin'], variable: '--font-signature' });
|
||||
|
||||
export const metadata = {
|
||||
title: 'Documenso - The Open Source DocuSign Alternative',
|
||||
description:
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
|
||||
keywords:
|
||||
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
|
||||
authors: { name: 'Documenso, Inc.' },
|
||||
robots: 'index, follow',
|
||||
openGraph: {
|
||||
// We do this so NEXT_PUBLIC variables will be evaluated at runtime.
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export const generateMetadata = async () => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
return {
|
||||
title: 'Documenso - The Open Source DocuSign Alternative',
|
||||
description:
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
|
||||
type: 'website',
|
||||
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
||||
},
|
||||
twitter: {
|
||||
site: '@documenso',
|
||||
card: 'summary_large_image',
|
||||
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
||||
description:
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
|
||||
},
|
||||
keywords:
|
||||
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
|
||||
authors: { name: 'Documenso, Inc.' },
|
||||
robots: 'index, follow',
|
||||
openGraph: {
|
||||
title: 'Documenso - The Open Source DocuSign Alternative',
|
||||
description:
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
|
||||
type: 'website',
|
||||
images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
||||
},
|
||||
twitter: {
|
||||
site: '@documenso',
|
||||
card: 'summary_large_image',
|
||||
images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
||||
description:
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
@ -67,19 +77,21 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
</Suspense>
|
||||
|
||||
<body>
|
||||
<LocaleProvider locale={locale}>
|
||||
<FeatureFlagProvider initialFlags={flags}>
|
||||
<PlausibleProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<TooltipProvider>
|
||||
<TrpcProvider>{children}</TrpcProvider>
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</PlausibleProvider>
|
||||
<RuntimeEnvProvider>
|
||||
<LocaleProvider locale={locale}>
|
||||
<FeatureFlagProvider initialFlags={flags}>
|
||||
<PlausibleProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<TooltipProvider>
|
||||
<TrpcProvider>{children}</TrpcProvider>
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</PlausibleProvider>
|
||||
|
||||
<Toaster />
|
||||
</FeatureFlagProvider>
|
||||
</LocaleProvider>
|
||||
<Toaster />
|
||||
</FeatureFlagProvider>
|
||||
</LocaleProvider>
|
||||
</RuntimeEnvProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||
|
||||
/**
|
||||
* getAssetBuffer is used to retrieve array buffers for various assets
|
||||
* that are hosted in the `public` folder.
|
||||
@ -8,7 +10,9 @@
|
||||
* @param path The path to the asset, relative to the `public` folder.
|
||||
*/
|
||||
export const getAssetBuffer = async (path: string) => {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const baseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer());
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ export type GetLimitsOptions = {
|
||||
export const getLimits = async ({ headers }: GetLimitsOptions = {}) => {
|
||||
const requestHeaders = headers ?? {};
|
||||
|
||||
const url = new URL(`${APP_BASE_URL}/api/limits`);
|
||||
const url = new URL('/api/limits', APP_BASE_URL ?? 'http://localhost:3000');
|
||||
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getRuntimeEnv } from '../universal/runtime-env/get-runtime-env';
|
||||
|
||||
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
|
||||
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
|
||||
|
||||
@ -6,3 +8,21 @@ export const APP_FOLDER = IS_APP_MARKETING ? 'marketing' : 'web';
|
||||
export const APP_BASE_URL = IS_APP_WEB
|
||||
? process.env.NEXT_PUBLIC_WEBAPP_URL
|
||||
: process.env.NEXT_PUBLIC_MARKETING_URL;
|
||||
|
||||
export const appBaseUrl = () => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||
|
||||
if (IS_APP_WEB) {
|
||||
return NEXT_PUBLIC_WEBAPP_URL;
|
||||
}
|
||||
|
||||
if (IS_APP_MARKETING) {
|
||||
return NEXT_PUBLIC_MARKETING_URL;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
return NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000';
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { APP_BASE_URL } from './app';
|
||||
import { appBaseUrl } from './app';
|
||||
|
||||
/**
|
||||
* The flag name for global session recording feature flag.
|
||||
@ -25,7 +25,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
|
||||
*/
|
||||
export function extractPostHogConfig(): { key: string; host: string } | null {
|
||||
const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
|
||||
const postHogHost = `${APP_BASE_URL}/ingest`;
|
||||
const postHogHost = `${appBaseUrl()}/ingest`;
|
||||
|
||||
if (!postHogKey || !postHogHost) {
|
||||
return null;
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { APP_BASE_URL } from './app';
|
||||
|
||||
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
||||
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
||||
|
||||
export const MIN_STANDARD_FONT_SIZE = 8;
|
||||
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
||||
|
||||
export const CAVEAT_FONT_PATH = `${APP_BASE_URL}/fonts/caveat.ttf`;
|
||||
export const CAVEAT_FONT_PATH = `/fonts/caveat.ttf`;
|
||||
|
||||
@ -5,11 +5,15 @@ import { render } from '@documenso/email/render';
|
||||
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
|
||||
export interface SendForgotPasswordOptions {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
@ -29,8 +33,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
|
||||
}
|
||||
|
||||
const token = user.PasswordResetToken[0].token;
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const resetPasswordLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`;
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const resetPasswordLink = `${NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`;
|
||||
|
||||
const template = createElement(ForgotPasswordTemplate, {
|
||||
assetBaseUrl,
|
||||
|
||||
@ -5,18 +5,22 @@ import { render } from '@documenso/email/render';
|
||||
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
|
||||
export interface SendResetPasswordOptions {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
const template = createElement(ResetPasswordTemplate, {
|
||||
assetBaseUrl,
|
||||
|
||||
@ -5,6 +5,7 @@ import { render } from '@documenso/email/render';
|
||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
|
||||
export interface SendDocumentOptions {
|
||||
@ -12,6 +13,8 @@ export interface SendDocumentOptions {
|
||||
}
|
||||
|
||||
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const document = await prisma.document.findUnique({
|
||||
where: {
|
||||
id: documentId,
|
||||
@ -36,12 +39,12 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) =>
|
||||
document.Recipient.map(async (recipient) => {
|
||||
const { email, name, token } = recipient;
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||
documentName: document.title,
|
||||
assetBaseUrl,
|
||||
downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
|
||||
downloadLink: `${NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
|
||||
});
|
||||
|
||||
await mailer.sendMail({
|
||||
|
||||
@ -8,12 +8,16 @@ import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-em
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
|
||||
export type SendDocumentOptions = {
|
||||
documentId: number;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
@ -59,8 +63,8 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
return;
|
||||
}
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
|
||||
|
||||
const template = createElement(DocumentInviteEmailTemplate, {
|
||||
documentName: document.title,
|
||||
|
||||
@ -5,12 +5,16 @@ import { render } from '@documenso/email/render';
|
||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
|
||||
export interface SendPendingEmailOptions {
|
||||
documentId: number;
|
||||
recipientId: number;
|
||||
}
|
||||
|
||||
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
id: documentId,
|
||||
@ -41,7 +45,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
|
||||
const { email, name } = recipient;
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
const template = createElement(DocumentPendingEmailTemplate, {
|
||||
documentName: document.title,
|
||||
|
||||
@ -5,12 +5,15 @@ import { getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
|
||||
|
||||
/**
|
||||
* Get all the evaluated feature flags based on the current user if possible.
|
||||
*/
|
||||
export default async function handlerFeatureFlagAll(req: Request) {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||
|
||||
const requestHeaders = Object.fromEntries(req.headers.entries());
|
||||
|
||||
const nextReq = new NextRequest(req, {
|
||||
@ -38,11 +41,11 @@ export default async function handlerFeatureFlagAll(req: Request) {
|
||||
const origin = req.headers.get('origin');
|
||||
|
||||
if (origin) {
|
||||
if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import { JWT, getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||
|
||||
/**
|
||||
* Evaluate a single feature flag based on the current user if possible.
|
||||
*
|
||||
@ -13,6 +15,8 @@ import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-po
|
||||
* @returns A Response with the feature flag value.
|
||||
*/
|
||||
export default async function handleFeatureFlagGet(req: Request) {
|
||||
const { NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||
|
||||
const { searchParams } = new URL(req.url ?? '');
|
||||
const flag = searchParams.get('flag');
|
||||
|
||||
@ -57,11 +61,11 @@ export default async function handleFeatureFlagGet(req: Request) {
|
||||
const origin = req.headers.get('Origin');
|
||||
|
||||
if (origin) {
|
||||
if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,11 @@ import { FieldType } from '@documenso/prisma/client';
|
||||
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
||||
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||
|
||||
import { appBaseUrl } from '../../constants/app';
|
||||
|
||||
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
||||
// Fetch the font file from the public URL.
|
||||
const fontResponse = await fetch(CAVEAT_FONT_PATH);
|
||||
const fontResponse = await fetch(new URL(CAVEAT_FONT_PATH, appBaseUrl()));
|
||||
const fontCaveat = await fontResponse.arrayBuffer();
|
||||
|
||||
const isSignatureField = isSignatureFieldType(field.type);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import fontkit from '@pdf-lib/fontkit';
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
|
||||
import { appBaseUrl } from '../../constants/app';
|
||||
import { CAVEAT_FONT_PATH } from '../../constants/pdf';
|
||||
|
||||
export async function insertTextInPDF(
|
||||
@ -12,7 +13,7 @@ export async function insertTextInPDF(
|
||||
useHandwritingFont = true,
|
||||
): Promise<string> {
|
||||
// Fetch the font file from the public URL.
|
||||
const fontResponse = await fetch(CAVEAT_FONT_PATH);
|
||||
const fontResponse = await fetch(new URL(CAVEAT_FONT_PATH, appBaseUrl()));
|
||||
const fontCaveat = await fontResponse.arrayBuffer();
|
||||
|
||||
const pdfDoc = await PDFDocument.load(pdfAsBase64);
|
||||
|
||||
3
packages/lib/types/pick-starts-with.ts
Normal file
3
packages/lib/types/pick-starts-with.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type PickStartsWith<T extends object, S extends string> = {
|
||||
[K in keyof T as K extends `${S}${string}` ? K : never]: T[K];
|
||||
};
|
||||
@ -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}`;
|
||||
};
|
||||
@ -11,6 +11,7 @@ import {
|
||||
TOAST_DOCUMENT_SHARE_SUCCESS,
|
||||
} from '@documenso/lib/constants/toast';
|
||||
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
|
||||
import { useRuntimeEnv } from '@documenso/lib/universal/runtime-env/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -37,6 +38,7 @@ export const DocumentShareButton = ({
|
||||
trigger,
|
||||
}: DocumentShareButtonProps) => {
|
||||
const { toast } = useToast();
|
||||
const { NEXT_PUBLIC_WEBAPP_URL } = useRuntimeEnv();
|
||||
|
||||
const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({
|
||||
onSuccess: () => toast(TOAST_DOCUMENT_SHARE_SUCCESS),
|
||||
@ -64,7 +66,7 @@ export const DocumentShareButton = ({
|
||||
|
||||
const onCopyClick = async () => {
|
||||
if (shareLink) {
|
||||
await copyShareLink(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`);
|
||||
await copyShareLink(`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`);
|
||||
} else {
|
||||
await createAndCopyShareLink({
|
||||
token,
|
||||
@ -88,7 +90,7 @@ export const DocumentShareButton = ({
|
||||
}
|
||||
|
||||
// Ensuring we've prewarmed the opengraph image for the Twitter
|
||||
await fetch(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${slug}/opengraph`, {
|
||||
await fetch(`${NEXT_PUBLIC_WEBAPP_URL}/share/${slug}/opengraph`, {
|
||||
// We don't care about the response, so we can use no-cors
|
||||
mode: 'no-cors',
|
||||
});
|
||||
@ -96,7 +98,7 @@ export const DocumentShareButton = ({
|
||||
window.open(
|
||||
generateTwitterIntent(
|
||||
`I just ${token ? 'signed' : 'sent'} a document with @documenso. Check it out!`,
|
||||
`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`,
|
||||
`${NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`,
|
||||
),
|
||||
'_blank',
|
||||
);
|
||||
@ -141,7 +143,7 @@ export const DocumentShareButton = ({
|
||||
'animate-pulse': !shareLink?.slug,
|
||||
})}
|
||||
>
|
||||
{process.env.NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'}
|
||||
{NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'}
|
||||
</span>
|
||||
<div
|
||||
className={cn('bg-muted/40 mt-4 aspect-video overflow-hidden rounded-lg border', {
|
||||
@ -150,7 +152,7 @@ export const DocumentShareButton = ({
|
||||
>
|
||||
{shareLink?.slug && (
|
||||
<img
|
||||
src={`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`}
|
||||
src={`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`}
|
||||
alt="sharing link"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user