feat: add analytics +fix server cors
This commit is contained in:
@ -1,3 +1,4 @@
|
|||||||
|
// Shared Express app (no .listen here)
|
||||||
import pinataSDK from '@pinata/sdk'
|
import pinataSDK from '@pinata/sdk'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
@ -13,7 +14,7 @@ const app = express()
|
|||||||
/**
|
/**
|
||||||
* CORS
|
* CORS
|
||||||
* - Allows localhost dev
|
* - Allows localhost dev
|
||||||
* - Allows your main frontend
|
* - Allows your main frontend(s) via ALLOWED_ORIGINS
|
||||||
* - Allows ANY *.vercel.app (so forks work for non-technical founders)
|
* - Allows ANY *.vercel.app (so forks work for non-technical founders)
|
||||||
*
|
*
|
||||||
* Optional: set ALLOWED_ORIGINS in Vercel env as comma-separated list
|
* Optional: set ALLOWED_ORIGINS in Vercel env as comma-separated list
|
||||||
@ -25,8 +26,9 @@ const explicitAllowed = (process.env.ALLOWED_ORIGINS || '')
|
|||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
const isAllowedOrigin = (origin: string) => {
|
function isAllowedOrigin(origin: string) {
|
||||||
// explicitly allowed
|
// explicitly allowed
|
||||||
|
if (explicitAllowed.includes('*')) return true
|
||||||
if (explicitAllowed.includes(origin)) return true
|
if (explicitAllowed.includes(origin)) return true
|
||||||
|
|
||||||
// local dev
|
// local dev
|
||||||
@ -37,27 +39,33 @@ const isAllowedOrigin = (origin: string) => {
|
|||||||
const host = new URL(origin).hostname
|
const host = new URL(origin).hostname
|
||||||
if (host.endsWith('.vercel.app')) return true
|
if (host.endsWith('.vercel.app')) return true
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore bad origins
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(
|
const corsOptions: cors.CorsOptions = {
|
||||||
cors({
|
|
||||||
origin: (origin, cb) => {
|
origin: (origin, cb) => {
|
||||||
// allow server-to-server / curl / same-origin (no Origin header)
|
// allow server-to-server / curl / same-origin (no Origin header)
|
||||||
if (!origin) return cb(null, true)
|
if (!origin) return cb(null, true)
|
||||||
|
|
||||||
if (isAllowedOrigin(origin)) return cb(null, true)
|
if (isAllowedOrigin(origin)) return cb(null, true)
|
||||||
return cb(null, false)
|
|
||||||
|
// IMPORTANT: return an error (not "false") so it's obvious in logs/debugging
|
||||||
|
return cb(new Error(`CORS blocked for origin: ${origin}`))
|
||||||
},
|
},
|
||||||
methods: ['GET', 'POST', 'OPTIONS'],
|
methods: ['GET', 'POST', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||||
}),
|
credentials: false,
|
||||||
)
|
optionsSuccessStatus: 204,
|
||||||
|
}
|
||||||
|
|
||||||
// Handle preflight requests for ALL routes
|
// Apply CORS to all routes
|
||||||
app.options('*', cors())
|
app.use(cors(corsOptions))
|
||||||
|
|
||||||
|
// Handle preflight requests for ALL routes (with the SAME options)
|
||||||
|
app.options('*', cors(corsOptions))
|
||||||
|
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|
||||||
@ -67,6 +75,16 @@ const pinata =
|
|||||||
? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
|
? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
|
||||||
: new pinataSDK(process.env.PINATA_API_KEY || '', process.env.PINATA_API_SECRET || '')
|
: new pinataSDK(process.env.PINATA_API_KEY || '', process.env.PINATA_API_SECRET || '')
|
||||||
|
|
||||||
|
// Optional: test credentials at cold start (helps a LOT on Vercel)
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
const auth = await (pinata as any).testAuthentication?.()
|
||||||
|
console.log('Pinata auth OK:', auth || 'ok')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Pinata authentication FAILED. Check env vars.', e)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
// Uploads (multipart/form-data)
|
// Uploads (multipart/form-data)
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: multer.memoryStorage(),
|
storage: multer.memoryStorage(),
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
"@perawallet/connect": "^1.4.1",
|
"@perawallet/connect": "^1.4.1",
|
||||||
"@txnlab/use-wallet": "^4.4.0",
|
"@txnlab/use-wallet": "^4.4.0",
|
||||||
"@txnlab/use-wallet-react": "^4.4.0",
|
"@txnlab/use-wallet-react": "^4.4.0",
|
||||||
|
"@vercel/analytics": "^1.6.1",
|
||||||
"@web3auth/base": "^9.7.0",
|
"@web3auth/base": "^9.7.0",
|
||||||
"@web3auth/base-provider": "^9.7.0",
|
"@web3auth/base-provider": "^9.7.0",
|
||||||
"@web3auth/modal": "^9.7.0",
|
"@web3auth/modal": "^9.7.0",
|
||||||
@ -4210,6 +4211,44 @@
|
|||||||
],
|
],
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@vercel/analytics": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@remix-run/react": "^2",
|
||||||
|
"@sveltejs/kit": "^1 || ^2",
|
||||||
|
"next": ">= 13",
|
||||||
|
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"svelte": ">= 4",
|
||||||
|
"vue": "^3",
|
||||||
|
"vue-router": "^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@remix-run/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@sveltejs/kit": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"next": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"svelte": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue-router": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
"@perawallet/connect": "^1.4.1",
|
"@perawallet/connect": "^1.4.1",
|
||||||
"@txnlab/use-wallet": "^4.4.0",
|
"@txnlab/use-wallet": "^4.4.0",
|
||||||
"@txnlab/use-wallet-react": "^4.4.0",
|
"@txnlab/use-wallet-react": "^4.4.0",
|
||||||
|
"@vercel/analytics": "^1.6.1",
|
||||||
"@web3auth/base": "^9.7.0",
|
"@web3auth/base": "^9.7.0",
|
||||||
"@web3auth/base-provider": "^9.7.0",
|
"@web3auth/base-provider": "^9.7.0",
|
||||||
"@web3auth/modal": "^9.7.0",
|
"@web3auth/modal": "^9.7.0",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
|
import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
|
||||||
|
import { Analytics } from '@vercel/analytics/next'
|
||||||
import { SnackbarProvider } from 'notistack'
|
import { SnackbarProvider } from 'notistack'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||||
@ -79,6 +80,7 @@ export default function App() {
|
|||||||
<SnackbarProvider maxSnack={3}>
|
<SnackbarProvider maxSnack={3}>
|
||||||
<WalletProvider manager={walletManager}>
|
<WalletProvider manager={walletManager}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<Analytics />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout />}>
|
<Route element={<Layout />}>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
|||||||
Reference in New Issue
Block a user