# PaperPhone IM
🌐 **Otros idiomas / Other Languages:** [中文](README.md) · [English](README_EN.md) · [日本語](README_JA.md) · [한국어](README_KO.md) · [Français](README_FR.md) · [Deutsch](README_DE.md) · [Русский](README_RU.md)
Una aplicación de mensajería instantánea cifrada de extremo a extremo estilo WeChat, con cifrado ECDH sin estado + XSalsa20-Poly1305 por mensaje, videollamadas en tiempo real, almacenamiento Cloudflare R2, soporte multilingüe y despliegue iOS PWA.
[](#) [](#) [](#) [](#)
[](https://zeabur.com/templates/P2J7Y3?referralCode=619dev)
---
## Características
| Función | Descripción |
|---------|-------------|
| 🔐 Cifrado E2E | ECDH sin estado + XSalsa20-Poly1305 — claves efímeras por mensaje, secreto hacia adelante |
| 🗝️ Servidor de conocimiento cero | El servidor solo almacena texto cifrado; las claves privadas nunca salen del dispositivo |
| 📹 Videollamadas/voz | WebRTC P2P (1:1) + Mesh (grupo), Cloudflare TURN para atravesar NAT |
| 👥 Chat grupal | Hasta 2000 miembros, mensajes en texto plano, modo No molestar, gestión de miembros |
| ⏱️ Eliminación automática | 5 niveles (nunca/1d/3d/1sem/1mes), en DMs ambas partes, en grupos solo propietario |
| 🔔 Notificaciones push | Web Push (VAPID) + OneSignal canal doble |
| 🌐 Multilingüe | ZH/EN/JA/KO/FR/DE/RU/ES — detección automática + selección manual |
| 📱 iOS sin certificado empresarial | PWA vía Safari "Añadir a inicio", sin firma de Apple |
| 💬 Mensajería rica | Texto, imágenes, mensajes de voz, 64 emojis, confirmaciones de lectura |
| 🌐 Momentos | Feed social: texto + hasta 9 fotos, likes (avatares de amigos), comentarios, visibilidad por etiquetas |
| 🏷️ Etiquetas de amigos | Múltiples etiquetas por amigo (paleta de 12 colores), filtrar contactos por etiqueta |
| 🗂️ Almacenamiento R2 | Cloudflare R2 para imágenes/audio — URL CDN opcional |
| 🏗️ Auto-alojable | Despliegue Docker Compose en un comando |
---
## Stack tecnológico
```
Backend (server/)
Node.js 20 + Express + ws
MySQL 8.0 — usuarios, mensajes (texto cifrado)
Redis — presencia en línea + enrutamiento entre nodos
Cloudflare R2 — almacenamiento de archivos (API compatible S3)
Autenticación JWT + bcrypt
Frontend (client/)
HTML nativo + Vanilla JS (ESM, sin bundler)
libsodium-wrappers (WebAssembly — Curve25519 / XSalsa20-Poly1305)
API WebRTC — videollamadas / llamadas de voz
PWA: manifest.json + Service Worker
Capa criptográfica
ECDH sin estado + XSalsa20-Poly1305 — par de claves efímeras por mensaje
Persistencia de claves en 4 niveles: memoria → localStorage → sessionStorage → IndexedDB
Todas las claves privadas solo en el dispositivo — nunca se envían al servidor
```
---
## Inicio rápido
### Opción 0: Zeabur — Despliegue en la nube con un clic
[](https://zeabur.com/templates/P2J7Y3?referralCode=619dev)
> [!NOTE]
> Se requiere un paso manual después del despliegue:
> 1. Consola Zeabur → servicio **server** → Variables de entorno → copiar el valor de `ZEABUR_WEB_URL`
> 2. Servicio **client** → Variables de entorno → agregar `SERVER_URL` = valor copiado
> 3. Reiniciar el servicio client
### Opción 1: Docker Compose (recomendado)
```bash
git clone && cd paperphone
cp server/.env.example server/.env
# Editar: DB_PASS / JWT_SECRET / R2_* etc.
docker compose up -d
open http://localhost
```
> Imágenes Docker Hub: `facilisvelox/paperphone-client:latest` y `facilisvelox/paperphone-server:latest`
### Opción 2: Inicio local manual
```bash
# Backend
cd server && npm install && npm run dev # → http://localhost:3000
# Frontend
npx serve client -p 8080 # → http://localhost:8080
```
---
## Configuración de videollamadas — Cloudflare TURN
```env
CF_CALLS_APP_ID=your_app_id_here
CF_CALLS_APP_SECRET=your_app_secret_here
```
| Tipo | Transporte | Recomendado para |
|------|-----------|------------------|
| Video 1:1 | WebRTC P2P + TURN | Todos los escenarios |
| Voz 1:1 | WebRTC P2P + TURN | Todos los escenarios |
| Llamada grupal | WebRTC Mesh | Hasta 6 participantes |
> **Sin configuración**: solo STUN. Las llamadas en LAN funcionan sin configuración adicional.
---
## Notificaciones push
| Canal | Plataformas | Configuración |
|-------|-------------|---------------|
| Web Push | Navegadores + iOS PWA (Safari 16.4+) | Claves VAPID |
| OneSignal | Apps nativas Median.co | App ID + REST Key |
```bash
npx web-push generate-vapid-keys
```
---
## iOS — Instalación permanente sin certificado
1. Desplegar en servidor HTTPS → 2. Abrir en Safari → 3. Compartir ⬆️ → 4. «Añadir a pantalla de inicio»
---
## Modelo de seguridad
```
Registro: IK + SPK + 10×OPK generados localmente, claves públicas subidas
Mensaje: ECDH efímero → X25519 → XSalsa20-Poly1305
El servidor ve: ✅ texto cifrado + metadatos de enrutamiento ❌ texto plano / claves privadas
```
---
## Esquema de base de datos
11 tablas, creadas automáticamente en el primer inicio:
| Tabla | Propósito |
|-------|-----------|
| `users` | Perfiles + claves públicas ECDH/OPK |
| `prekeys` | Pre-claves X3DH de un solo uso |
| `friends` | Relaciones de amistad |
| `groups` / `group_members` | Grupos + miembros |
| `messages` | Mensajes cifrados |
| `moments` / `moment_images` | Publicaciones + imágenes |
| `moment_likes` / `moment_comments` | Likes + comentarios |
| `push_subscriptions` | Web Push (VAPID) |
| `onesignal_players` | Dispositivos OneSignal |
---
## Variables de entorno
| Variable | Descripción | Predeterminado |
|----------|-------------|----------------|
| `PORT` | Puerto del servidor | `3000` |
| `JWT_SECRET` | Clave de firma JWT (**cambiar en producción**) | dev_secret |
| `DB_HOST`/`DB_PASS`/`DB_NAME` | Conexión MySQL | — |
| `REDIS_HOST`/`REDIS_PASS` | Conexión Redis | — |
| `R2_ACCOUNT_ID` | ID de cuenta Cloudflare | — |
| `R2_ACCESS_KEY_ID` | Clave de acceso R2 API | — |
| `R2_SECRET_ACCESS_KEY` | Clave secreta R2 API | — |
| `R2_BUCKET` | Nombre del bucket R2 | — |
| `R2_PUBLIC_URL` | URL pública R2 (opc.) | — |
| `CF_CALLS_APP_ID` | Calls App ID (opc.) | — |
| `CF_CALLS_APP_SECRET` | Calls Secret (opc.) | — |
| `VAPID_PUBLIC_KEY` | Clave pública VAPID (opc.) | — |
| `VAPID_PRIVATE_KEY` | Clave privada VAPID (opc.) | — |
| `ONESIGNAL_APP_ID` | OneSignal App ID (opc.) | — |
| `ONESIGNAL_REST_KEY` | OneSignal REST Key (opc.) | — |
---
## Licencia
MIT © PaperPhone Contributors