Move rage click and dead click detection (35% of script.js) into script.frustration.js as an optional add-on. Core script drops from 8.1KB to 5.7KB gzipped. Add-on auto-discovers core via window.pulse polling and supports opt-out via data-no-rage/data-no-dead attributes. - Expose cleanPath on window.pulse for add-on consumption - Add script.frustration.js to middleware PUBLIC_ROUTES - Update integration guides, ScriptSetupBlock, and FrustrationTable empty state to reference the add-on script
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
import { NextResponse } from 'next/server'
|
||
import type { NextRequest } from 'next/server'
|
||
|
||
const PUBLIC_ROUTES = new Set([
|
||
'/',
|
||
'/login',
|
||
'/signup',
|
||
'/auth/callback',
|
||
'/pricing',
|
||
'/features',
|
||
'/about',
|
||
'/faq',
|
||
'/changelog',
|
||
'/installation',
|
||
'/script.js', // * Tracking script – must load without auth for embedded sites (Shopify, etc.)
|
||
'/script.frustration.js', // * Frustration tracking add-on (rage clicks, dead clicks)
|
||
])
|
||
|
||
const PUBLIC_PREFIXES = [
|
||
'/share/',
|
||
'/integrations',
|
||
'/docs',
|
||
]
|
||
|
||
function isPublicRoute(pathname: string): boolean {
|
||
if (PUBLIC_ROUTES.has(pathname)) return true
|
||
return PUBLIC_PREFIXES.some((prefix) => pathname.startsWith(prefix))
|
||
}
|
||
|
||
const AUTH_ONLY_ROUTES = new Set(['/login', '/signup'])
|
||
|
||
export function middleware(request: NextRequest) {
|
||
const { pathname } = request.nextUrl
|
||
|
||
const hasAccess = request.cookies.has('access_token')
|
||
const hasRefresh = request.cookies.has('refresh_token')
|
||
const hasSession = hasAccess || hasRefresh
|
||
|
||
// * Authenticated user (with access token) hitting /login or /signup → send them home.
|
||
// * Only check access_token; stale refresh_token alone must not block login (fixes post-inactivity sign-in).
|
||
if (hasAccess && AUTH_ONLY_ROUTES.has(pathname)) {
|
||
return NextResponse.redirect(new URL('/', request.url))
|
||
}
|
||
|
||
// * Public route → allow through
|
||
if (isPublicRoute(pathname)) {
|
||
return NextResponse.next()
|
||
}
|
||
|
||
// * Protected route without a session → redirect to login
|
||
if (!hasSession) {
|
||
const loginUrl = new URL('/login', request.url)
|
||
return NextResponse.redirect(loginUrl)
|
||
}
|
||
|
||
return NextResponse.next()
|
||
}
|
||
|
||
export const config = {
|
||
matcher: [
|
||
/*
|
||
* Match all routes except:
|
||
* - _next/static, _next/image (Next.js internals)
|
||
* - favicon.ico, manifest.json, icons, images (static assets)
|
||
* - api routes (handled by their own auth)
|
||
*/
|
||
'/((?!_next/static|_next/image|favicon\\.ico|manifest\\.json|sitemap\\.xml|robots\\.txt|llms\\.txt|.*\\.png$|.*\\.svg$|.*\\.ico$|api/).*)',
|
||
],
|
||
}
|