# WhatsApp Device Migration - LID/PN (Identificadores)
## 📋 Índice
1. [O que é LID/PN](#o-que-é-lidpn)
2. [Por que existe](#por-que-existe)
3. [Como funciona](#como-funciona)
4. [Quando usa LID vs PN](#quando-usa-lid-vs-pn)
5. [Fluxo de Migração](#fluxo-de-migração)
6. [Como aparece nos logs](#como-aparece-nos-logs)
7. [Implementação técnica](#implementação-técnica)
---
## O que é LID/PN
### PN (Phone Number)
- **Formato**: `12345@s.whatsapp.net` ou `12345:5@s.whatsapp.net`
- **Significado**: Identificador baseado no número de telefone
- **Exemplo**: Se seu número é +55 11 98765-4321, o PN seria `5511987654321@s.whatsapp.net`
- **Com device**: `5511987654321:5@s.whatsapp.net` (device 5 = WhatsApp Web/Desktop)
### LID (Long-lived Identifier)
- **Formato**: `aaaaa@lid` ou `aaaaa:5@lid`
- **Significado**: Identificador de longa duração desvinculado do número de telefone
- **Exemplo**: `a1b2c3d4e5@lid`
- **Com device**: `a1b2c3d4e5:5@lid` (device 5 = WhatsApp Web/Desktop)
### Device Numbers
- **0**: Dispositivo principal (celular)
- **1-98**: Dispositivos companheiros (Web, Desktop, etc)
- **99**: Dispositivo "hosted" (companion device especial)
---
## Por que existe
### Problema Original (Apenas PN)
Quando o WhatsApp usava apenas números de telefone como identificadores:
- **Troca de número**: Usuário perde todo histórico de conversas criptografadas
- **Portabilidade**: Dificulta migração entre operadoras
- **Privacidade**: Número de telefone exposto em todos os metadados
- **Sessões Signal**: Precisavam ser recriadas ao trocar número
### Solução (LID)
Com LID, o WhatsApp pode:
- **Manter identidade**: Mesmo trocando de número, mantém o mesmo LID
- **Privacidade**: Não expõe número de telefone em todas comunicações
- **Migração suave**: Transição transparente PN → LID
- **Sessões preservadas**: Criptografia mantida mesmo com mudança de número
---
## Como funciona
### Arquitetura de Armazenamento
```
Database (SignalDataTypeMap['lid-mapping']):
├─ Forward Mapping: pn:{phoneUser} → {lidUser}
│ Exemplo: "pn:5511987654321" → "a1b2c3d4e5"
│
├─ Reverse Mapping: {lidUser}_reverse → {phoneUser}
│ Exemplo: "a1b2c3d4e5_reverse" → "5511987654321"
│
└─ Device List: device-list:{userId} → [devices...]
Exemplo: "device-list:5511987654321" → ["0", "1", "5", "99"]
```
### LRU Cache (In-Memory)
```javascript
Cache Configuration:
- TTL: 3 dias (259.200.000 ms)
- Max Size: 50.000 entradas
- Auto Purge: Ativado
- Update Age on Get: Sim (renova TTL ao acessar)
```
### Estrutura de Dados
```typescript
type LIDMapping = {
pn: string // "12345@s.whatsapp.net"
lid: string // "aaaaa@lid"
}
// Armazenamento
{
'lid-mapping': {
'pn:12345': 'aaaaa', // Forward
'aaaaa_reverse': '12345', // Reverse
},
'device-list': {
'12345': ['0', '1', '5', '99'] // Devices conhecidos
}
}
```
---
## Quando usa LID vs PN
### Usa PN (Phone Number)
1. **Primeiro contato**: Quando nunca conversou com o usuário
2. **Antes da migração**: Sistema ainda não recebeu o LID do servidor
3. **Fallback**: Se lookup do LID falhar
4. **Compatibilidade**: Dispositivos antigos que não suportam LID
### Usa LID (Long-lived ID)
1. **Após migração bem-sucedida**: Quando `migrateSession()` completou
2. **LID disponível**: Quando existe mapeamento em cache/database
3. **Usuário interagiu**: Depois que o usuário enviou/recebeu mensagem
4. **Prioridade**: LID sempre tem prioridade sobre PN quando disponível
### Decisão no Envio de Mensagem
```typescript
// Arquivo: messages-send.ts:346-370
const isLidUser = requestedLidUsers.has(user) // Usuário tem LID?
const finalJid = isLidUser
? jidEncode(user, item.server, item.device) // USA LID
: jidEncode(item.user, item.server, item.device) // USA PN
// Exemplo:
// isLidUser = true: "a1b2c3d4e5:5@lid"
// isLidUser = false: "5511987654321:5@s.whatsapp.net"
```
---
## Fluxo de Migração
### 1. Login/Conexão (Trigger Inicial)
```
socket.ts:1462-1487
1. Cliente conecta ao WhatsApp
└─> Envia credenciais
2. Servidor responde com SUCCESS
└─> ← SERVIDOR ENVIA O LID
3. Sistema armazena mapeamento próprio
└─> await lidMapping.storeLIDPNMappings([{
lid: "a1b2c3d4e5@lid",
pn: "5511987654321@s.whatsapp.net"
}])
4. Cria device list
└─> device-list[5511987654321] = ["0", "1", "5", "99"]
5. TRIGGER MIGRAÇÃO EM MASSA
└─> await signalRepository.migrateSession(
"5511987654321@s.whatsapp.net", // FROM (PN)
"a1b2c3d4e5@lid" // TO (LID)
)
```
### 2. Migração de Sessão Signal
```
libsignal.ts:484-635
migrateSession(fromPN, toLID):
├─ Validação
│ └─ Apenas suporta PN → LID (não LID → PN)
│
├─ Busca devices do device-list
│ └─ ["0", "1", "5", "99"]
│
├─ Filtra devices com sessão Signal existente
│ └─ [0, 5] (apenas esses têm sessão ativa)
│
├─ Cria operações de migração para cada device:
│ ├─ Device 0:
│ │ FROM: 5511987654321:0@s.whatsapp.net
│ │ TO: a1b2c3d4e5:0@lid
│ │
│ └─ Device 5:
│ FROM: 5511987654321:5@s.whatsapp.net
│ TO: a1b2c3d4e5:5@lid
│
├─ Executa transação atômica:
│ ├─ Copia sessão: session[a1b2c3d4e5:0@lid] = session[5511987654321:0@s.whatsapp.net]
│ ├─ Deleta antiga: session[5511987654321:0@s.whatsapp.net] = null
│ ├─ Copia sessão: session[a1b2c3d4e5:5@lid] = session[5511987654321:5@s.whatsapp.net]
│ └─ Deleta antiga: session[5511987654321:5@s.whatsapp.net] = null
│
├─ Cacheia migrações completadas (TTL: 7 dias)
│ └─ migratedSessionCache.add("5511987654321:0")
│
└─ Retorna: { migrated: 2, skipped: 0, total: 2 }
```
### 3. Descoberta de Devices no Envio
```
messages-send.ts:303-403
Ao enviar mensagem para usuário:
├─ Executa USyncQuery
│ └─> .withDeviceProtocol()
│ └─> .withLIDProtocol()
│
├─ Servidor responde com:
│ ├─ Device list: [0, 1, 5, 99]
│ ├─ LID: "a1b2c3d4e5"
│ └─ Identity keys de cada device
│
├─ Sistema armazena:
│ ├─ device-list[user] = [devices...]
│ └─> Permite migração em massa futura
│
└─ Prioriza uso de LID se disponível
```
### 4. USync Protocol (Busca de LID)
```
socket.ts:470-493
pnFromLIDUSync(jids: string[]):
├─ Cria USyncQuery
│ └─> .withLIDProtocol()
│ └─> .withContext('background')
│
├─ Adiciona usuários
│ └─> .withUser(new USyncUser().withId(jid))
│
├─ Executa query no servidor WhatsApp
│ └─> const results = await executeUSyncQuery(usyncQuery)
│
└─ Retorna mapeamentos:
[{
pn: "5511987654321@s.whatsapp.net",
lid: "a1b2c3d4e5@lid"
}]
```
---
## Como aparece nos logs
### Cenário 1: Primeiro Contato (Apenas PN)
```
[INFO] Sending message to: 5511987654321@s.whatsapp.net
[DEBUG] No LID mapping found, using PN
[DEBUG] Querying devices for: 5511987654321@s.whatsapp.net
[DEBUG] USyncQuery: withDeviceProtocol, withLIDProtocol
[INFO] Devices found: [0, 1, 5]
[INFO] LID received from server: a1b2c3d4e5
[DEBUG] Storing LID mapping: 5511987654321 → a1b2c3d4e5
[DEBUG] Storing device list: [0, 1, 5]
[INFO] Message encrypted for PN: 5511987654321:0@s.whatsapp.net
```
### Cenário 2: Após Interação (Usuário envia mensagem)
```
[INFO] Message received from: 5511987654321@s.whatsapp.net
[DEBUG] Checking for LID mapping...
[DEBUG] LID found in cache: a1b2c3d4e5@lid
[INFO] Triggering session migration...
[DEBUG] migrateSession(5511987654321@s.whatsapp.net → a1b2c3d4e5@lid)
[DEBUG] Found devices in device-list: [0, 1, 5]
[DEBUG] Migrating device 0: session copied and old deleted
[DEBUG] Migrating device 5: session copied and old deleted
[INFO] Migration completed: { migrated: 2, skipped: 0 }
[DEBUG] Cached migration for 7 days
```
### Cenário 3: Envio Subsequente (Usa LID)
```
[INFO] Sending message to: 5511987654321@s.whatsapp.net
[DEBUG] Checking for LID mapping...
[DEBUG] LID found in cache: a1b2c3d4e5@lid (cache hit)
[DEBUG] Using LID for encryption
[INFO] Message encrypted for LID: a1b2c3d4e5:0@lid
[INFO] Message encrypted for LID: a1b2c3d4e5:5@lid
[DEBUG] Encryption used migrated session (no re-establishment needed)
```
### Cenário 4: Cache Miss (Busca no Database)
```
[INFO] Sending message to: 5511988888888@s.whatsapp.net
[DEBUG] Cache miss for PN: 5511988888888
[DEBUG] Fetching from database: lid-mapping:pn:5511988888888
[DEBUG] Database hit: found LID mapping → b2c3d4e5f6
[DEBUG] Updating cache (TTL: 3 days)
[INFO] Using LID: b2c3d4e5f6:0@lid
```
### Cenário 5: USync Fallback (Não está no DB)
```
[INFO] Sending message to: 5511977777777@s.whatsapp.net
[DEBUG] Cache miss for PN: 5511977777777
[DEBUG] Database miss: no mapping found
[DEBUG] Triggering USync fetch...
[DEBUG] USyncQuery with LIDProtocol for: 5511977777777
[INFO] USync response received: LID = c3d4e5f6g7
[DEBUG] Storing LID mapping in database
[DEBUG] Updating cache
[INFO] Using LID: c3d4e5f6g7:0@lid
```
### Exemplo de Log Real Completo
```
2026-02-09 10:15:32 [socket.ts:1462] Connection success, lid=a1b2c3d4e5
2026-02-09 10:15:32 [lid-mapping.ts:245] storeLIDPNMappings: 1 mapping(s)
2026-02-09 10:15:32 [lid-mapping.ts:267] Phase 1: Validated 1, cache miss 1
2026-02-09 10:15:32 [lid-mapping.ts:305] Phase 3: Stored 1 new mapping(s)
2026-02-09 10:15:32 [socket.ts:1475] Stored device-list for user: 5511987654321
2026-02-09 10:15:32 [libsignal.ts:491] migrateSession: PN → LID
2026-02-09 10:15:32 [libsignal.ts:522] Found 2 devices: [0, 5]
2026-02-09 10:15:32 [libsignal.ts:589] Migrated device 0: a1b2c3d4e5:0@lid
2026-02-09 10:15:32 [libsignal.ts:589] Migrated device 5: a1b2c3d4e5:5@lid
2026-02-09 10:15:32 [libsignal.ts:627] Migration result: {migrated:2, skipped:0}
... usuário interage ...
2026-02-09 10:20:15 [messages-send.ts:346] Preparing message for 5511987654321
2026-02-09 10:20:15 [lid-mapping.ts:158] getLIDForPN: 5511987654321
2026-02-09 10:20:15 [lid-mapping.ts:164] Cache hit: a1b2c3d4e5
2026-02-09 10:20:15 [messages-send.ts:370] Using LID: a1b2c3d4e5:0@lid
2026-02-09 10:20:15 [messages-send.ts:370] Using LID: a1b2c3d4e5:5@lid
2026-02-09 10:20:15 [libsignal.ts:180] Encrypting with session: a1b2c3d4e5:0@lid
2026-02-09 10:20:15 [libsignal.ts:180] Encrypting with session: a1b2c3d4e5:5@lid
2026-02-09 10:20:15 [socket.ts:892] Message sent successfully
```
---
## Implementação Técnica
### Arquivos Principais
```
src/
├── Signal/
│ ├── lid-mapping.ts (1.166 linhas)
│ │ └─> LIDMappingStore: Cache LRU + Database + USync
│ │
│ └── libsignal.ts
│ └─> migrateSession(): Migração PN → LID
│
├── Socket/
│ ├── socket.ts
│ │ ├─> Inicialização: Armazena LID próprio
│ │ └─> Trigger migração no login
│ │
│ └── messages-send.ts
│ ├─> Device query com USyncQuery
│ └─> Decisão LID vs PN
│
├── Utils/
│ └── sync-action-utils.ts
│ └─> LID mapping events
│
└── Types/
└── Auth.ts
└─> type LIDMapping = { pn, lid }
```
### LIDMappingStore - Operações Principais
```typescript
class LIDMappingStore {
// 1. ARMAZENAR MAPEAMENTOS
async storeLIDPNMappings(mappings: LIDMapping[]): Promise {
// 3 fases: Validate → Fetch → Store
// Batch size: 100 (configurável)
// Retry: 3 tentativas com backoff exponencial
}
// 2. BUSCAR LID PARA PN (com device)
async getLIDForPN(pn: string): Promise {
// Input: "5511987654321:5@s.whatsapp.net"
// Output: "a1b2c3d4e5:5@lid"
// Flow: Cache → Database → USync (fallback)
// Request coalescing: Deduplica requests concorrentes
}
// 3. BUSCAR PN PARA LID (reverse)
async getPNForLID(lid: string): Promise {
// Input: "a1b2c3d4e5:5@lid"
// Output: "5511987654321:5@s.whatsapp.net"
// Flow: Cache → Database → USync (fallback)
}
// 4. OPERAÇÕES EM LOTE
async getLIDsForPNs(pns: string[]): Promise