ReboFilesDocs
Empezar3
Qué es ReboFiles
Primeros pasos
Demos para ejecutar
Ponlo en tu app6
Componente de React
Headless: tu propia interfaz
Sin framework
Estilos y temas
Teclado y accesibilidad
Idiomas
Archivos9
Subidas
Vistas previas y el visor
Búsqueda
Cambios en vivo y carpetas grandes
Comentarios
Destacados y campos
Versiones, papelera y espacio
Descargar como .zip
Documentos de Word
El servidor14
La ruta
Frameworks e inicio de sesión
Quién puede hacer qué
Almacenamiento: S3, GCS, Azure y disco
Revisar las subidas
Metadatos: SQLite y Postgres
Esquema de la base de datos
Compartir y enlaces
Actividad y notificaciones
Ganchos, registros y límites
Retención, bloqueos y exportación
Escritorio y otros orígenes
Sincronizar una carpeta
Ir a producción
En equipo1
Asistentes de IA (MCP)
Referencia5
Paquetes
Componentes y hooks
API del cliente
Rutas HTTP
Estado y hoja de ruta
↑↓ para moverte↵ para abrirEsc para cerrar38 páginas

El servidor Ganchos, registros y límites

Ganchos, registros y límites

Lo que el servidor dice de sí mismo, y lo que deja hacer a cada uno con qué frecuencia. Cada cosa es una opción de createDriveHandler, y cada fallo tuyo se registra, no es el de la petición.
Ganchos

Qué se hace, qué falló, cuánto tardó

import pino from 'pino';
import { metrics } from '@opentelemetry/api';
import { createDriveHandler, openTelemetry } from '@rebofiles/server';

const log = pino();

createDriveHandler({
  // …
  // Each thing done: a webhook, an audit of your own, another index, space counted.
  onChange: async ({ activity, entry }) => {
    await fetch(process.env.WEBHOOK!, { method: 'POST', body: JSON.stringify({ ...activity, size: entry?.kind === 'file' ? entry.versions[0].size : 0 }) });
  },
  logger: { error: (message, detail) => log.error({ detail }, message), warn: (message, detail) => log.warn({ detail }, message) },
  onRequest: openTelemetry(metrics.getMeter('rebofiles')),
});
  • A onChange se le dice cada cosa hecha, como la dice la actividad (created, uploaded, version, renamed, moved, trashed, restored, deleted, shared, commented, held…), con la entrada como queda, null si se borró.
  • logger es donde el servidor escribe lo que no respondió: una actividad no guardada, un gancho que falló, un archivo que no se pudo revisar. console si no se da.
  • A onRequest se le dice cada ruta como la dice la lista (GET /entries/:id), su estado, su tiempo y quién preguntó. openTelemetry(meter) lo hace el histograma http.server.request.duration y un contador de los 5xx.
Límites

Cuánto puede pedir cada uno

TipoPorSi no se dice
uploadspersona1000 por minuto
zippersona, y enlace30 por minuto
searchTextpersona120 por minuto
linkPasswordenlace y dirección10 erróneas en 15 minutos, después ni la correcta
linksenlace600 por minuto
requestspersonaninguno
import { createDriveHandler } from '@rebofiles/server';

createDriveHandler({
  // …
  limits: {
    uploads: { max: 200, every: 60_000 },     // starting an upload, by person
    requests: { max: 1200, every: 60_000 },   // every request, by person
  },
  // Wrong passwords of a link counted by the address too, behind a proxy you trust.
  clientIp: (request) => request.headers.get('x-forwarded-for')?.split(',')[0].trim(),
});

Pasado uno, la respuesta es un 429 rate-limited con Retry-After, y el drive lo dice en el idioma de cada persona. limits: false para ninguno.

HTTP

Webhooks

Cada cambio avisado a otro servicio por HTTP, como onChange se lo dice a tu código: sin retrasar nunca la petición que lo hizo.

import { everyChange, verifyWebhook, webhook } from '@rebofiles/server';

createDriveHandler({
  ...options,
  // Each change POSTed as JSON, signed, tried again three times: only these actions, or all.
  onChange: everyChange(
    webhook({ url: 'https://crm.example/hooks/files', secret: process.env.HOOK_SECRET!, actions: ['uploaded', 'version', 'deleted'] }),
    ({ activity }) => audit(activity),
  ),
});

// Where it arrives: the body as it came, checked before it is trusted.
const ok = verifyWebhook(process.env.HOOK_SECRET!, await request.text(), request.headers);
  • El cuerpo: id (el mismo en cada intento), action, at, by, la entry resumida y la activity. Cabeceras X-Rebofiles-Event, X-Rebofiles-Delivery, X-Rebofiles-Timestamp y X-Rebofiles-Signature, un HMAC-SHA256 de la marca de tiempo y el cuerpo.
  • Se reintenta al segundo y a los cuatro, pero no cuando se rechaza por lo que es (4xx); a onFailure se le avisa del que no llegó.
Redis

Más de un proceso

Las cuentas se guardan en el proceso, salvo que un `limiter` tuyo las guarde para todos: unas líneas sobre Redis.

limiter.ts
import { Redis } from 'ioredis';
import type { Limiter } from '@rebofiles/server';

const redis = new Redis(process.env.REDIS_URL!);

/** The same windows for every process: a count per key, gone when its window is. */
export const redisLimiter: Limiter = {
  async hit(key, limit, how) {
    if (how?.peek) {
      const [count, ttl] = await Promise.all([redis.get(key), redis.pttl(key)]);
      return Number(count) >= limit.max ? { ok: false, retryAfter: ttl } : { ok: true, retryAfter: 0 };
    }
    const count = await redis.incr(key);
    if (count === 1) await redis.pexpire(key, limit.every);
    return count <= limit.max ? { ok: true, retryAfter: 0 } : { ok: false, retryAfter: await redis.pttl(key) };
  },
};