@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 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
- By default the plugin uses **Netlify’s CDN rewrite** to your Qelos API (same pattern as [Netlify’s proxy docs](https://docs.netlify.com/manage/routing/redirects/rewrites-proxies/#proxy-to-another-service)):
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
- - `/api/*` → `${QELOS_API_IP origin}/api/:splat` with status `200` and `force`
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
- use_function_proxy = false
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` on proxied requests (CDN and function modes).
58
- - **use_function_proxy** — If `true`, deploy the bundled `qelos-api-proxy` function and rewrite to it instead of the CDN proxy.
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` (`cdn` or `function`).
63
- - **Redirects:** Injects the `/api/*` rewrite into `netlify.toml` (and **`postbuild`** rewrites `publish/_redirects` so Nuxt’s catch‑all does not win).
64
- - **Function mode only:** Copies `qelos-api-proxy.ts` into your Netlify functions directory.
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, preserving the original Host header
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) headers[key] = 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.headers["host"] ?? PROXY_HOST;
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 useFunctionProxy =
46
- inputs.use_function_proxy === true || inputs.use_function_proxy === 'true';
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 === 'function'
104
- ? 'function'
105
- : 'cdn';
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: use_function_proxy
11
+ - name: use_cdn_proxy
12
12
  required: false
13
- description: "If true, use the bundled serverless function instead of Netlify CDN proxy to api_url (default is CDN rewrite to origin/api/:splat)."
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/plugin-netlify-api",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Netlify build plugin: proxy /api/* to Qelos API and inject config",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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: CDN proxy to `${QELOS_API_IP}/api/:splat` (Netlify’s documented pattern).
10
- * Set QELOS_NETLIFY_PROXY_MODE=function to use the serverless function instead.
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 === 'function' ? 'function' : 'cdn';
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');