@wopr-network/platform-ui-core 1.27.2 → 1.27.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/api-config.ts +34 -45
- package/src/proxy.ts +22 -6
package/package.json
CHANGED
package/src/lib/api-config.ts
CHANGED
|
@@ -1,62 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized API base URL configuration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The API URL is derived at runtime from the hostname — no NEXT_PUBLIC_API_URL
|
|
5
|
+
* env var needed. Convention: UI at `<domain>` → API at `api.<domain>`.
|
|
6
|
+
* Staging: `staging.<domain>` → `staging.api.<domain>`.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* This prevents internal Docker hostnames from leaking into the browser bundle.
|
|
8
|
+
* Falls back to NEXT_PUBLIC_API_URL if set (local dev), then localhost.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { getBrandConfig } from "./brand-config";
|
|
12
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
|
-
parsed = new URL(url);
|
|
38
|
-
} catch {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`NEXT_PUBLIC_API_URL ("${url}") is not a valid URL. In production it must be a public HTTPS URL.`,
|
|
41
|
-
);
|
|
13
|
+
/**
|
|
14
|
+
* Derive the platform API URL from the current hostname.
|
|
15
|
+
* Works on both client (window.location) and server (brand config domain).
|
|
16
|
+
*/
|
|
17
|
+
function resolveApiUrl(): string {
|
|
18
|
+
// 1. Explicit env var — local dev, tests, or override
|
|
19
|
+
const envUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
20
|
+
if (envUrl) return envUrl;
|
|
21
|
+
|
|
22
|
+
// 2. Client-side: derive from browser hostname
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
const host = window.location.hostname;
|
|
25
|
+
const proto = window.location.protocol;
|
|
26
|
+
// localhost → local dev
|
|
27
|
+
if (host === "localhost" || host === "127.0.0.1") {
|
|
28
|
+
return "http://localhost:3001";
|
|
29
|
+
}
|
|
30
|
+
// staging.X.com → staging.api.X.com
|
|
31
|
+
if (host.startsWith("staging.")) {
|
|
32
|
+
const base = host.replace(/^staging\./, "");
|
|
33
|
+
return `${proto}//staging.api.${base}`;
|
|
34
|
+
}
|
|
35
|
+
// X.com → api.X.com
|
|
36
|
+
return `${proto}//api.${host}`;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
// 3. Server-side: derive from brand config domain
|
|
40
|
+
const domain = getBrandConfig().domain;
|
|
41
|
+
if (domain && domain !== "localhost" && domain.includes(".")) {
|
|
42
|
+
return `https://api.${domain}`;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
throw new Error(
|
|
52
|
-
`NEXT_PUBLIC_API_URL ("${url}") uses ${parsed.protocol} but production requires https. Set it to the public HTTPS URL.`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
45
|
+
return "http://localhost:3001";
|
|
55
46
|
}
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
export const PLATFORM_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001";
|
|
48
|
+
export const PLATFORM_BASE_URL = resolveApiUrl();
|
|
60
49
|
|
|
61
50
|
/** Base URL for REST API calls (platform root + /api). */
|
|
62
51
|
export const API_BASE_URL = `${PLATFORM_BASE_URL}/api`;
|
package/src/proxy.ts
CHANGED
|
@@ -5,9 +5,24 @@ import { sanitizeRedirectUrl } from "@/lib/utils";
|
|
|
5
5
|
|
|
6
6
|
const log = logger("middleware");
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/** Derive API origin from request hostname. Convention: api.<domain>. */
|
|
9
|
+
function getApiOrigin(host: string): string {
|
|
10
|
+
// Explicit override for local dev
|
|
11
|
+
if (process.env.NEXT_PUBLIC_API_URL) {
|
|
12
|
+
try {
|
|
13
|
+
return new URL(process.env.NEXT_PUBLIC_API_URL).origin;
|
|
14
|
+
} catch {
|
|
15
|
+
/* fall through */
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!host || host === "localhost" || host.startsWith("localhost:")) return "";
|
|
19
|
+
const hostname = host.split(":")[0];
|
|
20
|
+
if (hostname.startsWith("staging.")) {
|
|
21
|
+
const base = hostname.replace(/^staging\./, "");
|
|
22
|
+
return `https://staging.api.${base}`;
|
|
23
|
+
}
|
|
24
|
+
return `https://api.${hostname}`;
|
|
25
|
+
}
|
|
11
26
|
|
|
12
27
|
/**
|
|
13
28
|
* Only add upgrade-insecure-requests when actually serving over HTTPS.
|
|
@@ -25,8 +40,9 @@ const apiOrigin = process.env.NEXT_PUBLIC_API_URL
|
|
|
25
40
|
const NONCE_STYLES_ENABLED = true;
|
|
26
41
|
|
|
27
42
|
/** Build the CSP header value with a per-request nonce. */
|
|
28
|
-
function buildCsp(nonce: string, requestUrl?: string): string {
|
|
43
|
+
function buildCsp(nonce: string, requestUrl?: string, requestHost?: string): string {
|
|
29
44
|
const isHttps = requestUrl ? requestUrl.startsWith("https://") : false;
|
|
45
|
+
const api = getApiOrigin(requestHost ?? "");
|
|
30
46
|
return [
|
|
31
47
|
"default-src 'self'",
|
|
32
48
|
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://js.stripe.com`,
|
|
@@ -35,7 +51,7 @@ function buildCsp(nonce: string, requestUrl?: string): string {
|
|
|
35
51
|
: ["style-src 'self' 'unsafe-inline'"]),
|
|
36
52
|
"img-src 'self' data: blob:",
|
|
37
53
|
"font-src 'self'",
|
|
38
|
-
`connect-src 'self' https://api.stripe.com${
|
|
54
|
+
`connect-src 'self' https://api.stripe.com${api ? ` ${api}` : ""}`,
|
|
39
55
|
"frame-src https://js.stripe.com",
|
|
40
56
|
"frame-ancestors 'none'",
|
|
41
57
|
"base-uri 'self'",
|
|
@@ -165,7 +181,7 @@ export default async function middleware(request: NextRequest) {
|
|
|
165
181
|
|
|
166
182
|
// Generate a per-request nonce for CSP
|
|
167
183
|
const nonce = crypto.randomUUID();
|
|
168
|
-
const cspHeaderValue = buildCsp(nonce, request.url);
|
|
184
|
+
const cspHeaderValue = buildCsp(nonce, request.url, request.headers.get("host") ?? "");
|
|
169
185
|
|
|
170
186
|
/** Apply CSP and cache-busting headers to any response before returning it. */
|
|
171
187
|
function withCsp(response: NextResponse): NextResponse {
|