@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/vercel.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" });
|
|
@@ -368,7 +442,9 @@ function peajeVercel(opciones, siguiente) {
|
|
|
368
442
|
const destino = await peaje.decidir(entrada);
|
|
369
443
|
if (destino) {
|
|
370
444
|
const headers = new Headers(request.headers);
|
|
445
|
+
for (const h of HEADERS_NO_REENVIADOS) headers.delete(h);
|
|
371
446
|
headers.set("x-forwarded-host", url.host);
|
|
447
|
+
headers.set(PEAJE_HOST_HEADER, url.host);
|
|
372
448
|
headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
373
449
|
return respuestaMiddleware({ "x-middleware-rewrite": destino }, headers);
|
|
374
450
|
}
|
package/dist/vercel.d.cts
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import { OpcionesPeaje } from './index.cjs';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* `@peaje/proxy/vercel`: Routing Middleware de Vercel para proyectos que no
|
|
5
|
-
* son Next (Vite, SPA estática, Remix, SvelteKit en Vercel). Va en
|
|
6
|
-
* `middleware.ts` en la raíz, al lado de `package.json`:
|
|
7
|
-
*
|
|
8
|
-
* import peaje from '@peaje/proxy/vercel'
|
|
9
|
-
* export default peaje({ slug: 'mi-negocio' })
|
|
10
|
-
* export const config = { matcher: ['/((?!assets/|favicon.ico).*)'] }
|
|
11
|
-
*
|
|
12
|
-
* Responde con el mismo contrato de headers que `rewrite()` y `next()` de
|
|
13
|
-
* `@vercel/functions` (y antes `@vercel/edge`, y `NextResponse` en Next):
|
|
14
|
-
* `x-middleware-rewrite` con el destino, `x-middleware-next` para seguir, y
|
|
15
|
-
* `x-middleware-override-headers` + `x-middleware-request-<nombre>` para
|
|
16
|
-
* cambiar los headers de la request. Así no hace falta instalar nada más: la
|
|
17
|
-
* request no pasa por la función, Vercel la reescribe al gateway (método,
|
|
18
|
-
* body y query intactos). Con un middleware propio: `peaje({ slug }, tuyo)`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
3
|
type Contexto = {
|
|
22
4
|
waitUntil(promesa: Promise<unknown>): void;
|
|
23
5
|
};
|
package/dist/vercel.d.ts
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import { OpcionesPeaje } from './index.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* `@peaje/proxy/vercel`: Routing Middleware de Vercel para proyectos que no
|
|
5
|
-
* son Next (Vite, SPA estática, Remix, SvelteKit en Vercel). Va en
|
|
6
|
-
* `middleware.ts` en la raíz, al lado de `package.json`:
|
|
7
|
-
*
|
|
8
|
-
* import peaje from '@peaje/proxy/vercel'
|
|
9
|
-
* export default peaje({ slug: 'mi-negocio' })
|
|
10
|
-
* export const config = { matcher: ['/((?!assets/|favicon.ico).*)'] }
|
|
11
|
-
*
|
|
12
|
-
* Responde con el mismo contrato de headers que `rewrite()` y `next()` de
|
|
13
|
-
* `@vercel/functions` (y antes `@vercel/edge`, y `NextResponse` en Next):
|
|
14
|
-
* `x-middleware-rewrite` con el destino, `x-middleware-next` para seguir, y
|
|
15
|
-
* `x-middleware-override-headers` + `x-middleware-request-<nombre>` para
|
|
16
|
-
* cambiar los headers de la request. Así no hace falta instalar nada más: la
|
|
17
|
-
* request no pasa por la función, Vercel la reescribe al gateway (método,
|
|
18
|
-
* body y query intactos). Con un middleware propio: `peaje({ slug }, tuyo)`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
3
|
type Contexto = {
|
|
22
4
|
waitUntil(promesa: Promise<unknown>): void;
|
|
23
5
|
};
|
package/dist/vercel.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
HEADERS_NO_REENVIADOS,
|
|
3
|
+
PEAJE_HOST_HEADER,
|
|
2
4
|
crearPeaje
|
|
3
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QHUY5N5S.js";
|
|
4
6
|
|
|
5
7
|
// src/vercel.ts
|
|
6
8
|
function respuestaMiddleware(control, requestHeaders) {
|
|
@@ -23,7 +25,9 @@ function peajeVercel(opciones, siguiente) {
|
|
|
23
25
|
const destino = await peaje.decidir(entrada);
|
|
24
26
|
if (destino) {
|
|
25
27
|
const headers = new Headers(request.headers);
|
|
28
|
+
for (const h of HEADERS_NO_REENVIADOS) headers.delete(h);
|
|
26
29
|
headers.set("x-forwarded-host", url.host);
|
|
30
|
+
headers.set(PEAJE_HOST_HEADER, url.host);
|
|
27
31
|
headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
28
32
|
return respuestaMiddleware({ "x-middleware-rewrite": destino }, headers);
|
|
29
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaje/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Peaje for any server: answer HTTP 402 (MPP, x402) for your priced API routes on your own domain. Fetch handler plus Express, Hono and Vercel Routing Middleware adapters.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"url": "git+https://github.com/sandragcarrillo/peaje.git",
|
|
9
9
|
"directory": "packages/peaje-proxy"
|
|
10
10
|
},
|
|
11
|
+
"homepage": "https://github.com/sandragcarrillo/peaje/tree/main/packages/peaje-proxy#readme",
|
|
12
|
+
"bugs": "https://github.com/sandragcarrillo/peaje/issues",
|
|
11
13
|
"type": "module",
|
|
12
14
|
"sideEffects": false,
|
|
13
15
|
"files": [
|
|
14
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE"
|
|
15
18
|
],
|
|
16
19
|
"main": "./dist/index.cjs",
|
|
17
20
|
"module": "./dist/index.js",
|