@peaje/proxy 0.1.2 → 0.2.1
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 +16 -2
- package/dist/{chunk-IU5YMR4J.js → chunk-SWB3HJVY.js} +162 -34
- package/dist/express.cjs +156 -33
- package/dist/express.js +5 -2
- package/dist/hono.cjs +170 -35
- package/dist/hono.d.cts +3 -0
- package/dist/hono.d.ts +3 -0
- package/dist/hono.js +10 -2
- package/dist/index.cjs +161 -34
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +1 -1
- package/dist/vercel.cjs +158 -32
- package/dist/vercel.js +8 -1
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -56,9 +56,54 @@ 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";
|
|
60
87
|
var PEAJE_HOST_HEADER = "x-peaje-forwarded-host";
|
|
61
88
|
|
|
89
|
+
// ../shared/src/kit/bots.ts
|
|
90
|
+
var BOTS_DE_VISITA = [
|
|
91
|
+
{ patron: /OAI-SearchBot/i, tipo: "openai-searchbot" },
|
|
92
|
+
{ patron: /PerplexityBot/i, tipo: "perplexitybot" },
|
|
93
|
+
{ patron: /Googlebot/i, tipo: "googlebot" },
|
|
94
|
+
{ patron: /ClaudeBot|anthropic-ai/i, tipo: "claudebot" },
|
|
95
|
+
{ patron: /GPTBot/i, tipo: "gptbot" }
|
|
96
|
+
];
|
|
97
|
+
function tipoDeBot(ua) {
|
|
98
|
+
if (!ua) return null;
|
|
99
|
+
for (const b of BOTS_DE_VISITA) if (b.patron.test(ua)) return b.tipo;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
var PEAJE_VERIFICADOR_HEADER = "x-peaje-verificador";
|
|
103
|
+
var NO_ES_PAGINA = /\.(png|jpe?g|gif|webp|avif|svg|ico|css|m?js|map|woff2?|ttf|otf|eot|mp4|webm|mp3|wav)$/i;
|
|
104
|
+
var MAX_PATH_LECTURA = 500;
|
|
105
|
+
var MAX_UA_LECTURA = 300;
|
|
106
|
+
|
|
62
107
|
// ../shared/src/kit/proxy-runtime.ts
|
|
63
108
|
var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
|
|
64
109
|
function iguales(a, b) {
|
|
@@ -67,6 +112,38 @@ function iguales(a, b) {
|
|
|
67
112
|
for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
68
113
|
return d === 0;
|
|
69
114
|
}
|
|
115
|
+
var HEADERS_NO_REENVIADOS = ["cookie"];
|
|
116
|
+
function esReservado(path) {
|
|
117
|
+
const canon = pathCanonico(path).toLowerCase();
|
|
118
|
+
return PATHS_RESERVADOS.some((r) => r === "/" ? canon === "/" : canon === r || canon.startsWith(`${r}/`));
|
|
119
|
+
}
|
|
120
|
+
function prefijosApiValidos(prefijos = PREFIJOS_API_POR_DEFECTO) {
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const crudo of prefijos) {
|
|
123
|
+
const segs = segmentosCanonicos(String(crudo ?? "")).map((s) => s.toLowerCase());
|
|
124
|
+
if (segs.length === 0) continue;
|
|
125
|
+
const path = `/${segs.join("/")}`;
|
|
126
|
+
if (esReservado(path)) continue;
|
|
127
|
+
if (!out.includes(`${path}/`)) out.push(`${path}/`);
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function patronPermitido(patron, prefijos) {
|
|
132
|
+
if (!patronReenviable(patron)) return false;
|
|
133
|
+
const segs = patron.split("/").filter(Boolean);
|
|
134
|
+
const literal = (s) => s !== "*" && !s.startsWith(":");
|
|
135
|
+
const bajoPrefijo = prefijos.some((pre) => {
|
|
136
|
+
const ps = pre.split("/").filter(Boolean);
|
|
137
|
+
return segs.length > ps.length && ps.every((s, i) => literal(segs[i]) && segs[i].toLowerCase() === s);
|
|
138
|
+
});
|
|
139
|
+
if (!bajoPrefijo) return false;
|
|
140
|
+
const literales = [];
|
|
141
|
+
for (const s of segs) {
|
|
142
|
+
if (!literal(s)) break;
|
|
143
|
+
literales.push(s);
|
|
144
|
+
}
|
|
145
|
+
return !esReservado(`/${literales.join("/")}`);
|
|
146
|
+
}
|
|
70
147
|
function coincidePatron(patron, pathname) {
|
|
71
148
|
const p = patron.split("/").filter(Boolean);
|
|
72
149
|
const parts = segmentosCanonicos(pathname);
|
|
@@ -103,50 +180,77 @@ function patronReenviable(patron) {
|
|
|
103
180
|
if (segs.length === 0) return false;
|
|
104
181
|
return !(segs.length === 1 && (segs[0] === "*" || segs[0].startsWith(":")));
|
|
105
182
|
}
|
|
183
|
+
function lecturaDeBot(input) {
|
|
184
|
+
const method = input.method.toUpperCase();
|
|
185
|
+
if (method !== "GET" && method !== "HEAD") return null;
|
|
186
|
+
const h = input.headers;
|
|
187
|
+
if (h.has(PEAJE_ORIGIN_HEADER) || h.has(PEAJE_FETCH_HEADER) || h.has(PEAJE_VERIFICADOR_HEADER)) return null;
|
|
188
|
+
const ua = h.get("user-agent");
|
|
189
|
+
if (!ua || !tipoDeBot(ua)) return null;
|
|
190
|
+
const path = input.pathname;
|
|
191
|
+
if (!path.startsWith("/") || path.startsWith("/_next/") || path.startsWith("/_vercel/") || NO_ES_PAGINA.test(path)) return null;
|
|
192
|
+
return { path: path.slice(0, MAX_PATH_LECTURA), method, userAgent: ua.slice(0, MAX_UA_LECTURA) };
|
|
193
|
+
}
|
|
106
194
|
function crearProxyRuntime(opciones) {
|
|
107
195
|
const base = opciones.base.replace(/\/+$/, "");
|
|
108
196
|
const ttl = opciones.ttlMs ?? 6e4;
|
|
109
197
|
const fetchImpl = opciones.fetchImpl ?? fetch;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
patrones: opciones.respaldo?.patrones ?? []
|
|
115
|
-
};
|
|
198
|
+
const rutas = [...opciones.fijas?.rutas ?? RUTAS_PROXY.map((r) => r.path)];
|
|
199
|
+
const prefijos = [...opciones.fijas?.prefijos ?? RUTAS_DINAMICAS.map((r) => r.prefijo)];
|
|
200
|
+
const prefijosApi = prefijosApiValidos(opciones.prefijosApi ?? PREFIJOS_API_POR_DEFECTO);
|
|
201
|
+
let cache = { at: 0, patrones: [] };
|
|
116
202
|
let refrescando = null;
|
|
117
203
|
async function refrescar() {
|
|
118
204
|
try {
|
|
119
205
|
const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
|
|
120
206
|
if (!res.ok) throw new Error(String(res.status));
|
|
121
207
|
const m = await res.json();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
|
|
125
|
-
prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
|
|
126
|
-
patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
|
|
127
|
-
};
|
|
208
|
+
const patrones2 = Array.isArray(m.patrones) ? m.patrones.filter((p) => typeof p?.method === "string" && typeof p?.path === "string") : cache.patrones;
|
|
209
|
+
cache = { at: Date.now(), patrones: patrones2 };
|
|
128
210
|
} catch {
|
|
129
211
|
cache = { ...cache, at: Date.now() };
|
|
130
212
|
} finally {
|
|
131
213
|
refrescando = null;
|
|
132
214
|
}
|
|
133
215
|
}
|
|
134
|
-
async function
|
|
216
|
+
async function patrones() {
|
|
135
217
|
const vencido = Date.now() - cache.at >= ttl;
|
|
136
218
|
if (vencido) {
|
|
137
219
|
refrescando ??= refrescar();
|
|
138
220
|
if (cache.at === 0) await refrescando;
|
|
139
221
|
}
|
|
140
|
-
return cache;
|
|
222
|
+
return cache.patrones;
|
|
141
223
|
}
|
|
142
224
|
return {
|
|
225
|
+
/** Los prefijos de API efectivos, normalizados (para los avisos). */
|
|
226
|
+
prefijosApi,
|
|
143
227
|
/** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
|
|
144
228
|
async sinSecreto() {
|
|
145
229
|
if (opciones.secreto) return [];
|
|
146
|
-
return (await
|
|
230
|
+
return (await patrones()).filter((p) => patronPermitido(p.path, prefijosApi));
|
|
231
|
+
},
|
|
232
|
+
/** Rutas de API con precio que no se reenvían por caer fuera de `prefijosApi` o en un path reservado. */
|
|
233
|
+
async fueraDePrefijos() {
|
|
234
|
+
return (await patrones()).filter((p) => patronReenviable(p.path) && !patronPermitido(p.path, prefijosApi));
|
|
147
235
|
},
|
|
148
236
|
/** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
|
|
149
237
|
cargar: () => refrescando ??= refrescar(),
|
|
238
|
+
/**
|
|
239
|
+
* Si la request es un bot leyendo una página del sitio, le avisa al
|
|
240
|
+
* gateway y devuelve la promesa del aviso (para `waitUntil`); si no, null.
|
|
241
|
+
* El aviso nunca lanza ni demora la página: 3 s de tope y se descarta la
|
|
242
|
+
* respuesta.
|
|
243
|
+
*/
|
|
244
|
+
reportarLectura(input) {
|
|
245
|
+
const lectura = lecturaDeBot(input);
|
|
246
|
+
if (!lectura) return null;
|
|
247
|
+
return fetchImpl(`${base}/kit/visita`, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
headers: { "content-type": "application/json" },
|
|
250
|
+
body: JSON.stringify(lectura),
|
|
251
|
+
signal: AbortSignal.timeout(3e3)
|
|
252
|
+
}).then((res) => res.body?.cancel()).catch(() => void 0);
|
|
253
|
+
},
|
|
150
254
|
/**
|
|
151
255
|
* Decide si la request va al gateway. `method` y `pathname` de la request,
|
|
152
256
|
* `search` con el `?` incluido, y los headers para cortar bucles.
|
|
@@ -155,14 +259,18 @@ function crearProxyRuntime(opciones) {
|
|
|
155
259
|
const secreto = opciones.secreto || null;
|
|
156
260
|
const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
|
|
157
261
|
if (secreto && prueba && iguales(prueba, secreto)) return null;
|
|
158
|
-
const { rutas, prefijos, patrones } = await listas();
|
|
159
262
|
const path = input.pathname;
|
|
160
263
|
const canon = pathCanonico(path).toLowerCase();
|
|
161
264
|
const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
|
|
162
265
|
const fija = (rutas.includes(path) || rutas.some((r) => r.toLowerCase() === canon)) && !(canon === "/llms.txt" && esFetchDePeaje);
|
|
163
266
|
const prefijo = prefijos.some((p) => path.startsWith(p) || canon.startsWith(p.toLowerCase()));
|
|
164
|
-
|
|
165
|
-
|
|
267
|
+
let patron = false;
|
|
268
|
+
if (!fija && !prefijo && secreto !== null && !esReservado(path) && prefijosApi.some((pre) => canon.startsWith(pre))) {
|
|
269
|
+
const method = input.method.toUpperCase();
|
|
270
|
+
patron = (await patrones()).some(
|
|
271
|
+
(p) => p.method.toUpperCase() === method && patronPermitido(p.path, prefijosApi) && coincidePatron(p.path, path)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
166
274
|
if (!fija && !prefijo && !patron) return null;
|
|
167
275
|
return { destino: `${base}${path}${input.search ?? ""}` };
|
|
168
276
|
}
|
|
@@ -307,6 +415,24 @@ function baseDe({ slug, gateway }) {
|
|
|
307
415
|
}
|
|
308
416
|
var SALTO = ["host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "content-length"];
|
|
309
417
|
var avisado = false;
|
|
418
|
+
function avisar(runtime, conSecreto) {
|
|
419
|
+
if (!conSecreto) {
|
|
420
|
+
void runtime.sinSecreto().then((faltan) => {
|
|
421
|
+
if (faltan.length > 0) {
|
|
422
|
+
console.warn(
|
|
423
|
+
`[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.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
void runtime.fueraDePrefijos().then((fuera) => {
|
|
429
|
+
if (fuera.length > 0) {
|
|
430
|
+
console.warn(
|
|
431
|
+
`[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.`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
310
436
|
function _reiniciarAviso() {
|
|
311
437
|
avisado = false;
|
|
312
438
|
}
|
|
@@ -315,24 +441,14 @@ function crearPeaje(opciones) {
|
|
|
315
441
|
const base = baseDe(opciones);
|
|
316
442
|
const secreto = opciones.secret ?? env("PEAJE_ORIGIN_SECRET") ?? null;
|
|
317
443
|
const fetchImpl = opciones.fetch ?? ((...a) => fetch(...a));
|
|
318
|
-
const runtime = crearProxyRuntime({
|
|
319
|
-
|
|
320
|
-
secreto,
|
|
321
|
-
fetchImpl,
|
|
322
|
-
respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
|
|
323
|
-
});
|
|
444
|
+
const runtime = crearProxyRuntime({ base, secreto, fetchImpl, prefijosApi: opciones.apiPrefixes ?? PREFIJOS_API_POR_DEFECTO });
|
|
445
|
+
const reportar = opciones.reportBotVisits !== false;
|
|
324
446
|
return {
|
|
325
447
|
base,
|
|
326
448
|
async decidir({ method, url, headers }) {
|
|
327
|
-
if (!
|
|
449
|
+
if (!avisado) {
|
|
328
450
|
avisado = true;
|
|
329
|
-
|
|
330
|
-
if (faltan.length > 0) {
|
|
331
|
-
console.warn(
|
|
332
|
-
`[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.`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
});
|
|
451
|
+
avisar(runtime, Boolean(secreto));
|
|
336
452
|
}
|
|
337
453
|
const d = await runtime.decidir({ method, pathname: url.pathname, search: url.search, headers });
|
|
338
454
|
return d?.destino ?? null;
|
|
@@ -340,6 +456,7 @@ function crearPeaje(opciones) {
|
|
|
340
456
|
async reenviar(destino, { method, url, headers, body }) {
|
|
341
457
|
const salida = new Headers(headers);
|
|
342
458
|
for (const h of SALTO) salida.delete(h);
|
|
459
|
+
for (const h of HEADERS_NO_REENVIADOS) salida.delete(h);
|
|
343
460
|
const primero = (h) => headers.get(h)?.split(",")[0]?.trim() || null;
|
|
344
461
|
const host = primero("x-forwarded-host") ?? url.host;
|
|
345
462
|
salida.set("x-forwarded-host", host);
|
|
@@ -353,16 +470,26 @@ function crearPeaje(opciones) {
|
|
|
353
470
|
vuelta.delete("transfer-encoding");
|
|
354
471
|
vuelta.delete("connection");
|
|
355
472
|
return new Response(method === "HEAD" ? null : res.body, { status: res.status, statusText: res.statusText, headers: vuelta });
|
|
473
|
+
},
|
|
474
|
+
reportarLectura({ method, url, headers }) {
|
|
475
|
+
return reportar ? runtime.reportarLectura({ method, pathname: url.pathname, headers }) : null;
|
|
356
476
|
}
|
|
357
477
|
};
|
|
358
478
|
}
|
|
359
479
|
function peajeFetch(opciones) {
|
|
360
480
|
const peaje = crearPeaje(opciones);
|
|
361
|
-
return async (request) => {
|
|
481
|
+
return async (request, waitUntil) => {
|
|
362
482
|
const url = new URL(request.url);
|
|
363
483
|
const entrada = { method: request.method.toUpperCase(), url, headers: request.headers };
|
|
364
484
|
const destino = await peaje.decidir(entrada);
|
|
365
|
-
if (!destino)
|
|
485
|
+
if (!destino) {
|
|
486
|
+
const lectura = peaje.reportarLectura(entrada);
|
|
487
|
+
if (lectura) {
|
|
488
|
+
if (waitUntil) waitUntil(lectura);
|
|
489
|
+
else void lectura;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
366
493
|
const body = entrada.method === "GET" || entrada.method === "HEAD" ? null : await request.arrayBuffer();
|
|
367
494
|
return peaje.reenviar(destino, { ...entrada, body });
|
|
368
495
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,19 @@ 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[];
|
|
20
|
+
/**
|
|
21
|
+
* Avisarle al gateway cuando un bot de IA lee una página de la app, para el
|
|
22
|
+
* registro de visitas. Solo viajan la ruta (sin query) y el user-agent del
|
|
23
|
+
* bot. Por defecto `true`.
|
|
24
|
+
*/
|
|
25
|
+
reportBotVisits?: boolean;
|
|
13
26
|
/** Para tests o runtimes sin fetch global. */
|
|
14
27
|
fetch?: typeof fetch;
|
|
15
28
|
};
|
|
@@ -32,15 +45,23 @@ type Peaje = {
|
|
|
32
45
|
reenviar(destino: string, entrada: EntradaDecision & {
|
|
33
46
|
body: BodyInit | null;
|
|
34
47
|
}): Promise<Response>;
|
|
48
|
+
/**
|
|
49
|
+
* Para una request que no fue al gateway: si es un bot de IA leyendo una
|
|
50
|
+
* página, se lo avisa al gateway y devuelve la promesa del aviso (para
|
|
51
|
+
* `waitUntil`). Null si no hay nada que avisar. Nunca lanza.
|
|
52
|
+
*/
|
|
53
|
+
reportarLectura(entrada: EntradaDecision): Promise<void> | null;
|
|
35
54
|
};
|
|
36
55
|
declare function baseDe({ slug, gateway }: Pick<OpcionesPeaje, 'slug' | 'gateway'>): string;
|
|
37
56
|
/** Solo para tests: vuelve a permitir el aviso de secreto faltante. */
|
|
38
57
|
declare function _reiniciarAviso(): void;
|
|
39
58
|
declare function crearPeaje(opciones: OpcionesPeaje): Peaje;
|
|
40
|
-
type PeajeFetch = (request: Request) => Promise<Response | null>;
|
|
59
|
+
type PeajeFetch = (request: Request, waitUntil?: (promesa: Promise<unknown>) => void) => Promise<Response | null>;
|
|
41
60
|
/**
|
|
42
61
|
* `(request) => Response | null`. Null: la request no es de Peaje y la
|
|
43
|
-
* atiende la app. El body se lee (en memoria) solo si va al gateway.
|
|
62
|
+
* atiende la app. El body se lee (en memoria) solo si va al gateway. Con
|
|
63
|
+
* `waitUntil` (Workers, Vercel), el aviso de lectura de un bot corre ahí;
|
|
64
|
+
* sin él, al costado.
|
|
44
65
|
*/
|
|
45
66
|
declare function peajeFetch(opciones: OpcionesPeaje): PeajeFetch;
|
|
46
67
|
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,19 @@ 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[];
|
|
20
|
+
/**
|
|
21
|
+
* Avisarle al gateway cuando un bot de IA lee una página de la app, para el
|
|
22
|
+
* registro de visitas. Solo viajan la ruta (sin query) y el user-agent del
|
|
23
|
+
* bot. Por defecto `true`.
|
|
24
|
+
*/
|
|
25
|
+
reportBotVisits?: boolean;
|
|
13
26
|
/** Para tests o runtimes sin fetch global. */
|
|
14
27
|
fetch?: typeof fetch;
|
|
15
28
|
};
|
|
@@ -32,15 +45,23 @@ type Peaje = {
|
|
|
32
45
|
reenviar(destino: string, entrada: EntradaDecision & {
|
|
33
46
|
body: BodyInit | null;
|
|
34
47
|
}): Promise<Response>;
|
|
48
|
+
/**
|
|
49
|
+
* Para una request que no fue al gateway: si es un bot de IA leyendo una
|
|
50
|
+
* página, se lo avisa al gateway y devuelve la promesa del aviso (para
|
|
51
|
+
* `waitUntil`). Null si no hay nada que avisar. Nunca lanza.
|
|
52
|
+
*/
|
|
53
|
+
reportarLectura(entrada: EntradaDecision): Promise<void> | null;
|
|
35
54
|
};
|
|
36
55
|
declare function baseDe({ slug, gateway }: Pick<OpcionesPeaje, 'slug' | 'gateway'>): string;
|
|
37
56
|
/** Solo para tests: vuelve a permitir el aviso de secreto faltante. */
|
|
38
57
|
declare function _reiniciarAviso(): void;
|
|
39
58
|
declare function crearPeaje(opciones: OpcionesPeaje): Peaje;
|
|
40
|
-
type PeajeFetch = (request: Request) => Promise<Response | null>;
|
|
59
|
+
type PeajeFetch = (request: Request, waitUntil?: (promesa: Promise<unknown>) => void) => Promise<Response | null>;
|
|
41
60
|
/**
|
|
42
61
|
* `(request) => Response | null`. Null: la request no es de Peaje y la
|
|
43
|
-
* atiende la app. El body se lee (en memoria) solo si va al gateway.
|
|
62
|
+
* atiende la app. El body se lee (en memoria) solo si va al gateway. Con
|
|
63
|
+
* `waitUntil` (Workers, Vercel), el aviso de lectura de un bot corre ahí;
|
|
64
|
+
* sin él, al costado.
|
|
44
65
|
*/
|
|
45
66
|
declare function peajeFetch(opciones: OpcionesPeaje): PeajeFetch;
|
|
46
67
|
|