Status-Code
diff --git a/app/api/domains/[id]/chain-check/route.ts b/app/api/domains/[id]/chain-check/route.ts
new file mode 100644
index 0000000..a7b38ab
--- /dev/null
+++ b/app/api/domains/[id]/chain-check/route.ts
@@ -0,0 +1,20 @@
+import { NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/lib/auth";
+import { getDb } from "@/lib/db";
+import { checkRedirectChain } from "@/lib/chain-check";
+
+export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
+ const session = await getServerSession(authOptions);
+ if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
+
+ const { id } = await params;
+ const row = getDb()
+ .prepare("SELECT target_url FROM domains WHERE id = ?")
+ .get(Number(id)) as { target_url: string | null } | undefined;
+
+ if (!row) return NextResponse.json({ error: "not_found" }, { status: 404 });
+ if (!row.target_url) return NextResponse.json({ is_chain: false, hops: 0 });
+
+ return NextResponse.json(await checkRedirectChain(row.target_url));
+}
diff --git a/app/api/domains/[id]/qr/route.ts b/app/api/domains/[id]/qr/route.ts
new file mode 100644
index 0000000..4bec429
--- /dev/null
+++ b/app/api/domains/[id]/qr/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/lib/auth";
+import { getDb } from "@/lib/db";
+import QRCode from "qrcode";
+
+export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
+ const session = await getServerSession(authOptions);
+ if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
+
+ const { id } = await params;
+ const row = getDb()
+ .prepare("SELECT domain FROM domains WHERE id = ?")
+ .get(Number(id)) as { domain: string } | undefined;
+
+ if (!row) return NextResponse.json({ error: "not_found" }, { status: 404 });
+
+ const svg = await QRCode.toString(`https://${row.domain}`, {
+ type: "svg",
+ margin: 2,
+ color: { dark: "#ffffff", light: "#09090b" },
+ });
+
+ return new NextResponse(svg, {
+ headers: {
+ "Content-Type": "image/svg+xml",
+ "Content-Disposition": `attachment; filename="${row.domain}-qr.svg"`,
+ "Cache-Control": "no-cache",
+ },
+ });
+}
diff --git a/app/api/domains/[id]/route.ts b/app/api/domains/[id]/route.ts
index a1be90f..f17631a 100644
--- a/app/api/domains/[id]/route.ts
+++ b/app/api/domains/[id]/route.ts
@@ -20,6 +20,7 @@ const updateSchema = z.object({
redirect_code: z.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)]).optional(),
preserve_path: z.boolean().optional(),
include_www: z.boolean().optional(),
+ catchall_url: z.string().url().nullable().optional(),
sunset_config: sunsetSchema.nullable().optional(),
});
diff --git a/app/api/domains/route.ts b/app/api/domains/route.ts
index 2fda146..b983f69 100644
--- a/app/api/domains/route.ts
+++ b/app/api/domains/route.ts
@@ -29,6 +29,7 @@ const createSchema = z.object({
redirect_code: z.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)]).default(301),
preserve_path: z.boolean().default(true),
include_www: z.boolean().default(true),
+ catchall_url: z.string().url().optional().nullable(),
});
export async function POST(req: Request) {
@@ -40,7 +41,7 @@ export async function POST(req: Request) {
if (!parsed.success) {
return NextResponse.json({ error: "invalid", details: parsed.error.flatten() }, { status: 400 });
}
- const { domain, target_url, group_id, redirect_code, preserve_path, include_www } = parsed.data;
+ const { domain, target_url, group_id, redirect_code, preserve_path, include_www, catchall_url } = parsed.data;
if (!isValidDomain(domain)) return NextResponse.json({ error: "invalid_domain" }, { status: 400 });
if (!target_url && !group_id) return NextResponse.json({ error: "target_required" }, { status: 400 });
@@ -50,8 +51,8 @@ export async function POST(req: Request) {
if (existing) return NextResponse.json({ error: "domain_exists" }, { status: 409 });
const result = db
- .prepare(`INSERT INTO domains (domain, status, target_url, group_id, redirect_code, preserve_path, include_www, created_by, created_at)
- VALUES (?, 'pending', ?, ?, ?, ?, ?, ?, ?)`)
+ .prepare(`INSERT INTO domains (domain, status, target_url, group_id, redirect_code, preserve_path, include_www, catchall_url, created_by, created_at)
+ VALUES (?, 'pending', ?, ?, ?, ?, ?, ?, ?, ?)`)
.run(
domain,
target_url ?? null,
@@ -59,6 +60,7 @@ export async function POST(req: Request) {
redirect_code,
preserve_path ? 1 : 0,
include_www ? 1 : 0,
+ catchall_url ?? null,
Number(session.user.id),
Date.now()
);
diff --git a/lib/chain-check.ts b/lib/chain-check.ts
new file mode 100644
index 0000000..83a78d3
--- /dev/null
+++ b/lib/chain-check.ts
@@ -0,0 +1,44 @@
+export type ChainResult = {
+ is_chain: boolean;
+ hops: number;
+ final_url?: string;
+ error?: string;
+};
+
+export async function checkRedirectChain(url: string): Promise {
+ let current = url;
+ let hops = 0;
+ const maxHops = 5;
+
+ try {
+ while (hops < maxHops) {
+ const res = await fetch(current, {
+ method: "HEAD",
+ redirect: "manual",
+ signal: AbortSignal.timeout(8_000),
+ headers: { "User-Agent": "corex-nexredirect/chain-check" },
+ });
+ if (res.status >= 300 && res.status < 400) {
+ const location = res.headers.get("location");
+ if (!location) break;
+ try {
+ current = new URL(location, current).href;
+ } catch {
+ break;
+ }
+ hops++;
+ } else {
+ break;
+ }
+ }
+ } catch (e) {
+ return {
+ is_chain: hops > 0,
+ hops,
+ final_url: hops > 0 ? current : undefined,
+ error: e instanceof Error ? e.message : String(e),
+ };
+ }
+
+ return { is_chain: hops > 0, hops, final_url: hops > 0 ? current : undefined };
+}
diff --git a/lib/db.ts b/lib/db.ts
index 9c5909f..69a8384 100644
--- a/lib/db.ts
+++ b/lib/db.ts
@@ -165,6 +165,11 @@ function runMigrations(db: Database.Database) {
db.exec("ALTER TABLE users ADD COLUMN username TEXT");
try { db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users(username) WHERE username IS NOT NULL"); } catch {}
}
+
+ // catchall_url: redirect non-root paths to a separate target
+ if (!hasColumn(db, "domains", "catchall_url")) {
+ db.exec("ALTER TABLE domains ADD COLUMN catchall_url TEXT");
+ }
}
export function getSetting(key: string): string | null {
@@ -215,6 +220,7 @@ export type DomainRow = {
redirect_code: number;
preserve_path: number;
include_www: number;
+ catchall_url: string | null;
created_by: number | null;
created_at: number;
verified_at: number | null;
diff --git a/lib/redirect-resolver.ts b/lib/redirect-resolver.ts
index 5a9ea0b..193e121 100644
--- a/lib/redirect-resolver.ts
+++ b/lib/redirect-resolver.ts
@@ -6,6 +6,7 @@ export type ResolvedRedirect = {
target_url: string;
redirect_code: number;
preserve_path: boolean;
+ catchall_url: string | null;
sunset: SunsetConfig | null;
};
@@ -29,6 +30,7 @@ function loadCache(): Map {
target_url: target,
redirect_code: d.redirect_code,
preserve_path: !!d.preserve_path,
+ catchall_url: d.catchall_url ?? null,
sunset: parseSunset(d),
};
m.set(d.domain.toLowerCase(), r);
diff --git a/package-lock.json b/package-lock.json
index 20cf856..bd8e43d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "corex-nexredirect",
- "version": "0.1.34",
+ "version": "0.1.35",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "corex-nexredirect",
- "version": "0.1.34",
+ "version": "0.1.35",
"license": "MIT",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.4",
@@ -25,6 +25,7 @@
"next-auth": "^4.24.14",
"nodemailer": "^8.0.7",
"puppeteer-core": "^24.42.0",
+ "qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recharts": "^2.13.3",
@@ -37,6 +38,7 @@
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.11",
"@types/node": "^20",
+ "@types/qrcode": "^1.5.6",
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.21",
@@ -2131,6 +2133,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@@ -2558,6 +2570,15 @@
"node": "*"
}
},
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -2884,6 +2905,15 @@
}
}
},
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
@@ -2956,6 +2986,12 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"license": "Apache-2.0"
},
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "license": "MIT"
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -3228,6 +3264,19 @@
"node": ">=8"
}
},
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
@@ -3545,6 +3594,18 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -3893,6 +3954,42 @@
"url": "https://github.com/sponsors/panva"
}
},
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
@@ -3925,6 +4022,15 @@
"node": ">= 14"
}
},
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -3973,6 +4079,15 @@
"node": ">= 6"
}
},
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
@@ -4274,6 +4389,89 @@
"node": ">=18"
}
},
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "license": "ISC"
+ },
+ "node_modules/qrcode/node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4514,6 +4712,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "license": "ISC"
+ },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -4615,6 +4819,12 @@
"node": ">=10"
}
},
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC"
+ },
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
@@ -5271,6 +5481,12 @@
"integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
"license": "Apache-2.0"
},
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "license": "ISC"
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index d3f8be0..a3d795e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "corex-nexredirect",
- "version": "0.1.35",
+ "version": "0.1.36",
"license": "MIT",
"overrides": {
"postcss": "^8.5.13",
@@ -31,6 +31,7 @@
"next-auth": "^4.24.14",
"nodemailer": "^8.0.7",
"puppeteer-core": "^24.42.0",
+ "qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recharts": "^2.13.3",
@@ -43,6 +44,7 @@
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.11",
"@types/node": "^20",
+ "@types/qrcode": "^1.5.6",
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.21",
diff --git a/server.ts b/server.ts
index 68a6190..cb38296 100644
--- a/server.ts
+++ b/server.ts
@@ -81,9 +81,13 @@ app.prepare().then(() => {
return;
}
- const target = resolved.preserve_path
- ? resolved.target_url + (parsedUrl.path || "")
- : resolved.target_url;
+ const reqPathname = parsedUrl.pathname || "/";
+ const hasNonRootPath = reqPathname.length > 1;
+ const target = resolved.catchall_url && hasNonRootPath
+ ? resolved.catchall_url
+ : resolved.preserve_path
+ ? resolved.target_url + (parsedUrl.path || "")
+ : resolved.target_url;
res.writeHead(resolved.redirect_code || 302, {
Location: target,
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",