1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import {
isLocale,
localeCookieName,
localeCookieOptions,
requestHost,
} from "@/lib/localization";
const accountHosts = new Set(["account.progmasoft.com", "account.localhost"]);
const vigetHosts = new Set(["viget.progmasoft.com", "viget.localhost"]);
export function proxy(request: NextRequest) {
const host = requestHost(
request.headers.get("x-forwarded-host"),
request.headers.get("host"),
);
const pathname = request.nextUrl.pathname;
// Language selection is a user preference, not a second canonical URL.
// Consume the query parameter once, persist it across Progmasoft hosts, and
// redirect to the clean URL so crawlers never index preference variants.
// The header control switches in place through a server action; this
// parameter remains for links and for browsers that run no script.
const requestedLocale = request.nextUrl.searchParams.get("lang") ?? undefined;
if (isLocale(requestedLocale)) {
const destination = request.nextUrl.clone();
destination.searchParams.delete("lang");
const forwardedProtocol = request.headers.get("x-forwarded-proto");
const isProductionHost =
host === "progmasoft.com" || host.endsWith(".progmasoft.com");
destination.protocol = isProductionHost
? "https:"
: request.nextUrl.protocol;
const response = NextResponse.redirect(destination);
response.cookies.set(
localeCookieName,
requestedLocale,
localeCookieOptions(
host,
forwardedProtocol === "https" || request.nextUrl.protocol === "https:",
),
);
return response;
}
// The account root has its own landing page, but authentication and dashboard URLs remain stable and readable.
if (accountHosts.has(host) && pathname === "/") {
const destination = new URL("/account", request.url);
// TLS terminates at Nginx. Next derives the public scheme from X-Forwarded-Proto,
// so an unmodified absolute rewrite would attempt TLS against its HTTP-only
// loopback listener and fail before the account page can render.
destination.protocol = "http:";
return NextResponse.rewrite(destination);
}
// ViGet is deployed by Progmaweb but keeps short public catalog URLs. The
// internal /viget tree prevents those routes from colliding with the apex
// organization page and the account surface.
if (vigetHosts.has(host)) {
if (pathname === "/") {
return NextResponse.rewrite(internalRoute(request, "/viget"));
}
// Packages are grouped by the language they are written in and DSL
// plugins by the build system they extend. A group without a catalog of
// its own leads to the one catalog there is.
if (pathname === "/langs/tala" || pathname === "/langs/tala/") {
return NextResponse.rewrite(internalRoute(request, "/viget/langs/tala"));
}
if (pathname === "/dslplugins/tazel" || pathname === "/dslplugins/tazel/") {
return NextResponse.rewrite(
internalRoute(request, "/viget/dslplugins/tazel"),
);
}
if (pathname === "/langs" || pathname === "/langs/") {
return NextResponse.redirect(
new URL("https://viget.progmasoft.com/langs/tala"),
308,
);
}
if (pathname === "/dslplugins" || pathname === "/dslplugins/") {
return NextResponse.redirect(
new URL("https://viget.progmasoft.com/dslplugins/tazel"),
308,
);
}
if (pathname === "/login" || pathname === "/login/") {
return NextResponse.redirect(
new URL("https://account.progmasoft.com/login"),
);
}
if (pathname === "/register" || pathname === "/register/") {
return NextResponse.redirect(
new URL("https://account.progmasoft.com/register"),
);
}
if (pathname === "/recover" || pathname === "/recover/") {
return NextResponse.redirect(
new URL("https://account.progmasoft.com/recover"),
);
}
}
if (
!vigetHosts.has(host) &&
(pathname === "/viget" || pathname.startsWith("/viget/"))
) {
const publicPath = pathname.replace(/^\/viget/, "") || "/";
return NextResponse.redirect(
new URL(publicPath, "https://viget.progmasoft.com"),
);
}
// Corporate hosts must not accidentally expose the account-only landing page as a public navigation destination.
if (!accountHosts.has(host) && pathname === "/account") {
return NextResponse.redirect(new URL("https://account.progmasoft.com/"));
}
return NextResponse.next();
}
function internalRoute(request: NextRequest, pathname: string): URL {
const destination = new URL(pathname, request.url);
destination.protocol = "http:";
return destination;
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
],
};
|