README_ES.md

August 15, 2026 · View on GitHub

🌐 Otros idiomas: 中文 · English · 日本語 · 한국어 · Français · Deutsch · Русский

Una aplicación de mensajería instantánea cifrada de extremo a extremo, estilo WeChat, con cifrado ECDH + XSalsa20-Poly1305 sin estado por mensaje, videollamadas en tiempo real, almacenamiento de archivos Cloudflare R2, soporte multilingüe y despliegue PWA para iOS.

Rust React TypeScript MySQL Redis WebRTC License: AGPL v3

Deploy on Zeabur

Version

Google Play App Store Windows Mac


📸 Capturas de pantalla (haga clic para expandir) ui1 ui2 ui3 ui4 ui5 ui6 ui7 ui8 ui9 ui10 ui11 ui12 ui13 ui14 ui15 ui16 ui17 ui18

Características

CaracterísticaDescripción
🔐 Cifrado de extremo a extremoECDH sin estado + XSalsa20-Poly1305 — claves efímeras por mensaje, forward secrecy, verificación de número de seguridad estilo Signal
🗝️ Cifrado de conocimiento ceroPara las conversaciones cifradas, el servidor almacena texto cifrado, aunque sigue procesando los metadatos esenciales de cuenta, contactos/grupos, enrutamiento y notificaciones push. Las claves privadas de identidad y las Sender Keys permanecen locales: Web usa IndexedDB protegido con AES-GCM, mientras que Android, iOS, Windows y macOS usan el almacenamiento seguro del sistema operativo
🎭 Aspecto del texto y cifrado adicionalEn Perfil > Privacidad de los mensajes, establece una contraseña adicional para todos los chats de este dispositivo y muestra el contenido con uno de ocho aspectos; admite bloqueo manual y automático
📹 Llamadas de vídeo y vozSFU LiveKit para llamadas 1:1 y reuniones (hasta 100 participantes), silenciar a todos y modo clase
🎙️ Modificador de vozEfectos de voz en tiempo real para mensajes de voz, llamadas 1:1 y llamadas grupales — 3 modos (0.8x grave / 1.0x normal / 1.2x agudo), basado en Web Audio API
📱 Persistencia de sesiónTokens de acceso de 30 minutos con sesiones de actualización de dispositivo de 90 días; recupera cambios de red, IP, VPN o proxy sin pedir la contraseña
📨 Sincronización fiable de mensajesHeartbeat bidireccional, detección de conexiones semimuertas, bandeja de salida persistente, IDs idempotentes y recuperación por cursor de secuencia del servidor
📴 Acceso sin conexiónCaché aislada por cuenta para contactos, grupos, hasta 2.000 mensajes por conversación, Momentos, Línea de tiempo y multimedia; los envíos sin conexión se reintentan automáticamente
🔎 Búsqueda Unicode de amigosLa protección de composición IME, normalización NFC y codificación UTF-8 permiten buscar de forma fiable nombres de usuario y apodos chinos
👥 Chat grupalHasta 2000 miembros, modos "Cifrado" / "Sin cifrar" conmutables (solo propietario, al cambiar se borra el historial del chat). El modo cifrado usa el protocolo Sender Key estilo Signal (cifrado simétrico XSalsa20-Poly1305 + distribución de claves ECDH) — solo los miembros del grupo pueden descifrar los mensajes; los bots están desactivados en modo cifrado. Modo No Molestar, gestión de miembros
👫 Sistema de amigosLas solicitudes de amistad requieren aprobación con hasta 512 caracteres de mensaje; apodos personalizados; agrupación por etiquetas
⏱️ Eliminación automática de mensajes5 niveles (nunca / 1 día / 3 días / 1 semana / 1 mes), configurable por ambas partes en DMs, solo por el propietario en grupos
🔔 Notificaciones pushWeb Push (VAPID) + FCM + OneSignal + ntfy + APNS cinco canales — alcanza usuarios incluso sin conexión (iOS nativo + Android chino sin Google Services)
🌐 MultilingüeChino, inglés, japonés, coreano, francés, alemán, ruso, español — detección automática + cambio manual
📱 iOS — Sin certificado empresarialPWA vía Safari «Agregar a pantalla de inicio», funciona permanentemente sin firma de Apple
📱 App nativa AndroidDisponible en Google Play, con soporte de notificaciones push FCM
📱 App nativa iOSDisponible en App Store, con soporte de notificaciones push APNS
🖥️ Cliente de escritorio WindowsAplicación de escritorio Windows nativa, descargar aquí
🍎 Cliente de escritorio MacAplicación de escritorio Mac nativa, descargar aquí
💬 Mensajería enriquecidaTexto, imágenes, video, archivos de documentos, mensajes de voz, 200+ emojis, paquetes de stickers de Telegram, confirmaciones de lectura, indicadores de escritura
📤 Subida de archivosHasta 500 MB por archivo, Cloudflare R2 o almacenamiento local, con animación de progreso
🌐 MomentosFeed social estilo WeChat: texto + hasta 9 fotos o 1 video (≤ 10 min), likes, comentarios, visibilidad por etiquetas
👤 Perfil de usuarioPágina de perfil de contacto con controles de privacidad bidireccionales de Momentos
📰 Línea de tiempoFeed público estilo Xiaohongshu — diseño masonry de dos columnas, publicaciones anónimas, likes y comentarios
🏷️ Etiquetas de amigosAsignar múltiples etiquetas a amigos (paleta de 12 colores), filtrar contactos por etiqueta
🗂️ Almacenamiento de objetos R2Cloudflare R2 para archivos de imagen/voz — URL CDN pública opcional
🔑 Autenticación de dos factores (2FA)TOTP compatible con Google Authenticator, 8 códigos de recuperación, obligatorio al iniciar sesión
📷 Escanear y compartir código QREscanear códigos QR para agregar amigos o unirse a grupos con expiración configurable
🏗️ Auto-hospedableDocker Compose, Zeabur con un clic, o frontend en Vercel
🌐 Configuración de proxySoporte de proxy SOCKS5 / HTTP / HTTPS — configurable en páginas de inicio de sesión y ajustes con dirección del servidor, puerto, usuario y contraseña para entornos de red restringidos
🛡️ Moderación de contenidoReportes de usuarios (6 categorías) + bloqueo de usuarios (oculta instantáneamente publicaciones/mensajes) + Términos de uso (EULA)
🔧 Panel de administraciónDashboard de administración web integrado (/admin, ruta configurable), protegido por contraseña, revisar reportes, eliminar contenido infractor, banear usuarios — 8 idiomas

Novedades de v2.4.4

  • Se corrigió el diálogo de cifrado adicional bloqueado que pedía configurar una contraseña; ahora solicita la contraseña de desbloqueo en los ocho idiomas.

  • Se corrigió un problema de seguridad que permitía desactivar el cifrado adicional de apariencia de texto sin verificar la contraseña; ahora es obligatorio volver a introducir la contraseña adicional correcta incluso si está desbloqueado.

  • La apariencia de texto ahora oculta prefijos de protocolo, sal e IV; la caché local ya no conserva el texto original.

  • El cifrado adicional se trasladó a Perfil > Privacidad de los mensajes y se aplica globalmente a todos los chats.

  • Los chats cifrados ahora fallan de forma segura: los errores de cifrado, distribución de claves o almacenamiento seguro nunca provocan un envío en texto claro. Cada mensaje muestra el protocolo realmente utilizado (PQ v2, X25519 ↓ o SK vN).

  • Se añadió una contraseña opcional para el historial y ocho códecs de presentación: texto budista, chino aleatorio, símbolos del I Ching, coreano, jeroglíficos egipcios, cuneiforme, texto de valores fundamentales y alfanumérico.

  • Sin la contraseña adicional correcta solo se muestra el texto cifrado de presentación; puede bloquearse automáticamente tras 5/15/30/60 minutos en segundo plano. La contraseña permanece únicamente en memoria.

  • Se reforzó la protección local de claves privadas y Sender Keys con IndexedDB envuelto en AES-GCM en Web y almacenamiento seguro del sistema en clientes nativos; se completó la interfaz en los ocho idiomas.


Novedades de v2.3.9

  • Se corrigieron registros antiguos de amistad unidireccionales que mostraban «Ya son amigos» aunque el contacto seguía invisible y no se podía chatear; al volver a añadirlo ahora se reparan ambas direcciones y se actualiza inmediatamente la lista de contactos.

Novedades de v2.3.8

  • Se corrigió el botón Atrás que no respondía después de iniciar la cámara del escáner QR; al cerrar, la cámara se detiene y libera inmediatamente.
  • Se corrigieron las solicitudes duplicadas a amigos existentes que dañaban la relación; los resultados ahora muestran «Ya son amigos».
  • Los mensajes privados salientes guardan el texto cifrado en el objeto optimista justo después del cifrado de extremo a extremo, evitando persistir texto plano mientras llega la confirmación del servidor.
  • Los mensajes de voz se detienen automáticamente a los 120 segundos; el audio con cambio de voz usa el mismo límite.
  • La pantalla permanece activa durante grabaciones y llamadas, y al salir se liberan de forma fiable dispositivos y temporizadores.
  • Los clientes Android, iOS, Windows y macOS protegen las claves privadas de identidad y las Sender Keys locales con el almacenamiento seguro del sistema operativo; Web usa IndexedDB protegido con AES-GCM. La caché de chats y el almacenamiento de claves privadas son límites de seguridad distintos, lo que reemplaza la antigua descripción de «persistencia en cuatro capas».

Recuperación de sesión y fiabilidad de mensajes

PaperPhonePlus separa el estado local de la cuenta, la conexión en tiempo real y la sincronización de mensajes. Un WebSocket abierto no se considera utilizable hasta recibir auth_ok. Los heartbeats bidireccionales ping/pong detectan conexiones semimuertas por cambios de VPN/IP, transiciones Wi-Fi/móvil o suspensión de la aplicación.

  • Los tokens de acceso duran 30 minutos. Los tokens de actualización del dispositivo duran 90 días y se prolongan durante el uso activo, permitiendo renovar la sesión sin contraseña.
  • Los dispositivos conectados con una versión anterior se actualizan automáticamente mientras su token siga vigente. Si ya caducó, se requiere un último inicio de sesión manual.
  • Cada mensaje saliente tiene una client_msg_id estable. Los mensajes sin ACK permanecen en la bandeja de salida local persistente y se reintentan con el mismo ID; una restricción única evita duplicados.
  • Cada mensaje almacenado tiene una server_seq creciente. El cliente recupera por cursor los mensajes faltantes tras autenticarse, reconectarse o volver al primer plano.
  • El cierre de sesión explícito y la revocación del dispositivo invalidan la sesión duradera. Los fallos de transporte y cambios de IP normales no la eliminan.

Important

En una actualización, despliegue primero el servidor y después los clientes. El servidor aplica y verifica automáticamente la migración de fiabilidad y no arranca si faltan columnas críticas. Haga una copia de seguridad de MySQL antes de actualizar producción.


Stack tecnológico

Backend (server/)
  Rust (Axum 0.8) — Framework web asíncrono de alto rendimiento
  sqlx + MySQL 8.0 — Persistencia de usuarios/mensajes
  deadpool-redis + Redis 7 — Presencia en línea + enrutamiento entre nodos
  aws-sdk-s3 — Almacenamiento de archivos Cloudflare R2 (API compatible con S3)
  Autenticación argon2 + jsonwebtoken

Frontend (client/)
  React 19 + TypeScript + Vite 6
  Gestión de estado Zustand
  libsodium-wrappers-sumo (WebAssembly — Curve25519 / XSalsa20-Poly1305)
  WebRTC API — videollamadas / llamadas de voz
  Web Audio API — modificador de voz en tiempo real (cadena de audio ScriptProcessorNode)
  PWA: manifest.json + Service Worker

Capa criptográfica
  ECDH sin estado + XSalsa20-Poly1305 — par de claves efímero por mensaje
  Protección local de claves: IndexedDB protegido con AES-GCM en Web; almacenamiento seguro del sistema en Android/iOS/Windows/macOS
  Las claves privadas de identidad y las Sender Keys permanecen locales y nunca se envían al servidor

📖 Guía de despliegue detallada (中文) | Deployment Guide (English) — Instrucciones paso a paso completas para el despliegue híbrido Zeabur + Vercel, despliegue local con Docker Compose + Nginx, y configuración de la dirección del servidor del cliente.

Opción 0: Despliegue en la nube con Zeabur en un clic

Deploy on Zeabur

Limitación de red de Zeabur: la plantilla despliega LiveKit mediante WebSocket/API 7880 e ICE/TCP 7881. Zeabur actualmente no expone puertos UDP, por lo que las llamadas 1:1 y las reuniones usan TCP como alternativa. Para llamadas de producción, use LiveKit Cloud o una VM compatible con UDP.

Configuración Nginx del servidor

Use la configuración de producción de dos dominios deploy/nginx/paperphone-plus.conf. Sustituya api.example.com y meeting.example.com, obtenga certificados TLS, copie el archivo a /etc/nginx/sites-available/paperphone-plus, actívelo y ejecute sudo nginx -t && sudo systemctl reload nginx. Configure LIVEKIT_URL=wss://meeting.example.com en el backend. Nginx solo reenvía API y WebSocket; exponga TCP 7881 y UDP 7882 directamente en los cortafuegos.

Tip

Avanzado: Despliegue híbrido Zeabur + Vercel Después de desplegar en Zeabur, puede eliminar manualmente el servicio client y desplegar el frontend en Vercel en su lugar (ver Opción 2 a continuación). De esta forma, server/MySQL/Redis se alojan en Zeabur mientras el frontend se acelera mediante el CDN global de Vercel. El frontend no requiere variables de entorno en Vercel — los usuarios simplemente ingresan la dirección del servidor backend en la página de inicio de sesión.

Opción 1: Docker Compose (Recomendado)

git clone <repo-url> && cd paperphone-plus
cp server/.env.example server/.env
# Editar: DB_PASS / JWT_SECRET / LIVEKIT_URL etc.
docker compose up -d
open http://localhost

Opción 2: Frontend en Vercel

# 1. Hacer fork de este repositorio
# 2. Importar en Vercel: Root Directory = client/, Build = npm run build, Output = dist/
#    No se necesitan variables de entorno
# 3. Desplegar el backend vía Docker o Zeabur
# 4. Abrir el frontend desplegado en Vercel, ingresar la dirección del servidor backend en la página de inicio de sesión
#    ej. https://your-server.zeabur.app

Opción 3: Desarrollo local

# Backend (Rust)
cd server && cp .env.example .env && cargo run --release

# Frontend (React)
cd client && npm install && npm run dev

Modificador de voz

Los mensajes de voz, las llamadas 1:1 y las llamadas grupales admiten modificación de voz en tiempo real con 3 modos seleccionables:

ModoVelocidadEfecto
🐢 Lento0.8xVoz más profunda y grave — ideal para anonimato
🔊 Normal1.0xVoz original, sin procesamiento
🐇 Rápido1.2xVoz más aguda — divertida y juguetona

Cómo funciona: Utiliza la Web Audio API para construir una cadena de procesamiento de audio (AudioContext → MediaStreamSource → ScriptProcessorNode → MediaStreamDestination) que ajusta el tono/velocidad de la entrada del micrófono en tiempo real.

  • Mensajes de voz: Seleccionar el modo de voz durante la grabación. El archivo .webm exportado ya contiene el efecto de voz — los destinatarios no pueden restaurar la voz original, permitiendo mensajería verdaderamente anónima
  • Llamadas 1:1 / Grupales: Tocar el botón de modificador de voz durante una llamada para alternar entre modos. La pista de audio procesada reemplaza la original mediante LiveKit LocalAudioTrack.replaceTrack()

No se requiere configuración del lado del servidor. El modificador de voz funciona completamente en el lado del cliente.


Aspecto del texto y cifrado adicional

En Perfil > Privacidad de los mensajes, puedes activar una contraseña adicional para todos los chats de este dispositivo. El contenido se cifra y se muestra como texto budista, chino aleatorio, símbolos del I Ching, hangul, jeroglíficos egipcios, cuneiforme, texto de valores fundamentales o letras y números.

  • Ambas partes deben configurar la misma contraseña en sus dispositivos; no se sincroniza automáticamente.
  • La contraseña debe tener al menos ocho caracteres y solo permanece en memoria mientras está desbloqueada. Localmente solo se guardan la sal y el verificador.
  • Bloquea de inmediato o automáticamente 5 / 15 / 30 / 60 minutos después de pasar a segundo plano. Bloqueado, solo se muestra el texto cifrado con el aspecto elegido.
  • Desactivar el cifrado adicional siempre exige volver a introducir la contraseña correcta, incluso si está desbloqueado.
  • El aspecto del texto es una capa local adicional de privacidad; no sustituye el cifrado de extremo a extremo.

Variables de entorno

VariableDescripciónPredeterminado
PORTPuerto del servidor3000
JWT_SECRETClave de firma JWT (cambiar en producción)dev_secret
DB_HOST / DB_PASS / DB_NAMEConexión MySQL
REDIS_HOST / REDIS_PASSConexión Redis
R2_ACCOUNT_IDID de cuenta de Cloudflare
R2_ACCESS_KEY_IDAccess Key del token API de R2
R2_SECRET_ACCESS_KEYSecret Key del token API de R2
R2_BUCKETNombre del bucket R2
R2_PUBLIC_URLURL base pública de R2 (opcional)
LIVEKIT_URLURL WebSocket pública de LiveKit para todas las llamadas
LIVEKIT_API_KEYClave API compartida por el servidor y LiveKit
LIVEKIT_API_SECRETSecreto API compartido por el servidor y LiveKit
VAPID_PUBLIC_KEYClave pública VAPID de Web Push (opcional)
VAPID_PRIVATE_KEYClave privada VAPID de Web Push (opcional)
VAPID_SUBJECTEmail de contacto VAPID (opcional)mailto:admin@paperphoneplus.app
FCM_PROJECT_IDID del proyecto Firebase (opcional, Capacitor Android)
FCM_CLIENT_EMAILEmail de la cuenta de servicio Firebase (opcional)
FCM_PRIVATE_KEYClave privada de la cuenta de servicio Firebase (opcional, soporta tanto escape \n como saltos de línea reales; ver abajo)
FCM_RELAY_SECRETSecreto del relay push FCM (opcional, configurar en el host relay para habilitar endpoint)
FCM_RELAY_URLURL del relay push FCM (opcional, servidores auto-hospedados apuntan al host relay)
FCM_RELAY_KEYClave de autenticación del relay push FCM (opcional, debe coincidir con FCM_RELAY_SECRET del host relay)
ONESIGNAL_APP_IDOneSignal App ID (opcional)
ONESIGNAL_REST_KEYOneSignal REST API Key (opcional)
ONESIGNAL_RELAY_SECRETSecreto del relay push OneSignal (opcional, configurar en host relay para habilitar endpoint)
ONESIGNAL_RELAY_URLURL del relay push OneSignal (opcional, servidores auto-hospedados apuntan al host relay)
ONESIGNAL_RELAY_KEYClave de autenticación del relay push OneSignal (opcional, debe coincidir con ONESIGNAL_RELAY_SECRET del host relay)
NTFY_BASE_URLURL del servidor ntfy (opcional, usa ntfy.sh público por defecto)https://ntfy.sh
NTFY_TOKENToken de autenticación ntfy (opcional, para servidores auto-hospedados)
APNS_TEAM_IDApple Developer Team ID (opcional, push nativo iOS)
APNS_KEY_IDID de clave de autenticación APNS (opcional)
APNS_PRIVATE_KEYContenido de clave privada .p8 de APNS (opcional, soporta escape \n)
APNS_BUNDLE_IDiOS App Bundle Identifier (opcional)
APNS_SANDBOXModo sandbox APNS (opcional, true para desarrollo/TestFlight)false
APNS_RELAY_SECRETSecreto del relay push (opcional, configurar en host relay para habilitar endpoint)
APNS_RELAY_URLURL del relay push (opcional, servidores auto-hospedados apuntan al host relay)
APNS_RELAY_KEYClave de autenticación del relay push (opcional, debe coincidir con APNS_RELAY_SECRET del host relay)
TELEGRAM_BOT_TOKENTelegram Bot Token (opcional)
STICKER_PACKSPaquetes de stickers personalizados (opcional, nombre:etiqueta)13 predeterminados integrados
ADMIN_PATHRuta URL del panel de administración/admin
ADMIN_PASSWORDContraseña del panel de administración (cambiar en producción)admin123

Manejo de saltos de línea en la clave privada FCM

El campo private_key en el JSON de la cuenta de servicio de Firebase contiene una clave privada RSA en formato PEM, que requiere saltos de línea reales (\n, ASCII 0x0A) entre cada línea de 64 caracteres. Sin embargo, muchas plataformas de despliegue (Zeabur, Vercel, Railway, Docker) almacenan las variables de entorno como cadenas de una sola línea, convirtiendo \n en la secuencia literal de dos caracteres \ + n.

Esta es la causa más común de fallo en las notificaciones push FCM — el parser PEM falla silenciosamente y no se envían notificaciones push, sin registros de error.

El servidor maneja esto automáticamente: fcm.rs normaliza las secuencias literales \n a saltos de línea reales antes del análisis. Ambos formatos funcionan:

  • Una línea (recomendado para plataformas en la nube): Pegar el valor bruto de private_key del archivo JSON con escapes \n:

    FCM_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----\n
    
  • Múltiples líneas (para archivos .env): Envolver el contenido PEM completo entre comillas con saltos de línea reales:

    FCM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
    MIIEvQ...
    -----END PRIVATE KEY-----"
    
PlataformaFormato recomendadoNotas
ZeaburUna línea (\n escapado)Pegar valor JSON directamente en el panel de Variables
Docker / docker-composeAmbosUsar YAML | para múltiples líneas; una línea en .env
Vercel / RailwayUna línea (\n escapado)Los campos de entrada normalmente no soportan saltos de línea reales
Archivo .env en LinuxMúltiples líneas (con comillas)Asegurarse de que las comillas estén correctamente cerradas

Solución de problemas: Si las variables FCM están configuradas pero el push de Android no funciona, revisar los registros del servidor:

  • [FCM] No access token available → Error de formato de clave privada (problema de salto de línea)
  • [FCM] ✅ Push sent to user xxx → El envío FCM funciona, el problema es del lado del cliente
  • Sin registros FCM → FCM_PROJECT_ID no configurado o no hay token en la tabla fcm_tokens

Push ntfy (Dispositivos Android chinos sin Google Services)

Para dispositivos Android sin Google Mobile Services (Huawei, Xiaomi, OPPO, vivo, etc.), PaperPhonePlus soporta notificaciones push vía ntfy.

Configuración predeterminada (sin configuración): Usa el servicio público ntfy.sh. No se necesita configuración adicional.

Configuración opcional (para servidores ntfy auto-hospedados):

NTFY_BASE_URL=https://your-ntfy-server.com
NTFY_TOKEN=your_optional_auth_token

Flujo de configuración del usuario:

  1. Instalar la app ntfy (Google Play / F-Droid / Descarga directa)
  2. Abrir Ajustes de PaperPhonePlus y encontrar la tarjeta «ntfy Push»
  3. Copiar el nombre del topic mostrado y suscribirse en la app ntfy
  4. Tocar «Registrar Push» para completar el registro

Nota de seguridad: Las notificaciones ntfy contienen títulos y resúmenes en texto plano (no el contenido real del mensaje). Para mayor seguridad, considere auto-hospedar un servidor ntfy.

Push APNS (App nativa iOS)

APNS (Apple Push Notification Service) envía notificaciones push a apps iOS nativas construidas con Capacitor. Hay dos opciones de configuración:

Opción A: Configuración directa (Servidor del desarrollador de la app)

  1. Iniciar sesión en Apple DeveloperCertificates, Identifiers & ProfilesKeys
  2. Clic en + para crear una nueva Key → marcar Apple Push Notifications service (APNs) → Register
  3. Descargar el archivo .p8 (⚠️ ¡solo se puede descargar una vez!) y anotar el Key ID
  4. Anotar su Team ID de la página de membresía de Apple Developer (10 caracteres alfanuméricos)
  5. Agregar a server/.env:
APNS_TEAM_ID=AB12CD34EF
APNS_KEY_ID=LH4Z9YN3P7
APNS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGTAgEA...(contenido del archivo .p8)...\n-----END PRIVATE KEY-----"
APNS_BUNDLE_ID=com.yourcompany.paperphoneplus
APNS_SANDBOX=false

APNS_SANDBOX: Configurar como true para compilaciones de desarrollo/TestFlight, false para producción en App Store.

Opción B: Vía Push Relay (Servidores auto-hospedados)

Si está usando la app iOS de otra persona (ej. descargada del App Store), no tiene las credenciales de Apple del desarrollador y no puede enviar pushes APNS directamente. Use el Push Relay en su lugar.

Cómo funciona:

┌──────────────────────┐       ┌─────────────────────────┐       ┌─────────┐
│  Servidor             │  HTTP  │  Servidor del            │  APNS  │  Apple  │
│  auto-hospedado       │──────→│  desarrollador           │──────→│  ──→ 📱 │
│  (sin creds Apple)    │       │  (tiene .p8 Key + Relay) │       └─────────┘
│  APNS_RELAY_URL=...   │       │  APNS_TEAM_ID=...        │
│  APNS_RELAY_KEY=...   │       │  APNS_RELAY_SECRET=...   │
└──────────────────────┘       └─────────────────────────┘

Paso 1: El desarrollador de la app habilita el endpoint Relay

En el servidor del desarrollador de la app (que ya tiene credenciales APNS), configurar un secreto relay:

# .env del servidor del desarrollador (ya tiene APNS_TEAM_ID etc.)
APNS_RELAY_SECRET=un_secreto_compartido_largo_aleatorio

Esto habilita automáticamente el endpoint de relay push en POST /api/push-relay/apns.

Paso 2: El usuario auto-hospedado configura el Relay

Los servidores auto-hospedados solo necesitan dos variables — no se requieren credenciales de Apple:

# .env del servidor auto-hospedado
APNS_RELAY_URL=https://app-developer-server.com
APNS_RELAY_KEY=el_secreto_compartido_del_paso_1

Cómo funciona:

  1. El servidor auto-hospedado recibe un mensaje offline → consulta la tabla local apns_tokens para los tokens de dispositivos iOS del usuario
  2. Envía tokens de dispositivo + título/contenido push vía HTTP POST al Relay
  3. El Relay valida la clave, luego envía a Apple usando sus propias credenciales APNS
  4. El Relay devuelve una lista de tokens obsoletos; el servidor auto-hospedado limpia automáticamente su base de datos local

Prioridad: Credenciales APNS locales → Push Relay → Omitir (silencioso). Si ambos están configurados, la conexión directa local tiene prioridad.

Nota de seguridad: El relay solo transmite títulos y resúmenes de notificaciones push (ej. «Alguien te envió un mensaje»), no el contenido real del mensaje. Los tokens de dispositivo no pueden usarse para leer datos del usuario.

Push Relay (Todos los canales)

Para operadores de servidores auto-hospedados que usan la app publicada de otra persona (ej. del App Store/Google Play), no tiene las credenciales push del desarrollador (Apple .p8 Key / cuenta de servicio Firebase / OneSignal API Key).

El sistema Push Relay proporciona capacidad de relay para los canales APNS, FCM y OneSignal:

El desarrollador de la app habilita los endpoints relay en su servidor:

# .env del servidor del desarrollador
APNS_RELAY_SECRET=una_cadena_larga_aleatoria
FCM_RELAY_SECRET=una_cadena_larga_aleatoria
ONESIGNAL_RELAY_SECRET=una_cadena_larga_aleatoria

Los usuarios auto-hospedados solo necesitan URL y clave del relay — no se requieren credenciales de servicios push:

# .env del servidor auto-hospedado
# APNS (push nativo iOS)
APNS_RELAY_URL=https://app-developer-server.com
APNS_RELAY_KEY=secreto_compartido

# FCM (push nativo Android)
FCM_RELAY_URL=https://app-developer-server.com
FCM_RELAY_KEY=secreto_compartido

# OneSignal (apps empaquetadas con Median.co)
ONESIGNAL_RELAY_URL=https://app-developer-server.com
ONESIGNAL_RELAY_KEY=secreto_compartido

Prioridad: Credenciales locales → Push Relay → Omitir (silencioso). Si ambos están configurados, la conexión directa local tiene prioridad.


Push Relay oficial

Los operadores de servidores auto-hospedados pueden usar el relay push oficial para habilitar notificaciones push iOS/Android sin configurar credenciales push:

# 2026-05-18
APNS_RELAY_URL=https://619.chat
APNS_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX
FCM_RELAY_URL=https://619.chat
FCM_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX
ONESIGNAL_RELAY_URL=https://619.chat
ONESIGNAL_RELAY_KEY=EzmpqftbsENaRUO6BTABxLV96q7RuEDyokXJr1DWdDjL54cLg7yXVUQqydCQvxrX

Agregue estas líneas al archivo .env de su servidor auto-hospedado.


Licencia

Este proyecto está licenciado bajo la GNU Affero General Public License v3.0 (AGPL-3.0).