108 files: README, marketing copy, page titles, meta tags, footer text, email templates, .env.example, UI strings. URLs swapped from papermark.com/papermark.io to dataroom.hanzo.ai. Internal identifiers left intact per repo policy: localStorage keys (papermark.email/papermark.name/last_papermark_login — changing them logs users out), type field names (papermarkUserId), internal helper symbols (PAPERMARK_HEADERS, PapermarkSparkle component, isPapermarkUrl helper), internal-only filenames. LLM.md retains 'Upstream: Papermark' attribution line. Per-locale .po translations regenerate from source on the next lingui-extract CI run. DB migrations untouched (append-only history; rewriting them breaks installed schemas). LICENSE stays AGPL-3.0.
94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
|
|
|
|
import AppMiddleware from "@/lib/middleware/app";
|
|
|
|
import { BLOCKED_PATHNAMES } from "./lib/constants";
|
|
import IncomingWebhookMiddleware, {
|
|
isWebhookPath,
|
|
} from "./lib/middleware/incoming-webhooks";
|
|
import InsightsMiddleware from "./lib/middleware/insights";
|
|
|
|
function isAnalyticsPath(path: string) {
|
|
const pattern = /^\/ingest\/.*/;
|
|
return pattern.test(path);
|
|
}
|
|
|
|
// App domains that should NOT be treated as custom domains
|
|
const APP_DOMAINS = [
|
|
"localhost",
|
|
"dataroom.hanzo.ai",
|
|
"hanzo.ai",
|
|
".vercel.app",
|
|
];
|
|
|
|
function isCustomDomain(host: string) {
|
|
if (process.env.NODE_ENV === "development") {
|
|
return host?.includes(".local") || host?.includes("dataroom.hanzo.dev");
|
|
}
|
|
return !APP_DOMAINS.some((d) =>
|
|
d.startsWith(".") ? host?.endsWith(d) : host?.includes(d),
|
|
);
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
/*
|
|
* Match all paths except for:
|
|
* 1. /api/ routes
|
|
* 2. /_next/ (Next.js internals)
|
|
* 3. /_static (inside /public)
|
|
* 4. /_vercel (Vercel internals)
|
|
* 5. /favicon.ico, /sitemap.xml, /robots.txt (static files)
|
|
*/
|
|
"/((?!api/|_next/|_static|vendor|_icons|_vercel|favicon.ico|sitemap.xml|robots.txt).*)",
|
|
],
|
|
};
|
|
|
|
export default async function middleware(req: NextRequest, ev: NextFetchEvent) {
|
|
const path = req.nextUrl.pathname;
|
|
const host = req.headers.get("host");
|
|
|
|
if (isAnalyticsPath(path)) {
|
|
return InsightsMiddleware(req);
|
|
}
|
|
|
|
// Handle incoming webhooks
|
|
if (isWebhookPath(host)) {
|
|
return IncomingWebhookMiddleware(req);
|
|
}
|
|
|
|
// For custom domains, rewrite to domain viewer routes
|
|
// NOTE: DomainMiddleware is dynamically imported to avoid pulling ioredis
|
|
// into the Edge Runtime bundle (Edge Runtime has no TCP/net APIs)
|
|
if (isCustomDomain(host || "")) {
|
|
const { default: DomainMiddleware } = await import(
|
|
"@/lib/middleware/domain"
|
|
);
|
|
return DomainMiddleware(req);
|
|
}
|
|
|
|
// Handle standard app paths
|
|
if (
|
|
!path.startsWith("/view/") &&
|
|
!path.startsWith("/verify") &&
|
|
!path.startsWith("/unsubscribe") &&
|
|
!path.startsWith("/notification-preferences") &&
|
|
!path.startsWith("/auth/email")
|
|
) {
|
|
return AppMiddleware(req);
|
|
}
|
|
|
|
// Check for blocked pathnames in view routes
|
|
if (
|
|
path.startsWith("/view/") &&
|
|
(BLOCKED_PATHNAMES.some((blockedPath) => path.includes(blockedPath)) ||
|
|
path.includes("."))
|
|
) {
|
|
const url = req.nextUrl.clone();
|
|
url.pathname = "/404";
|
|
return NextResponse.rewrite(url, { status: 404 });
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|