From 5f0fd8876a2afa34012f778a89db74bcf91f5856 Mon Sep 17 00:00:00 2001 From: lucas-neynar Date: Mon, 5 May 2025 15:52:38 -0700 Subject: [PATCH] refactor: move env vars to constants file --- bin/init.js | 4 +++- src/app/app.tsx | 4 ++-- src/app/layout.tsx | 5 +++-- src/app/opengraph-image.tsx | 3 ++- src/app/page.tsx | 28 +++++++++++----------------- src/lib/constants.ts | 8 ++++++++ src/lib/kv.ts | 3 ++- src/lib/neynar.ts | 3 ++- src/lib/notifs.ts | 5 ++--- src/lib/utils.ts | 23 ++++++++++++----------- 10 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 src/lib/constants.ts diff --git a/bin/init.js b/bin/init.js index febbf61..ebf5730 100644 --- a/bin/init.js +++ b/bin/init.js @@ -6,7 +6,7 @@ import { dirname } from 'path'; import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; -import { mnemonicToAccount } from 'viem/accounts'; +import crypto from 'crypto'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -295,6 +295,7 @@ export async function init() { "eslint": "^8", "eslint-config-next": "15.0.3", "localtunnel": "^2.0.2", + "pino-pretty": "^13.0.0", "postcss": "^8", "tailwindcss": "^3.4.1", "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_DESCRIPTION="${answers.description}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`); + fs.appendFileSync(envPath, `\nNEXTAUTH_SECRET="${crypto.randomBytes(32).toString('hex')}"`); if (useNeynar && neynarApiKey && neynarClientId) { fs.appendFileSync(envPath, `\nNEYNAR_API_KEY="${neynarApiKey}"`); fs.appendFileSync(envPath, `\nNEYNAR_CLIENT_ID="${neynarClientId}"`); diff --git a/src/app/app.tsx b/src/app/app.tsx index c9071e9..e58eeaa 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -1,7 +1,7 @@ "use client"; import dynamic from "next/dynamic"; - +import { APP_NAME } from "~/lib/constants"; // note: dynamic import is required for components that use the Frame SDK const Demo = dynamic(() => import("~/components/Demo"), { @@ -9,7 +9,7 @@ const Demo = dynamic(() => import("~/components/Demo"), { }); export default function App( - { title }: { title?: string } = { title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo" } + { title }: { title?: string } = { title: APP_NAME } ) { return ; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 288c052..b7a8dde 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,10 +3,11 @@ import type { Metadata } from "next"; import { getSession } from "~/auth" import "~/app/globals.css"; import { Providers } from "~/app/providers"; +import { APP_NAME, APP_DESCRIPTION } from "~/lib/constants"; export const metadata: Metadata = { - title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo", - description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app", + title: APP_NAME, + description: APP_DESCRIPTION, }; export default async function RootLayout({ diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx index 01b2560..86b07cc 100644 --- a/src/app/opengraph-image.tsx +++ b/src/app/opengraph-image.tsx @@ -1,6 +1,7 @@ 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 = { width: 600, height: 400, diff --git a/src/app/page.tsx b/src/app/page.tsx index b8c5756..5214b54 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,25 +1,19 @@ import { Metadata } from "next"; import App from "./app"; - -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`; +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 framePreviewMetadata = { version: "next", - imageUrl: `${appUrl}/opengraph-image`, + imageUrl: APP_OG_IMAGE_URL, button: { - title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT, + title: APP_BUTTON_TEXT, action: { type: "launch_frame", - name: appName, - url: appUrl, - splashImageUrl, - iconUrl, - splashBackgroundColor: "#f7f7f7", + name: APP_NAME, + url: APP_URL, + splashImageUrl: APP_SPLASH_URL, + iconUrl: APP_ICON_URL, + splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR, }, }, }; @@ -28,10 +22,10 @@ export const revalidate = 300; export async function generateMetadata(): Promise { return { - title: appName, + title: APP_NAME, openGraph: { - title: appName, - description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION, + title: APP_NAME, + description: APP_DESCRIPTION, }, other: { "fc:frame": JSON.stringify(framePreviewMetadata), diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..201983c --- /dev/null +++ b/src/lib/constants.ts @@ -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; diff --git a/src/lib/kv.ts b/src/lib/kv.ts index b49c45b..55477e9 100644 --- a/src/lib/kv.ts +++ b/src/lib/kv.ts @@ -1,5 +1,6 @@ import { FrameNotificationDetails } from "@farcaster/frame-sdk"; import { Redis } from "@upstash/redis"; +import { APP_NAME } from "./constants"; // In-memory fallback storage const localStore = new Map(); @@ -12,7 +13,7 @@ const redis = useRedis ? new Redis({ }) : null; function getUserNotificationDetailsKey(fid: number): string { - return `${process.env.NEXT_PUBLIC_FRAME_NAME}:user:${fid}`; + return `${APP_NAME}:user:${fid}`; } export async function getUserNotificationDetails( diff --git a/src/lib/neynar.ts b/src/lib/neynar.ts index e0e6cf1..44fbb5f 100644 --- a/src/lib/neynar.ts +++ b/src/lib/neynar.ts @@ -1,4 +1,5 @@ import { NeynarAPIClient, Configuration } from '@neynar/nodejs-sdk'; +import { APP_URL } from './constants'; let neynarClient: NeynarAPIClient | null = null; @@ -41,7 +42,7 @@ export async function sendNeynarFrameNotification({ const notification = { title, body, - target_url: process.env.NEXT_PUBLIC_URL!, + target_url: APP_URL, }; const result = await client.publishFrameNotifications({ diff --git a/src/lib/notifs.ts b/src/lib/notifs.ts index 3789d0a..a1dffda 100644 --- a/src/lib/notifs.ts +++ b/src/lib/notifs.ts @@ -3,8 +3,7 @@ import { sendNotificationResponseSchema, } from "@farcaster/frame-sdk"; import { getUserNotificationDetails } from "~/lib/kv"; - -const appUrl = process.env.NEXT_PUBLIC_URL || ""; +import { APP_URL } from "./constants"; type SendFrameNotificationResult = | { @@ -38,7 +37,7 @@ export async function sendFrameNotification({ notificationId: crypto.randomUUID(), title, body, - targetUrl: appUrl, + targetUrl: APP_URL, tokens: [notificationDetails.token], } satisfies SendNotificationRequest), }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 32b437f..392cd65 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,8 @@ import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; 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 { accountAssociation?: { @@ -48,13 +50,12 @@ export async function getFarcasterMetadata(): Promise { } } - const appUrl = process.env.NEXT_PUBLIC_URL; - if (!appUrl) { + if (!APP_URL) { throw new Error('NEXT_PUBLIC_URL not configured'); } // 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); const secretEnvVars = getSecretEnvVars(); @@ -97,19 +98,19 @@ export async function getFarcasterMetadata(): Promise { const neynarClientId = process.env.NEYNAR_CLIENT_ID; const webhookUrl = neynarApiKey && neynarClientId ? `https://api.neynar.com/f/app/${neynarClientId}/event` - : `${appUrl}/api/webhook`; + : `${APP_URL}/api/webhook`; return { accountAssociation, frame: { version: "1", - name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo", - iconUrl: `${appUrl}/icon.png`, - homeUrl: appUrl, - imageUrl: `${appUrl}/opengraph-image`, - buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame", - splashImageUrl: `${appUrl}/splash.png`, - splashBackgroundColor: "#f7f7f7", + name: APP_NAME ?? "Frames v2 Demo", + iconUrl: APP_ICON_URL, + homeUrl: APP_URL, + imageUrl: APP_OG_IMAGE_URL, + buttonTitle: APP_BUTTON_TEXT ?? "Launch Frame", + splashImageUrl: APP_SPLASH_URL, + splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR, webhookUrl, }, };