@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/LICENSE +21 -0
- package/README.md +11 -2
- package/dist/{chunk-6ADZXHC7.js → chunk-QHUY5N5S.js} +109 -33
- package/dist/express.cjs +107 -33
- package/dist/express.js +1 -1
- package/dist/hono.cjs +107 -33
- package/dist/hono.js +1 -1
- package/dist/index.cjs +107 -33
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/vercel.cjs +109 -33
- package/dist/vercel.d.cts +0 -18
- package/dist/vercel.d.ts +0 -18
- package/dist/vercel.js +5 -1
- package/package.json +5 -2
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
160
|
-
|
|
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 (!
|
|
396
|
+
if (!avisado) {
|
|
320
397
|
avisado = true;
|
|
321
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
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 (!
|
|
403
|
+
if (!avisado) {
|
|
327
404
|
avisado = true;
|
|
328
|
-
|
|
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
|
-
|
|
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
|
};
|