@peaje/proxy 0.1.1 → 0.2.0

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/dist/hono.cjs CHANGED
@@ -52,7 +52,35 @@ var RUTAS_DINAMICAS = [
52
52
  { path: "/r/:slug", prefijo: "/r/", nota: "links con precio: el 402 en tu dominio" },
53
53
  { path: "/checkout_sessions/:id", prefijo: "/checkout_sessions/", nota: "leer una sesi\xF3n de checkout ACP" }
54
54
  ];
55
+ var PREFIJOS_API_POR_DEFECTO = ["/api/", "/v1/"];
56
+ var PATHS_RESERVADOS = [
57
+ "/",
58
+ "/login",
59
+ "/logout",
60
+ "/signin",
61
+ "/signout",
62
+ "/signup",
63
+ "/sign-in",
64
+ "/sign-out",
65
+ "/sign-up",
66
+ "/register",
67
+ "/auth",
68
+ "/oauth",
69
+ "/session",
70
+ "/account",
71
+ "/admin",
72
+ "/dashboard",
73
+ "/_next",
74
+ "/_vercel",
75
+ "/api/auth",
76
+ "/static",
77
+ "/assets",
78
+ "/favicon.ico",
79
+ "/robots.txt",
80
+ "/sitemap.xml"
81
+ ];
55
82
  var PEAJE_FETCH_HEADER = "x-peaje-fetch";
83
+ var PEAJE_HOST_HEADER = "x-peaje-forwarded-host";
56
84
 
57
85
  // ../shared/src/kit/proxy-runtime.ts
58
86
  var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
@@ -62,6 +90,38 @@ function iguales(a, b) {
62
90
  for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
63
91
  return d === 0;
64
92
  }
93
+ var HEADERS_NO_REENVIADOS = ["cookie"];
94
+ function esReservado(path) {
95
+ const canon = pathCanonico(path).toLowerCase();
96
+ return PATHS_RESERVADOS.some((r) => r === "/" ? canon === "/" : canon === r || canon.startsWith(`${r}/`));
97
+ }
98
+ function prefijosApiValidos(prefijos = PREFIJOS_API_POR_DEFECTO) {
99
+ const out = [];
100
+ for (const crudo of prefijos) {
101
+ const segs = segmentosCanonicos(String(crudo ?? "")).map((s) => s.toLowerCase());
102
+ if (segs.length === 0) continue;
103
+ const path = `/${segs.join("/")}`;
104
+ if (esReservado(path)) continue;
105
+ if (!out.includes(`${path}/`)) out.push(`${path}/`);
106
+ }
107
+ return out;
108
+ }
109
+ function patronPermitido(patron, prefijos) {
110
+ if (!patronReenviable(patron)) return false;
111
+ const segs = patron.split("/").filter(Boolean);
112
+ const literal = (s) => s !== "*" && !s.startsWith(":");
113
+ const bajoPrefijo = prefijos.some((pre) => {
114
+ const ps = pre.split("/").filter(Boolean);
115
+ return segs.length > ps.length && ps.every((s, i) => literal(segs[i]) && segs[i].toLowerCase() === s);
116
+ });
117
+ if (!bajoPrefijo) return false;
118
+ const literales = [];
119
+ for (const s of segs) {
120
+ if (!literal(s)) break;
121
+ literales.push(s);
122
+ }
123
+ return !esReservado(`/${literales.join("/")}`);
124
+ }
65
125
  function coincidePatron(patron, pathname) {
66
126
  const p = patron.split("/").filter(Boolean);
67
127
  const parts = segmentosCanonicos(pathname);
@@ -102,43 +162,43 @@ function crearProxyRuntime(opciones) {
102
162
  const base = opciones.base.replace(/\/+$/, "");
103
163
  const ttl = opciones.ttlMs ?? 6e4;
104
164
  const fetchImpl = opciones.fetchImpl ?? fetch;
105
- let cache = {
106
- at: 0,
107
- rutas: opciones.respaldo?.rutas ?? [],
108
- prefijos: opciones.respaldo?.prefijos ?? [],
109
- patrones: opciones.respaldo?.patrones ?? []
110
- };
165
+ const rutas = [...opciones.fijas?.rutas ?? RUTAS_PROXY.map((r) => r.path)];
166
+ const prefijos = [...opciones.fijas?.prefijos ?? RUTAS_DINAMICAS.map((r) => r.prefijo)];
167
+ const prefijosApi = prefijosApiValidos(opciones.prefijosApi ?? PREFIJOS_API_POR_DEFECTO);
168
+ let cache = { at: 0, patrones: [] };
111
169
  let refrescando = null;
112
170
  async function refrescar() {
113
171
  try {
114
172
  const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
115
173
  if (!res.ok) throw new Error(String(res.status));
116
174
  const m = await res.json();
117
- cache = {
118
- at: Date.now(),
119
- rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
120
- prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
121
- patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
122
- };
175
+ const patrones2 = Array.isArray(m.patrones) ? m.patrones.filter((p) => typeof p?.method === "string" && typeof p?.path === "string") : cache.patrones;
176
+ cache = { at: Date.now(), patrones: patrones2 };
123
177
  } catch {
124
178
  cache = { ...cache, at: Date.now() };
125
179
  } finally {
126
180
  refrescando = null;
127
181
  }
128
182
  }
129
- async function listas() {
183
+ async function patrones() {
130
184
  const vencido = Date.now() - cache.at >= ttl;
131
185
  if (vencido) {
132
186
  refrescando ??= refrescar();
133
187
  if (cache.at === 0) await refrescando;
134
188
  }
135
- return cache;
189
+ return cache.patrones;
136
190
  }
137
191
  return {
192
+ /** Los prefijos de API efectivos, normalizados (para los avisos). */
193
+ prefijosApi,
138
194
  /** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
139
195
  async sinSecreto() {
140
196
  if (opciones.secreto) return [];
141
- return (await listas()).patrones.filter((p) => patronReenviable(p.path));
197
+ return (await patrones()).filter((p) => patronPermitido(p.path, prefijosApi));
198
+ },
199
+ /** Rutas de API con precio que no se reenvían por caer fuera de `prefijosApi` o en un path reservado. */
200
+ async fueraDePrefijos() {
201
+ return (await patrones()).filter((p) => patronReenviable(p.path) && !patronPermitido(p.path, prefijosApi));
142
202
  },
143
203
  /** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
144
204
  cargar: () => refrescando ??= refrescar(),
@@ -150,14 +210,18 @@ function crearProxyRuntime(opciones) {
150
210
  const secreto = opciones.secreto || null;
151
211
  const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
152
212
  if (secreto && prueba && iguales(prueba, secreto)) return null;
153
- const { rutas, prefijos, patrones } = await listas();
154
213
  const path = input.pathname;
155
214
  const canon = pathCanonico(path).toLowerCase();
156
215
  const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
157
216
  const fija = (rutas.includes(path) || rutas.some((r) => r.toLowerCase() === canon)) && !(canon === "/llms.txt" && esFetchDePeaje);
158
217
  const prefijo = prefijos.some((p) => path.startsWith(p) || canon.startsWith(p.toLowerCase()));
159
- const method = input.method.toUpperCase();
160
- const patron = secreto !== null && patrones.some((p) => p.method.toUpperCase() === method && patronReenviable(p.path) && coincidePatron(p.path, path));
218
+ let patron = false;
219
+ if (!fija && !prefijo && secreto !== null && !esReservado(path) && prefijosApi.some((pre) => canon.startsWith(pre))) {
220
+ const method = input.method.toUpperCase();
221
+ patron = (await patrones()).some(
222
+ (p) => p.method.toUpperCase() === method && patronPermitido(p.path, prefijosApi) && coincidePatron(p.path, path)
223
+ );
224
+ }
161
225
  if (!fija && !prefijo && !patron) return null;
162
226
  return { destino: `${base}${path}${input.search ?? ""}` };
163
227
  }
@@ -302,29 +366,36 @@ function baseDe({ slug, gateway }) {
302
366
  }
303
367
  var SALTO = ["host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "content-length"];
304
368
  var avisado = false;
369
+ function avisar(runtime, conSecreto) {
370
+ if (!conSecreto) {
371
+ void runtime.sinSecreto().then((faltan) => {
372
+ if (faltan.length > 0) {
373
+ console.warn(
374
+ `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
375
+ );
376
+ }
377
+ });
378
+ }
379
+ void runtime.fueraDePrefijos().then((fuera) => {
380
+ if (fuera.length > 0) {
381
+ console.warn(
382
+ `[peaje] ${fuera.length} priced API route(s) are outside apiPrefixes (${runtime.prefijosApi.join(", ") || "none"}) or on a reserved path, so they are charged only at the gateway URL, not on this domain: ${fuera.map((p) => `${p.method} ${p.path}`).join(", ")}. A route under a prefix of its own (/weather/:city) is charged here once that prefix is in the apiPrefixes option; a single path at the root or a reserved path never is.`
383
+ );
384
+ }
385
+ });
386
+ }
305
387
  function crearPeaje(opciones) {
306
388
  if (!opciones?.slug) throw new Error("@peaje/proxy: `slug` is required (the business slug from your Peaje dashboard).");
307
389
  const base = baseDe(opciones);
308
390
  const secreto = opciones.secret ?? env("PEAJE_ORIGIN_SECRET") ?? null;
309
391
  const fetchImpl = opciones.fetch ?? ((...a) => fetch(...a));
310
- const runtime = crearProxyRuntime({
311
- base,
312
- secreto,
313
- fetchImpl,
314
- respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
315
- });
392
+ const runtime = crearProxyRuntime({ base, secreto, fetchImpl, prefijosApi: opciones.apiPrefixes ?? PREFIJOS_API_POR_DEFECTO });
316
393
  return {
317
394
  base,
318
395
  async decidir({ method, url, headers }) {
319
- if (!secreto && !avisado) {
396
+ if (!avisado) {
320
397
  avisado = true;
321
- void runtime.sinSecreto().then((faltan) => {
322
- if (faltan.length > 0) {
323
- console.warn(
324
- `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
325
- );
326
- }
327
- });
398
+ avisar(runtime, Boolean(secreto));
328
399
  }
329
400
  const d = await runtime.decidir({ method, pathname: url.pathname, search: url.search, headers });
330
401
  return d?.destino ?? null;
@@ -332,8 +403,11 @@ function crearPeaje(opciones) {
332
403
  async reenviar(destino, { method, url, headers, body }) {
333
404
  const salida = new Headers(headers);
334
405
  for (const h of SALTO) salida.delete(h);
406
+ for (const h of HEADERS_NO_REENVIADOS) salida.delete(h);
335
407
  const primero = (h) => headers.get(h)?.split(",")[0]?.trim() || null;
336
- salida.set("x-forwarded-host", primero("x-forwarded-host") ?? url.host);
408
+ const host = primero("x-forwarded-host") ?? url.host;
409
+ salida.set("x-forwarded-host", host);
410
+ salida.set(PEAJE_HOST_HEADER, host);
337
411
  salida.set("x-forwarded-proto", primero("x-forwarded-proto") ?? url.protocol.replace(":", ""));
338
412
  const sinBody = method === "GET" || method === "HEAD";
339
413
  const res = await fetchImpl(destino, { method, headers: salida, body: sinBody ? null : body, redirect: "manual" });
package/dist/hono.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  peajeFetch
3
- } from "./chunk-6ADZXHC7.js";
3
+ } from "./chunk-QHUY5N5S.js";
4
4
 
5
5
  // src/hono.ts
6
6
  function peajeHono(opciones) {
package/dist/index.cjs CHANGED
@@ -56,7 +56,35 @@ var RUTAS_DINAMICAS = [
56
56
  { path: "/r/:slug", prefijo: "/r/", nota: "links con precio: el 402 en tu dominio" },
57
57
  { path: "/checkout_sessions/:id", prefijo: "/checkout_sessions/", nota: "leer una sesi\xF3n de checkout ACP" }
58
58
  ];
59
+ var PREFIJOS_API_POR_DEFECTO = ["/api/", "/v1/"];
60
+ var PATHS_RESERVADOS = [
61
+ "/",
62
+ "/login",
63
+ "/logout",
64
+ "/signin",
65
+ "/signout",
66
+ "/signup",
67
+ "/sign-in",
68
+ "/sign-out",
69
+ "/sign-up",
70
+ "/register",
71
+ "/auth",
72
+ "/oauth",
73
+ "/session",
74
+ "/account",
75
+ "/admin",
76
+ "/dashboard",
77
+ "/_next",
78
+ "/_vercel",
79
+ "/api/auth",
80
+ "/static",
81
+ "/assets",
82
+ "/favicon.ico",
83
+ "/robots.txt",
84
+ "/sitemap.xml"
85
+ ];
59
86
  var PEAJE_FETCH_HEADER = "x-peaje-fetch";
87
+ var PEAJE_HOST_HEADER = "x-peaje-forwarded-host";
60
88
 
61
89
  // ../shared/src/kit/proxy-runtime.ts
62
90
  var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
@@ -66,6 +94,38 @@ function iguales(a, b) {
66
94
  for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
67
95
  return d === 0;
68
96
  }
97
+ var HEADERS_NO_REENVIADOS = ["cookie"];
98
+ function esReservado(path) {
99
+ const canon = pathCanonico(path).toLowerCase();
100
+ return PATHS_RESERVADOS.some((r) => r === "/" ? canon === "/" : canon === r || canon.startsWith(`${r}/`));
101
+ }
102
+ function prefijosApiValidos(prefijos = PREFIJOS_API_POR_DEFECTO) {
103
+ const out = [];
104
+ for (const crudo of prefijos) {
105
+ const segs = segmentosCanonicos(String(crudo ?? "")).map((s) => s.toLowerCase());
106
+ if (segs.length === 0) continue;
107
+ const path = `/${segs.join("/")}`;
108
+ if (esReservado(path)) continue;
109
+ if (!out.includes(`${path}/`)) out.push(`${path}/`);
110
+ }
111
+ return out;
112
+ }
113
+ function patronPermitido(patron, prefijos) {
114
+ if (!patronReenviable(patron)) return false;
115
+ const segs = patron.split("/").filter(Boolean);
116
+ const literal = (s) => s !== "*" && !s.startsWith(":");
117
+ const bajoPrefijo = prefijos.some((pre) => {
118
+ const ps = pre.split("/").filter(Boolean);
119
+ return segs.length > ps.length && ps.every((s, i) => literal(segs[i]) && segs[i].toLowerCase() === s);
120
+ });
121
+ if (!bajoPrefijo) return false;
122
+ const literales = [];
123
+ for (const s of segs) {
124
+ if (!literal(s)) break;
125
+ literales.push(s);
126
+ }
127
+ return !esReservado(`/${literales.join("/")}`);
128
+ }
69
129
  function coincidePatron(patron, pathname) {
70
130
  const p = patron.split("/").filter(Boolean);
71
131
  const parts = segmentosCanonicos(pathname);
@@ -106,43 +166,43 @@ function crearProxyRuntime(opciones) {
106
166
  const base = opciones.base.replace(/\/+$/, "");
107
167
  const ttl = opciones.ttlMs ?? 6e4;
108
168
  const fetchImpl = opciones.fetchImpl ?? fetch;
109
- let cache = {
110
- at: 0,
111
- rutas: opciones.respaldo?.rutas ?? [],
112
- prefijos: opciones.respaldo?.prefijos ?? [],
113
- patrones: opciones.respaldo?.patrones ?? []
114
- };
169
+ const rutas = [...opciones.fijas?.rutas ?? RUTAS_PROXY.map((r) => r.path)];
170
+ const prefijos = [...opciones.fijas?.prefijos ?? RUTAS_DINAMICAS.map((r) => r.prefijo)];
171
+ const prefijosApi = prefijosApiValidos(opciones.prefijosApi ?? PREFIJOS_API_POR_DEFECTO);
172
+ let cache = { at: 0, patrones: [] };
115
173
  let refrescando = null;
116
174
  async function refrescar() {
117
175
  try {
118
176
  const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
119
177
  if (!res.ok) throw new Error(String(res.status));
120
178
  const m = await res.json();
121
- cache = {
122
- at: Date.now(),
123
- rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
124
- prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
125
- patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
126
- };
179
+ const patrones2 = Array.isArray(m.patrones) ? m.patrones.filter((p) => typeof p?.method === "string" && typeof p?.path === "string") : cache.patrones;
180
+ cache = { at: Date.now(), patrones: patrones2 };
127
181
  } catch {
128
182
  cache = { ...cache, at: Date.now() };
129
183
  } finally {
130
184
  refrescando = null;
131
185
  }
132
186
  }
133
- async function listas() {
187
+ async function patrones() {
134
188
  const vencido = Date.now() - cache.at >= ttl;
135
189
  if (vencido) {
136
190
  refrescando ??= refrescar();
137
191
  if (cache.at === 0) await refrescando;
138
192
  }
139
- return cache;
193
+ return cache.patrones;
140
194
  }
141
195
  return {
196
+ /** Los prefijos de API efectivos, normalizados (para los avisos). */
197
+ prefijosApi,
142
198
  /** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
143
199
  async sinSecreto() {
144
200
  if (opciones.secreto) return [];
145
- return (await listas()).patrones.filter((p) => patronReenviable(p.path));
201
+ return (await patrones()).filter((p) => patronPermitido(p.path, prefijosApi));
202
+ },
203
+ /** Rutas de API con precio que no se reenvían por caer fuera de `prefijosApi` o en un path reservado. */
204
+ async fueraDePrefijos() {
205
+ return (await patrones()).filter((p) => patronReenviable(p.path) && !patronPermitido(p.path, prefijosApi));
146
206
  },
147
207
  /** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
148
208
  cargar: () => refrescando ??= refrescar(),
@@ -154,14 +214,18 @@ function crearProxyRuntime(opciones) {
154
214
  const secreto = opciones.secreto || null;
155
215
  const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
156
216
  if (secreto && prueba && iguales(prueba, secreto)) return null;
157
- const { rutas, prefijos, patrones } = await listas();
158
217
  const path = input.pathname;
159
218
  const canon = pathCanonico(path).toLowerCase();
160
219
  const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
161
220
  const fija = (rutas.includes(path) || rutas.some((r) => r.toLowerCase() === canon)) && !(canon === "/llms.txt" && esFetchDePeaje);
162
221
  const prefijo = prefijos.some((p) => path.startsWith(p) || canon.startsWith(p.toLowerCase()));
163
- const method = input.method.toUpperCase();
164
- const patron = secreto !== null && patrones.some((p) => p.method.toUpperCase() === method && patronReenviable(p.path) && coincidePatron(p.path, path));
222
+ let patron = false;
223
+ if (!fija && !prefijo && secreto !== null && !esReservado(path) && prefijosApi.some((pre) => canon.startsWith(pre))) {
224
+ const method = input.method.toUpperCase();
225
+ patron = (await patrones()).some(
226
+ (p) => p.method.toUpperCase() === method && patronPermitido(p.path, prefijosApi) && coincidePatron(p.path, path)
227
+ );
228
+ }
165
229
  if (!fija && !prefijo && !patron) return null;
166
230
  return { destino: `${base}${path}${input.search ?? ""}` };
167
231
  }
@@ -306,6 +370,24 @@ function baseDe({ slug, gateway }) {
306
370
  }
307
371
  var SALTO = ["host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "content-length"];
308
372
  var avisado = false;
373
+ function avisar(runtime, conSecreto) {
374
+ if (!conSecreto) {
375
+ void runtime.sinSecreto().then((faltan) => {
376
+ if (faltan.length > 0) {
377
+ console.warn(
378
+ `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
379
+ );
380
+ }
381
+ });
382
+ }
383
+ void runtime.fueraDePrefijos().then((fuera) => {
384
+ if (fuera.length > 0) {
385
+ console.warn(
386
+ `[peaje] ${fuera.length} priced API route(s) are outside apiPrefixes (${runtime.prefijosApi.join(", ") || "none"}) or on a reserved path, so they are charged only at the gateway URL, not on this domain: ${fuera.map((p) => `${p.method} ${p.path}`).join(", ")}. A route under a prefix of its own (/weather/:city) is charged here once that prefix is in the apiPrefixes option; a single path at the root or a reserved path never is.`
387
+ );
388
+ }
389
+ });
390
+ }
309
391
  function _reiniciarAviso() {
310
392
  avisado = false;
311
393
  }
@@ -314,24 +396,13 @@ function crearPeaje(opciones) {
314
396
  const base = baseDe(opciones);
315
397
  const secreto = opciones.secret ?? env("PEAJE_ORIGIN_SECRET") ?? null;
316
398
  const fetchImpl = opciones.fetch ?? ((...a) => fetch(...a));
317
- const runtime = crearProxyRuntime({
318
- base,
319
- secreto,
320
- fetchImpl,
321
- respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
322
- });
399
+ const runtime = crearProxyRuntime({ base, secreto, fetchImpl, prefijosApi: opciones.apiPrefixes ?? PREFIJOS_API_POR_DEFECTO });
323
400
  return {
324
401
  base,
325
402
  async decidir({ method, url, headers }) {
326
- if (!secreto && !avisado) {
403
+ if (!avisado) {
327
404
  avisado = true;
328
- void runtime.sinSecreto().then((faltan) => {
329
- if (faltan.length > 0) {
330
- console.warn(
331
- `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
332
- );
333
- }
334
- });
405
+ avisar(runtime, Boolean(secreto));
335
406
  }
336
407
  const d = await runtime.decidir({ method, pathname: url.pathname, search: url.search, headers });
337
408
  return d?.destino ?? null;
@@ -339,8 +410,11 @@ function crearPeaje(opciones) {
339
410
  async reenviar(destino, { method, url, headers, body }) {
340
411
  const salida = new Headers(headers);
341
412
  for (const h of SALTO) salida.delete(h);
413
+ for (const h of HEADERS_NO_REENVIADOS) salida.delete(h);
342
414
  const primero = (h) => headers.get(h)?.split(",")[0]?.trim() || null;
343
- salida.set("x-forwarded-host", primero("x-forwarded-host") ?? url.host);
415
+ const host = primero("x-forwarded-host") ?? url.host;
416
+ salida.set("x-forwarded-host", host);
417
+ salida.set(PEAJE_HOST_HEADER, host);
344
418
  salida.set("x-forwarded-proto", primero("x-forwarded-proto") ?? url.protocol.replace(":", ""));
345
419
  const sinBody = method === "GET" || method === "HEAD";
346
420
  const res = await fetchImpl(destino, { method, headers: salida, body: sinBody ? null : body, redirect: "manual" });
package/dist/index.d.cts CHANGED
@@ -10,6 +10,13 @@ type OpcionesPeaje = {
10
10
  secret?: string;
11
11
  /** URL pública del gateway. Por defecto `PEAJE_GATEWAY_URL` o la de producción. */
12
12
  gateway?: string;
13
+ /**
14
+ * Prefijos bajo los que viven tus rutas de API con precio. Una ruta del
15
+ * dashboard fuera de ellos cobra solo en la URL del gateway, no en tu
16
+ * dominio. Por defecto `['/api/', '/v1/']`. La raíz y los paths reservados
17
+ * (`/login`, `/auth`, `/api/auth`, `/_next`...) nunca se aceptan.
18
+ */
19
+ apiPrefixes?: string[];
13
20
  /** Para tests o runtimes sin fetch global. */
14
21
  fetch?: typeof fetch;
15
22
  };
package/dist/index.d.ts CHANGED
@@ -10,6 +10,13 @@ type OpcionesPeaje = {
10
10
  secret?: string;
11
11
  /** URL pública del gateway. Por defecto `PEAJE_GATEWAY_URL` o la de producción. */
12
12
  gateway?: string;
13
+ /**
14
+ * Prefijos bajo los que viven tus rutas de API con precio. Una ruta del
15
+ * dashboard fuera de ellos cobra solo en la URL del gateway, no en tu
16
+ * dominio. Por defecto `['/api/', '/v1/']`. La raíz y los paths reservados
17
+ * (`/login`, `/auth`, `/api/auth`, `/_next`...) nunca se aceptan.
18
+ */
19
+ apiPrefixes?: string[];
13
20
  /** Para tests o runtimes sin fetch global. */
14
21
  fetch?: typeof fetch;
15
22
  };
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  crearPeaje,
6
6
  peajeFetch,
7
7
  src_default
8
- } from "./chunk-6ADZXHC7.js";
8
+ } from "./chunk-QHUY5N5S.js";
9
9
  export {
10
10
  GATEWAY_POR_DEFECTO,
11
11
  _reiniciarAviso,