refactor: move env vars to constants file

This commit is contained in:
lucas-neynar 2025-05-05 15:52:38 -07:00
parent 40e40543cd
commit 5f0fd8876a
No known key found for this signature in database
10 changed files with 47 additions and 39 deletions

View File

@ -6,7 +6,7 @@ import { dirname } from 'path';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { mnemonicToAccount } from 'viem/accounts'; import crypto from 'crypto';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@ -295,6 +295,7 @@ export async function init() {
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "15.0.3", "eslint-config-next": "15.0.3",
"localtunnel": "^2.0.2", "localtunnel": "^2.0.2",
"pino-pretty": "^13.0.0",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5" "typescript": "^5"
@ -322,6 +323,7 @@ export async function init() {
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`);
fs.appendFileSync(envPath, `\nNEXTAUTH_SECRET="${crypto.randomBytes(32).toString('hex')}"`);
if (useNeynar && neynarApiKey && neynarClientId) { if (useNeynar && neynarApiKey && neynarClientId) {
fs.appendFileSync(envPath, `\nNEYNAR_API_KEY="${neynarApiKey}"`); fs.appendFileSync(envPath, `\nNEYNAR_API_KEY="${neynarApiKey}"`);
fs.appendFileSync(envPath, `\nNEYNAR_CLIENT_ID="${neynarClientId}"`); fs.appendFileSync(envPath, `\nNEYNAR_CLIENT_ID="${neynarClientId}"`);

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { APP_NAME } from "~/lib/constants";
// note: dynamic import is required for components that use the Frame SDK // note: dynamic import is required for components that use the Frame SDK
const Demo = dynamic(() => import("~/components/Demo"), { const Demo = dynamic(() => import("~/components/Demo"), {
@ -9,7 +9,7 @@ const Demo = dynamic(() => import("~/components/Demo"), {
}); });
export default function App( export default function App(
{ title }: { title?: string } = { title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo" } { title }: { title?: string } = { title: APP_NAME }
) { ) {
return <Demo title={title} />; return <Demo title={title} />;
} }

View File

@ -3,10 +3,11 @@ import type { Metadata } from "next";
import { getSession } from "~/auth" import { getSession } from "~/auth"
import "~/app/globals.css"; import "~/app/globals.css";
import { Providers } from "~/app/providers"; import { Providers } from "~/app/providers";
import { APP_NAME, APP_DESCRIPTION } from "~/lib/constants";
export const metadata: Metadata = { export const metadata: Metadata = {
title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo", title: APP_NAME,
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app", description: APP_DESCRIPTION,
}; };
export default async function RootLayout({ export default async function RootLayout({

View File

@ -1,6 +1,7 @@
import { ImageResponse } from "next/og"; import { ImageResponse } from "next/og";
import { APP_NAME } from "~/lib/constants";
export const alt = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames V2 Demo"; export const alt = APP_NAME;
export const size = { export const size = {
width: 600, width: 600,
height: 400, height: 400,

View File

@ -1,25 +1,19 @@
import { Metadata } from "next"; import { Metadata } from "next";
import App from "./app"; import App from "./app";
import { APP_URL, APP_NAME, APP_DESCRIPTION, APP_OG_IMAGE_URL, APP_ICON_URL, APP_SPLASH_URL, APP_SPLASH_BACKGROUND_COLOR, APP_BUTTON_TEXT } from "~/lib/constants";
const appUrl = process.env.NEXT_PUBLIC_URL;
// frame preview metadata
const appName = process.env.NEXT_PUBLIC_FRAME_NAME;
const splashImageUrl = `${appUrl}/splash.png`;
const iconUrl = `${appUrl}/icon.png`;
const framePreviewMetadata = { const framePreviewMetadata = {
version: "next", version: "next",
imageUrl: `${appUrl}/opengraph-image`, imageUrl: APP_OG_IMAGE_URL,
button: { button: {
title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT, title: APP_BUTTON_TEXT,
action: { action: {
type: "launch_frame", type: "launch_frame",
name: appName, name: APP_NAME,
url: appUrl, url: APP_URL,
splashImageUrl, splashImageUrl: APP_SPLASH_URL,
iconUrl, iconUrl: APP_ICON_URL,
splashBackgroundColor: "#f7f7f7", splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
}, },
}, },
}; };
@ -28,10 +22,10 @@ export const revalidate = 300;
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
return { return {
title: appName, title: APP_NAME,
openGraph: { openGraph: {
title: appName, title: APP_NAME,
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION, description: APP_DESCRIPTION,
}, },
other: { other: {
"fc:frame": JSON.stringify(framePreviewMetadata), "fc:frame": JSON.stringify(framePreviewMetadata),

8
src/lib/constants.ts Normal file
View File

@ -0,0 +1,8 @@
export const APP_URL = process.env.NEXT_PUBLIC_URL;
export const APP_NAME = process.env.NEXT_PUBLIC_FRAME_NAME;
export const APP_DESCRIPTION = process.env.NEXT_PUBLIC_FRAME_DESCRIPTION;
export const APP_ICON_URL = `${APP_URL}/icon.png`;
export const APP_OG_IMAGE_URL = `${APP_URL}/opengraph-image`;
export const APP_SPLASH_URL = `${APP_URL}/splash.png`;
export const APP_SPLASH_BACKGROUND_COLOR = "#f7f7f7";
export const APP_BUTTON_TEXT = process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT;

View File

@ -1,5 +1,6 @@
import { FrameNotificationDetails } from "@farcaster/frame-sdk"; import { FrameNotificationDetails } from "@farcaster/frame-sdk";
import { Redis } from "@upstash/redis"; import { Redis } from "@upstash/redis";
import { APP_NAME } from "./constants";
// In-memory fallback storage // In-memory fallback storage
const localStore = new Map<string, FrameNotificationDetails>(); const localStore = new Map<string, FrameNotificationDetails>();
@ -12,7 +13,7 @@ const redis = useRedis ? new Redis({
}) : null; }) : null;
function getUserNotificationDetailsKey(fid: number): string { function getUserNotificationDetailsKey(fid: number): string {
return `${process.env.NEXT_PUBLIC_FRAME_NAME}:user:${fid}`; return `${APP_NAME}:user:${fid}`;
} }
export async function getUserNotificationDetails( export async function getUserNotificationDetails(

View File

@ -1,4 +1,5 @@
import { NeynarAPIClient, Configuration } from '@neynar/nodejs-sdk'; import { NeynarAPIClient, Configuration } from '@neynar/nodejs-sdk';
import { APP_URL } from './constants';
let neynarClient: NeynarAPIClient | null = null; let neynarClient: NeynarAPIClient | null = null;
@ -41,7 +42,7 @@ export async function sendNeynarFrameNotification({
const notification = { const notification = {
title, title,
body, body,
target_url: process.env.NEXT_PUBLIC_URL!, target_url: APP_URL,
}; };
const result = await client.publishFrameNotifications({ const result = await client.publishFrameNotifications({

View File

@ -3,8 +3,7 @@ import {
sendNotificationResponseSchema, sendNotificationResponseSchema,
} from "@farcaster/frame-sdk"; } from "@farcaster/frame-sdk";
import { getUserNotificationDetails } from "~/lib/kv"; import { getUserNotificationDetails } from "~/lib/kv";
import { APP_URL } from "./constants";
const appUrl = process.env.NEXT_PUBLIC_URL || "";
type SendFrameNotificationResult = type SendFrameNotificationResult =
| { | {
@ -38,7 +37,7 @@ export async function sendFrameNotification({
notificationId: crypto.randomUUID(), notificationId: crypto.randomUUID(),
title, title,
body, body,
targetUrl: appUrl, targetUrl: APP_URL,
tokens: [notificationDetails.token], tokens: [notificationDetails.token],
} satisfies SendNotificationRequest), } satisfies SendNotificationRequest),
}); });

View File

@ -1,6 +1,8 @@
import { type ClassValue, clsx } from 'clsx'; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { mnemonicToAccount } from 'viem/accounts'; import { mnemonicToAccount } from 'viem/accounts';
import { APP_BUTTON_TEXT, APP_ICON_URL, APP_NAME, APP_OG_IMAGE_URL, APP_SPLASH_BACKGROUND_COLOR, APP_URL } from './constants';
import { APP_SPLASH_URL } from './constants';
interface FrameMetadata { interface FrameMetadata {
accountAssociation?: { accountAssociation?: {
@ -48,13 +50,12 @@ export async function getFarcasterMetadata(): Promise<FrameMetadata> {
} }
} }
const appUrl = process.env.NEXT_PUBLIC_URL; if (!APP_URL) {
if (!appUrl) {
throw new Error('NEXT_PUBLIC_URL not configured'); throw new Error('NEXT_PUBLIC_URL not configured');
} }
// Get the domain from the URL (without https:// prefix) // Get the domain from the URL (without https:// prefix)
const domain = new URL(appUrl).hostname; const domain = new URL(APP_URL).hostname;
console.log('Using domain for manifest:', domain); console.log('Using domain for manifest:', domain);
const secretEnvVars = getSecretEnvVars(); const secretEnvVars = getSecretEnvVars();
@ -97,19 +98,19 @@ export async function getFarcasterMetadata(): Promise<FrameMetadata> {
const neynarClientId = process.env.NEYNAR_CLIENT_ID; const neynarClientId = process.env.NEYNAR_CLIENT_ID;
const webhookUrl = neynarApiKey && neynarClientId const webhookUrl = neynarApiKey && neynarClientId
? `https://api.neynar.com/f/app/${neynarClientId}/event` ? `https://api.neynar.com/f/app/${neynarClientId}/event`
: `${appUrl}/api/webhook`; : `${APP_URL}/api/webhook`;
return { return {
accountAssociation, accountAssociation,
frame: { frame: {
version: "1", version: "1",
name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo", name: APP_NAME ?? "Frames v2 Demo",
iconUrl: `${appUrl}/icon.png`, iconUrl: APP_ICON_URL,
homeUrl: appUrl, homeUrl: APP_URL,
imageUrl: `${appUrl}/opengraph-image`, imageUrl: APP_OG_IMAGE_URL,
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame", buttonTitle: APP_BUTTON_TEXT ?? "Launch Frame",
splashImageUrl: `${appUrl}/splash.png`, splashImageUrl: APP_SPLASH_URL,
splashBackgroundColor: "#f7f7f7", splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
webhookUrl, webhookUrl,
}, },
}; };