@qelos/plugin-netlify-api 1.1.0 → 1.1.2
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/README.md +9 -14
- package/functions/qelos-api-proxy.ts +45 -4
- package/index.js +9 -5
- package/manifest.yml +6 -2
- package/package.json +1 -1
- package/scripts/patch-redirects.cjs +3 -3
package/README.md
CHANGED
|
@@ -33,15 +33,9 @@ If your publish directory is not `dist`, set `QELOS_NETLIFY_PUBLISH` to that fol
|
|
|
33
33
|
|
|
34
34
|
## How it proxies (default)
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
**Default:** the bundled serverless function **`qelos-api-proxy`** — `/api/*` is rewritten to `/.netlify/functions/qelos-api-proxy`. That runs your Node proxy (correct `Host` / `x-forwarded-host`, optional `x-bypass-admin`, etc.).
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
So `/api/me` is proxied to `http://your-api-host/api/me` **at the edge**, without a serverless function. That avoids fragile `/.netlify/functions/...` rewrites and matches Nuxt’s `_redirects` behavior.
|
|
41
|
-
|
|
42
|
-
Optional: **`use_function_proxy = true`** — uses the bundled `qelos-api-proxy` serverless function instead (for cases where you must run Node logic on each request).
|
|
43
|
-
|
|
44
|
-
Optional plugin inputs:
|
|
38
|
+
**Optional — CDN direct to API:** set **`use_cdn_proxy = true`** to rewrite `/api/*` → `${QELOS_API_IP}/api/:splat` at the edge **without** invoking the function (faster, but no per-request logic in `qelos-api-proxy.ts`).
|
|
45
39
|
|
|
46
40
|
```toml
|
|
47
41
|
[[plugins]]
|
|
@@ -50,17 +44,18 @@ Optional plugin inputs:
|
|
|
50
44
|
[plugins.inputs]
|
|
51
45
|
api_url = "http://159.203.152.168"
|
|
52
46
|
bypass_admin = true
|
|
53
|
-
|
|
47
|
+
use_cdn_proxy = false
|
|
54
48
|
```
|
|
55
49
|
|
|
56
50
|
- **api_url** — Qelos API base URL (same as `QELOS_API_IP` / `API_HOST`).
|
|
57
|
-
- **bypass_admin** — If `true`, adds header `x-bypass-admin: true`
|
|
58
|
-
- **
|
|
51
|
+
- **bypass_admin** — If `true`, adds header `x-bypass-admin: true` (function mode: in the proxy; CDN mode: on the redirect when supported).
|
|
52
|
+
- **use_cdn_proxy** — If `true`, edge proxy only (no `qelos-api-proxy` function). Default **false**.
|
|
53
|
+
- **use_function_proxy** — Deprecated; set **`false`** to get the same as `use_cdn_proxy = true`.
|
|
59
54
|
|
|
60
55
|
## What it does
|
|
61
56
|
|
|
62
|
-
- **Environment:** Sets `QELOS_API_IP`, `QELOS_NETLIFY_PROXY_MODE` (`
|
|
63
|
-
- **Redirects:** Injects
|
|
64
|
-
- **
|
|
57
|
+
- **Environment:** Sets `QELOS_API_IP`, `QELOS_NETLIFY_PROXY_MODE` (`function` unless CDN).
|
|
58
|
+
- **Redirects:** Injects `/api/*` into `netlify.toml` and **`postbuild`** updates `publish/_redirects` so Nuxt’s catch‑all does not win.
|
|
59
|
+
- **Unless `use_cdn_proxy`:** copies `qelos-api-proxy.ts` into your Netlify functions directory.
|
|
65
60
|
|
|
66
61
|
No need to commit redirects yourself; the plugin and `postbuild` maintain them each build.
|
|
@@ -29,12 +29,18 @@ const ADD_BYPASS_ADMIN_HEADER =
|
|
|
29
29
|
|
|
30
30
|
export const handler: Handler = (event) =>
|
|
31
31
|
new Promise((resolve) => {
|
|
32
|
-
// Build forwarded headers
|
|
32
|
+
// Build forwarded headers. Upstream Qelos expects Host = public app domain (e.g. app.auto.qelos.io),
|
|
33
|
+
// not the raw API IP — use the visitor-facing host (case-insensitive headers; rawUrl fallback).
|
|
33
34
|
const headers: Record<string, string> = {};
|
|
34
|
-
for (const [key, val] of Object.entries(event.headers)) {
|
|
35
|
-
if (val
|
|
35
|
+
for (const [key, val] of Object.entries(event.headers ?? {})) {
|
|
36
|
+
if (val == null || val === "") continue;
|
|
37
|
+
if (Array.isArray(val)) {
|
|
38
|
+
headers[key] = val.join(", ");
|
|
39
|
+
} else if (typeof val === "string") {
|
|
40
|
+
headers[key] = val;
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
|
-
headers["host"] = event
|
|
43
|
+
headers["host"] = publicHostForUpstream(event);
|
|
38
44
|
if (ADD_BYPASS_ADMIN_HEADER) {
|
|
39
45
|
headers["x-bypass-admin"] = "true";
|
|
40
46
|
}
|
|
@@ -86,3 +92,38 @@ export const handler: Handler = (event) =>
|
|
|
86
92
|
if (body) proxyReq.write(body);
|
|
87
93
|
proxyReq.end();
|
|
88
94
|
});
|
|
95
|
+
|
|
96
|
+
function publicHostForUpstream(event: {
|
|
97
|
+
headers: Record<string, string | string[] | undefined>;
|
|
98
|
+
rawUrl: string;
|
|
99
|
+
}): string {
|
|
100
|
+
const forwarded = pickHeader(event.headers, "x-forwarded-host");
|
|
101
|
+
if (forwarded) {
|
|
102
|
+
return forwarded.split(",")[0].trim();
|
|
103
|
+
}
|
|
104
|
+
const h = pickHeader(event.headers, "host");
|
|
105
|
+
if (h) {
|
|
106
|
+
return h;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
return new URL(event.rawUrl).hostname;
|
|
110
|
+
} catch {
|
|
111
|
+
return PROXY_HOST;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function pickHeader(
|
|
116
|
+
headers: Record<string, string | string[] | undefined>,
|
|
117
|
+
name: string,
|
|
118
|
+
): string | undefined {
|
|
119
|
+
const want = name.toLowerCase();
|
|
120
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
121
|
+
if (k.toLowerCase() !== want) continue;
|
|
122
|
+
if (v == null) continue;
|
|
123
|
+
const s = Array.isArray(v) ? v[0] : v;
|
|
124
|
+
if (typeof s === "string" && s.length > 0) {
|
|
125
|
+
return s;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
package/index.js
CHANGED
|
@@ -42,8 +42,12 @@ module.exports = {
|
|
|
42
42
|
inputs.api_url ?? process.env.QELOS_API_IP ?? DEFAULT_API_URL;
|
|
43
43
|
const bypassAdmin =
|
|
44
44
|
inputs.bypass_admin === true || inputs.bypass_admin === 'true';
|
|
45
|
-
const
|
|
46
|
-
inputs.
|
|
45
|
+
const useCdnProxy =
|
|
46
|
+
inputs.use_cdn_proxy === true ||
|
|
47
|
+
inputs.use_cdn_proxy === 'true' ||
|
|
48
|
+
inputs.use_function_proxy === false ||
|
|
49
|
+
inputs.use_function_proxy === 'false';
|
|
50
|
+
const useFunctionProxy = !useCdnProxy;
|
|
47
51
|
|
|
48
52
|
if (!netlifyConfig.build.environment) {
|
|
49
53
|
netlifyConfig.build.environment = {};
|
|
@@ -100,9 +104,9 @@ module.exports = {
|
|
|
100
104
|
process.env.QELOS_API_IP ??
|
|
101
105
|
DEFAULT_API_URL;
|
|
102
106
|
const mode =
|
|
103
|
-
netlifyConfig?.build?.environment?.QELOS_NETLIFY_PROXY_MODE === '
|
|
104
|
-
? '
|
|
105
|
-
: '
|
|
107
|
+
netlifyConfig?.build?.environment?.QELOS_NETLIFY_PROXY_MODE === 'cdn'
|
|
108
|
+
? 'cdn'
|
|
109
|
+
: 'function';
|
|
106
110
|
const changed = patchRedirects(publishDir, apiUrl, mode);
|
|
107
111
|
console.log(
|
|
108
112
|
`[@qelos/plugin-netlify-api] onPostBuild: ${changed ? 'patched' : 'unchanged'} ${path.join(publishDir, '_redirects')} (${mode})`,
|
package/manifest.yml
CHANGED
|
@@ -8,7 +8,11 @@ inputs:
|
|
|
8
8
|
required: false
|
|
9
9
|
description: "If true, the proxy adds header x-bypass-admin: true to every request (so admins are scoped as regular users)."
|
|
10
10
|
default: false
|
|
11
|
-
- name:
|
|
11
|
+
- name: use_cdn_proxy
|
|
12
12
|
required: false
|
|
13
|
-
description: "If true,
|
|
13
|
+
description: "If true, rewrite /api/* directly to api_url at the edge (no serverless function). Default is false — use the bundled qelos-api-proxy function so Host headers and bypass_admin work."
|
|
14
14
|
default: false
|
|
15
|
+
- name: use_function_proxy
|
|
16
|
+
required: false
|
|
17
|
+
description: "Deprecated alias: set false to get CDN direct-to-IP behavior (same as use_cdn_proxy true)."
|
|
18
|
+
default: true
|
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Netlify evaluates this file before netlify.toml; Nuxt `netlify-static` adds
|
|
7
7
|
* `/* → /404.html` — that must come after `/api/*`.
|
|
8
8
|
*
|
|
9
|
-
* Default:
|
|
10
|
-
* Set QELOS_NETLIFY_PROXY_MODE=
|
|
9
|
+
* Default: serverless function `/.netlify/functions/qelos-api-proxy` (QELOS_NETLIFY_PROXY_MODE unset or `function`).
|
|
10
|
+
* Set QELOS_NETLIFY_PROXY_MODE=cdn for edge direct-to-IP (no function).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('node:fs');
|
|
@@ -88,7 +88,7 @@ function resolvePublishDirFromEnv() {
|
|
|
88
88
|
if (require.main === module) {
|
|
89
89
|
const abs = resolvePublishDirFromEnv();
|
|
90
90
|
const mode =
|
|
91
|
-
process.env.QELOS_NETLIFY_PROXY_MODE === '
|
|
91
|
+
process.env.QELOS_NETLIFY_PROXY_MODE === 'cdn' ? 'cdn' : 'function';
|
|
92
92
|
const apiUrl = process.env.QELOS_API_IP;
|
|
93
93
|
const changed = patchRedirects(abs, apiUrl, mode);
|
|
94
94
|
const target = path.join(abs, '_redirects');
|