Skip to content

Conversation

@oriondesign2015
Copy link
Contributor

@oriondesign2015 oriondesign2015 commented Dec 12, 2025

Correção: Suporte a Tokens do WhatsApp Business com Mais de 255 Caracteres

📋 Resumo

Este PR implementa uma solução completa para suportar tokens de acesso do WhatsApp Business (Meta API) que excedem 255 caracteres. A solução utiliza um sistema de cache híbrido para armazenar o token completo, enquanto o banco de dados continua armazenando apenas os primeiros 255 caracteres para compatibilidade.

🎯 Tipo de Mudança

  • 🐛 Bug fix (correção que não quebra funcionalidade existente)
  • ✨ Nova feature
  • 💥 Breaking change
  • 📚 Documentação

🔍 Problema Identificado

O token de acesso do WhatsApp Business (Meta API) pode ter mais de 255 caracteres, mas o campo token na tabela Instance do banco de dados está limitado a 255 caracteres (VarChar(255)). Isso causava dois problemas:

  1. Falha na autenticação: Ao tentar autenticar usando o token da instância no header apikey, o sistema não reconhecia o token porque ele foi truncado no banco.

  2. Erro na API do Meta: Quando o token truncado era usado nas chamadas da API do Meta, ocorria o erro:

    "The access token could not be decrypted"
    ou
    "Invalid OAuth access token - Cannot parse access token"
    
  3. Problema após reiniciar a API: Quando a API era reiniciada, o cache local (node-cache) era limpo, fazendo com que o token completo não fosse encontrado e a instância usasse o token truncado, causando falhas nas requisições.

💡 Solução Implementada

A solução utiliza um sistema de cache híbrido para armazenar o token completo, enquanto o banco de dados continua armazenando apenas os primeiros 255 caracteres (para compatibilidade e autenticação).

Componentes da Solução

  1. Cache para Token Completo: O token completo é armazenado no cache (Redis ou Node-cache) com a chave instance:{instanceName}:fullToken e sem expiração (TTL = 0).

  2. Validação e Truncamento: Strings são validadas e truncadas antes de serem salvas no banco de dados para evitar erros de tamanho de coluna.

  3. Autenticação Inteligente: O guard de autenticação compara apenas os primeiros 255 caracteres do token fornecido com o token do banco, mas salva automaticamente o token completo no cache quando detectado.

  4. Atualização Automática: Quando uma requisição é feita com o token completo após reiniciar a API, o sistema automaticamente salva o token no cache e atualiza a instância em memória.

🔧 Arquivos Modificados

1. src/api/services/channel.service.ts

Mudanças:

  • Adicionada função helper truncate() para truncar strings
  • Aplicado truncamento nos campos msgCall e wavoipToken (limite de 100 caracteres)

Motivo: Previne erros de violação de tamanho de coluna no banco de dados.

2. src/api/services/monitor.service.ts

Mudanças:

  • Adicionada função helper truncate() para truncar strings
  • Aplicado truncamento em múltiplos campos ao salvar instâncias:
    • instanceName: 255 caracteres (com validação de campo obrigatório)
    • ownerJid: 100 caracteres
    • profileName: 100 caracteres
    • profilePicUrl: 500 caracteres
    • number: 100 caracteres
    • integration: 100 caracteres
    • token: 255 caracteres
    • clientName: 100 caracteres
    • businessId: 100 caracteres
  • Modificados os métodos loadInstancesFromDatabasePostgres, loadInstancesFromRedis e loadInstancesFromProvider para tentar carregar o token completo do cache para instâncias WhatsApp Business
  • Ajustado o método setInstance para lidar com o método async do BusinessStartupService
  • Adicionada validação para garantir que instanceName não seja vazio
  • Corrigido uso de await desnecessário em get<Database>()
  • Melhorado tratamento de erros propagando exceções

Motivo: Garante que dados longos sejam automaticamente ajustados e que o token completo seja carregado do cache quando disponível.

3. src/api/guards/auth.guard.ts

Mudanças:

  • Modificada a comparação de token para usar apenas os primeiros 255 caracteres do token fornecido
  • Adicionada lógica para detectar quando uma requisição usa um token completo (> 255 caracteres) para instâncias WhatsApp Business
  • Implementado salvamento automático do token completo no cache quando detectado
  • Implementada atualização automática da instância em memória com o token completo

Motivo: Permite autenticação com tokens truncados no banco, mas salva automaticamente o token completo quando fornecido, resolvendo o problema após reiniciar a API.

4. src/api/controllers/instance.controller.ts

Mudanças:

  • Modificado para aguardar o método setInstance quando a integração é WhatsApp Business (agora é async)
  • O token original completo é passado ao método setInstance em vez do hash truncado

Motivo: Garante que o token completo seja armazenado no cache ao criar a instância.

5. src/api/integrations/channel/meta/whatsapp.business.service.ts

Mudanças:

  • Adicionada propriedade privada fullToken para armazenar o token completo
  • Override do getter token para retornar o token completo quando disponível
  • Override do método setInstance (agora async) para:
    • Armazenar o token completo no cache quando fornecido
    • Carregar o token completo do cache quando disponível
    • Adicionar logs informativos sobre o carregamento do token

Motivo: Garante que o serviço use sempre o token completo quando disponível, em vez do token truncado do banco.

6. src/api/integrations/event/websocket/websocket.controller.ts

Mudanças:

  • Ajustada a comparação de token para usar apenas os primeiros 255 caracteres

Motivo: Mantém consistência na autenticação via WebSocket.

7. src/api/services/auth.service.ts

Mudanças:

  • Ajustada a verificação de tokens duplicados para comparar apenas os primeiros 255 caracteres

Motivo: Evita falsos positivos na verificação de tokens duplicados.

🔍 Detalhes Técnicos

Função Helper truncate()

const truncate = (str: string | null | undefined, maxLength: number): string | null => {
  if (!str) return null;
  return str.length > maxLength ? str.substring(0, maxLength) : str;
};

Esta função:

  • Retorna null se o valor for null ou undefined
  • Trunca strings que excedem o maxLength especificado
  • Retorna a string original se estiver dentro do limite

Sistema de Cache para Token Completo

Chave do Cache: instance:{instanceName}:fullToken

TTL: 0 (sem expiração)

Comportamento:

  • Ao criar instância: Token completo é salvo no cache se > 255 caracteres
  • Ao carregar instância: Sistema tenta carregar token completo do cache
  • Na autenticação: Se token completo for fornecido, é salvo automaticamente no cache

Fluxo de Autenticação

  1. Usuário envia requisição com token completo no header apikey
  2. Guard compara apenas os primeiros 255 caracteres com o banco
  3. Se autenticação bem-sucedida e token > 255 caracteres:
    • Token completo é salvo no cache
    • Instância em memória é atualizada com token completo
  4. Requisição continua usando instância atualizada

✅ Benefícios

  1. Prevenção de Erros: Evita erros de violação de tamanho de coluna no banco de dados
  2. Robustez: Garante que dados longos sejam automaticamente ajustados
  3. Compatibilidade: Funciona com instâncias antigas (que não têm token completo no cache)
  4. Recuperação Automática: Após reiniciar a API, primeira requisição com token completo resolve o problema automaticamente
  5. Manutenibilidade: Código mais seguro e previsível

🧪 Como Testar

Cenário 1: Criar Nova Instância com Token Completo

curl -X POST "http://localhost:8080/instance/create" \
  -H "Content-Type: application/json" \
  -H "apikey: SUA_APIKEY_GLOBAL" \
  -d '{
    "instanceName": "WABA",
    "token": "SEU_TOKEN_COMPLETO_AQUI",
    "number": "SEU_NUMERO",
    "integration": "WHATSAPP-BUSINESS",
    "businessId": "SEU_BUSINESS_ID"
  }'

Resultado Esperado: Instância criada com sucesso, token completo salvo no cache.

Cenário 2: Enviar Mensagem Após Reiniciar API

  1. Reinicie a API
  2. Verifique os logs - deve aparecer: Full token not found in cache for WABA, using truncated token
  3. Faça uma requisição com o token completo:
curl -X POST "http://localhost:8080/message/sendText/WABA" \
  -H "Content-Type: application/json" \
  -H "apikey: SEU_TOKEN_COMPLETO_AQUI" \
  -d '{
    "number": "5511999999999",
    "text": "teste"
  }'

Resultado Esperado:

  • Logs devem mostrar: Stored full token in cache for WABA from request
  • Logs devem mostrar: Updated full token in memory for WABA
  • Mensagem enviada com sucesso (status PENDING)

Cenário 3: Requisições Subsequentes

Após o cenário 2, faça mais requisições:

curl -X POST "http://localhost:8080/message/sendText/WABA" \
  -H "Content-Type: application/json" \
  -H "apikey: SEU_TOKEN_COMPLETO_AQUI" \
  -d '{
    "number": "5511999999999",
    "text": "teste 2"
  }'

Resultado Esperado: Mensagem enviada com sucesso sem necessidade de salvar token novamente.

Cenário 4: Verificar Cache Manualmente (Redis)

redis-cli GET "instance:WABA:fullToken"

Resultado Esperado: Deve retornar o token completo.

📝 Notas Importantes

  1. Limitação do Banco: O campo token no banco continua limitado a 255 caracteres. Esta é uma limitação do schema do Prisma e não deve ser alterada sem uma migração adequada.

  2. Cache: O token completo é armazenado no cache (Redis ou Node-cache) com a chave instance:{instanceName}:fullToken e sem expiração (TTL = 0).

  3. Compatibilidade: A solução é compatível com instâncias antigas (que não têm token completo no cache), mas elas precisarão fazer pelo menos uma requisição com o token completo após reiniciar a API.

  4. Outros Canais: Esta correção é específica para WHATSAPP-BUSINESS. Outros canais (Baileys, Evolution) não são afetados.

  5. Node-cache vs Redis:

    • Com Redis: Token persiste entre reinicializações
    • Com Node-cache: Token é perdido ao reiniciar, mas é recuperado automaticamente na primeira requisição

🔄 Migração de Instâncias Existentes

Instâncias criadas antes desta correção não terão o token completo no cache. Duas opções:

Opção 1: Recriar a Instância (Recomendado)

# 1. Deletar a instância existente
curl -X DELETE "http://localhost:8080/instance/delete/WABA" \
  -H "apikey: SUA_APIKEY_GLOBAL"

# 2. Recriar com o token completo
curl -X POST "http://localhost:8080/instance/create" \
  -H "Content-Type: application/json" \
  -H "apikey: SUA_APIKEY_GLOBAL" \
  -d '{
    "instanceName": "WABA",
    "token": "SEU_TOKEN_COMPLETO_AQUI",
    "number": "SEU_NUMERO",
    "integration": "WHATSAPP-BUSINESS",
    "businessId": "SEU_BUSINESS_ID"
  }'

Opção 2: Atualizar Token Manualmente no Cache

Redis:

redis-cli SET "instance:WABA:fullToken" "SEU_TOKEN_COMPLETO_AQUI"

Node-cache: Não é possível atualizar manualmente. Use a Opção 1 ou faça uma requisição com o token completo.

🐛 Troubleshooting

Problema: Token ainda não funciona após recriar

Solução:

  • Verifique se o cache está funcionando (Redis rodando, se aplicável)
  • Verifique os logs para mensagens sobre carregamento do token
  • Tente atualizar manualmente no cache (veja seção acima)

Problema: Erro 401 ao usar token da instância

Solução:

  • Verifique se está usando o token completo (não truncado)
  • Verifique os logs do guard para mensagens de autenticação
  • Certifique-se de que o token no banco corresponde aos primeiros 255 caracteres do token completo

Problema: Token expirado

Solução: Este é um problema diferente - o token do Meta expirou. Gere um novo token no Facebook Developers e recrie a instância.

Problema: "Full token not found in cache" após reiniciar

Solução: Isso é esperado. Faça uma requisição com o token completo e o sistema salvará automaticamente no cache.

📊 Logs de Referência

Logs Esperados ao Carregar Instância

[WARN] Full token not found in cache for WABA, using truncated token

Logs Esperados ao Autenticar com Token Completo

[INFO] Stored full token in cache for WABA from request
[INFO] Updated full token in memory for WABA

Logs Esperados ao Criar Instância

[INFO] Stored full token in cache for WABA

Logs Esperados ao Carregar Token do Cache

[INFO] Loaded full token from cache for WABA

✅ Checklist de Revisão

  • Código segue os padrões do projeto
  • Mudanças foram testadas localmente
  • Não há breaking changes
  • Documentação atualizada
  • Commits seguem o padrão Conventional Commits
  • Solução funciona com Redis e Node-cache
  • Compatível com instâncias antigas
  • Logs informativos adicionados

🔗 Referências

Summary by Sourcery

Handle WhatsApp Business access tokens longer than 255 characters by truncating database fields while storing and using full tokens via cache, and align related authentication and startup flows with this behavior.

Bug Fixes:

  • Fix authentication failures and Meta API errors caused by truncation of long WhatsApp Business access tokens.
  • Prevent database column size violations by truncating overly long instance and settings string fields before persistence.

Enhancements:

  • Load and persist full WhatsApp Business tokens from cache during instance initialization and recovery flows.
  • Make WhatsApp Business startup asynchronous-aware so token handling can persist and restore full tokens reliably across restarts.
  • Normalize token comparisons across HTTP, WebSocket, and duplicate-token checks to only use the first 255 characters while preserving full tokens in cache.

…acters

- Add string truncation validation to prevent database column size errors
- Implement full token cache system for WhatsApp Business instances
- Auto-save full token to cache on authentication requests
- Update instance in memory with full token when detected
- Add token full length support in monitor service
- Adjust token comparison in auth guard and websocket controller
- Fix duplicate token check for full length tokens

This fixes the issue where WhatsApp Business API tokens (>255 chars) were
truncated in the database, causing authentication failures and API errors.
The solution uses a hybrid cache system to store full tokens while keeping
the database compatible with the 255 char limit.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 12, 2025

Reviewer's Guide

Adds hybrid caching support so WhatsApp Business tokens longer than 255 characters are stored and reused from cache while only the first 255 chars remain in the database, plus general defensive truncation for other string fields and adjustments to async instance initialization and token-based authentication paths.

Sequence diagram for WhatsApp Business token-based HTTP authentication with hybrid cache

sequenceDiagram
  actor Client
  participant API as ExpressMiddleware
  participant AuthGuard as AuthGuard_apikey
  participant DB as PrismaInstanceRepo
  participant Cache as CacheService
  participant Monitor as WAMonitoringService
  participant WA as BusinessStartupService

  Client->>API: HTTP request with header apikey=fullToken
  API->>AuthGuard: Invoke apikey middleware
  AuthGuard->>DB: findUnique(name=instanceName)
  DB-->>AuthGuard: Instance(name, token=truncatedToken, integration)
  AuthGuard->>AuthGuard: keyToCompare = first255(fullToken)
  AuthGuard->>AuthGuard: Compare keyToCompare with instance.token
  alt Token_matches_and_integration_is_WABA
    AuthGuard->>Cache: set(instance:instanceName:fullToken, fullToken, ttl=0)
    AuthGuard->>Monitor: Read waInstances[instanceName]
    alt Instance_in_memory_and_has_setInstance
      AuthGuard->>WA: setInstance(instanceId, instanceName, integration, token=fullToken, number, businessId)
      WA->>Cache: set(instance:instanceName:fullToken, fullToken, ttl=0)
      WA-->>AuthGuard: fullToken stored and in-memory updated
    else No_in_memory_instance
      AuthGuard-->>AuthGuard: Skip_in_memory_update
    end
    AuthGuard-->>API: next()
  else Token_does_not_match
    AuthGuard-->>API: Respond_401
  end
  API-->>Client: 200_or_401_response
Loading

Sequence diagram for WhatsApp Business instance creation and full token caching

sequenceDiagram
  actor Client
  participant API as InstanceController
  participant Monitor as WAMonitoringService
  participant Factory as InstanceFactory
  participant WA as BusinessStartupService
  participant DB as PrismaInstanceRepo
  participant Cache as CacheService

  Client->>API: POST /instance/create (token=fullToken)
  API->>Monitor: saveInstance(data)
  Monitor->>Monitor: truncate(instanceName, hash, other_fields)
  Monitor->>DB: instance.create(id, name, token=truncatedHash, other_fields)
  DB-->>Monitor: persisted_instance
  Monitor-->>API: return

  API->>Factory: createInstance(integration=WHATSAPP_BUSINESS)
  Factory-->>API: BusinessStartupService_instance

  API->>WA: setInstance(instanceId, instanceName, integration, token=fullToken, number, businessId)
  WA->>WA: fullToken = fullToken
  WA->>Cache: set(instance:instanceName:fullToken, fullToken, ttl=0)
  WA-->>API: instance_ready_with_full_token

  API->>Monitor: waInstances[instanceName] = WA
  API-->>Client: 201 instance_created
Loading

Updated class diagram for WhatsApp Business token handling and monitoring

classDiagram
  class ChannelStartupService {
    - configService
    - eventEmitter
    - prismaRepository
    - chatwootCache
    - cache
    - instanceId
    - instance
    - localSettings
    + setInstance(instanceId, instanceName, integration, token, number, businessId, ownerJid)
    + setSettings(data)
  }

  class BusinessStartupService {
    - fullToken : string
    + stateConnection
    + phoneNumber
    + mobile : boolean
    + get token() string
    + setInstance(instanceId, instanceName, integration, token, number, businessId, ownerJid)
    + get connectionStatus()
  }

  class WAMonitoringService {
    - configService
    - prismaRepository
    - cache
    - logger
    + waInstances : Record
    + saveInstance(data)
    + setInstance(instanceId, instanceName, integration, token, number, businessId, ownerJid, connectionStatus)
    + loadInstancesFromRedis()
    + loadInstancesFromDatabasePostgres()
    + loadInstancesFromProvider(instanceId)
  }

  class InstanceController {
    - waMonitor : WAMonitoringService
    - prismaRepository
    - configService
    + createInstance(instanceData)
  }

  class AuthService {
    - prismaRepository
    + isDuplicateInstanceToken(token) bool
  }

  class AuthGuardApikey {
    - prismaRepository
    - cache
    - waMonitor : WAMonitoringService
    - logger
    + apikey(req, res, next)
  }

  class WebsocketController {
    - prismaRepository
    - logger
    + handleConnection(socket, callback)
  }

  ChannelStartupService <|-- BusinessStartupService
  WAMonitoringService --> BusinessStartupService : manages_instances
  InstanceController --> WAMonitoringService : uses_for_lifecycle
  AuthGuardApikey --> WAMonitoringService : updates_in_memory_instance
  AuthGuardApikey --> BusinessStartupService : async_setInstance
  AuthService --> PrismaInstanceRepo : queries_tokens
  WebsocketController --> PrismaInstanceRepo : queries_tokens

  class PrismaInstanceRepo {
    + findUnique(where)
    + findFirst(where)
    + findMany(where)
    + create(data)
  }

  class CacheService {
    + get(key)
    + set(key, value, ttl)
  }

  BusinessStartupService --> CacheService : cache_fullToken
  WAMonitoringService --> CacheService : load_fullToken_on_boot
  AuthGuardApikey --> CacheService : store_fullToken_on_auth
Loading

File-Level Changes

Change Details Files
Ensure database writes respect column limits via centralized string truncation before persistence.
  • Introduced a local truncate helper in ChannelStartupService.setSettings to cap msgCall and wavoipToken at 100 chars when persisting and updating localSettings.
  • Introduced a local truncate helper in WAMonitoringService.saveInstance and applied length limits to instanceName, ownerJid, profileName, profilePicUrl, number, integration, token, clientName, and businessId.
  • Added validation so instanceName is required and non-empty, throwing an error if invalid, and rethrowing caught errors after logging in saveInstance.
src/api/services/channel.service.ts
src/api/services/monitor.service.ts
Introduce full-token caching for WhatsApp Business and adjust instance bootstrapping to use the full token when available.
  • Extended BusinessStartupService with a private fullToken field and a token getter that prefers fullToken over the truncated instance token.
  • Overrode BusinessStartupService.setInstance to (a) immediately cache tokens longer than 255 chars under instance:{instanceName}:fullToken with TTL 0 and (b) load a full token from cache when only a truncated token is provided, with informative logging for both cases.
  • Updated WAMonitoringService.loadInstancesFromRedis/loadInstancesFromDatabasePostgres/loadInstancesFromProvider to attempt loading WhatsApp Business full tokens from cache and pass them into setInstance.
  • Updated WAMonitoringService.setInstance and InstanceController.createInstance flow to treat WhatsApp Business setInstance as async and to pass the original full token (when available) instead of the truncated hash, awaiting completion before proceeding.
src/api/integrations/channel/meta/whatsapp.business.service.ts
src/api/services/monitor.service.ts
src/api/controllers/instance.controller.ts
Adapt all token-based authentication and lookup paths to compare only the first 255 characters while opportunistically capturing and caching full WhatsApp Business tokens.
  • In auth.guard.apikey, changed token comparison to use a 255-char prefix of the provided key, and when a WhatsApp Business instance authenticates with a >255-char key, store the full token in cache and re-run setInstance on the in-memory instance with the full token.
  • In the /instance/fetchInstances path, adjusted instance lookup to use the 255-char prefix and added the same full-token caching and in-memory instance update behavior for WhatsApp Business.
  • Updated WebsocketController authentication to look up instances by the 255-char prefix of apiKey instead of the full string.
  • Adjusted AuthService.isDuplicateToken to detect duplicate tokens using only the first 255 characters so long tokens still collide correctly with their truncated DB representation.
src/api/guards/auth.guard.ts
src/api/integrations/event/websocket/websocket.controller.ts
src/api/services/auth.service.ts

Possibly linked issues

  • [EN][BUG] #607: PR adiciona truncamento de msgCall em setSettings, evitando erro 500 ao salvar mensagem grande em Reject Calls.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The truncate() helper is defined inline in multiple services; consider extracting it (and the max-length values) into a shared utility or constants module to avoid duplication and ensure column limits stay consistent.
  • The logic around setInstance being sometimes async (e.g., instance as any and instanceof Promise checks) is a bit fragile; it would be clearer to normalize the interface (e.g., always returning a Promise and always awaiting it) instead of relying on runtime type checks.
  • The hard-coded 255 limit for token comparison appears in several places (auth guard, websocket controller, auth service); centralizing this as a single constant tied to the DB schema would reduce the risk of mismatches if the column size changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `truncate()` helper is defined inline in multiple services; consider extracting it (and the max-length values) into a shared utility or constants module to avoid duplication and ensure column limits stay consistent.
- The logic around `setInstance` being sometimes async (e.g., `instance as any` and `instanceof Promise` checks) is a bit fragile; it would be clearer to normalize the interface (e.g., always returning a Promise and always `await`ing it) instead of relying on runtime type checks.
- The hard-coded `255` limit for token comparison appears in several places (auth guard, websocket controller, auth service); centralizing this as a single constant tied to the DB schema would reduce the risk of mismatches if the column size changes.

## Individual Comments

### Comment 1
<location> `src/api/services/monitor.service.ts:297-306` </location>
<code_context>
-        number: instanceData.number,
-        businessId: instanceData.businessId,
-      });
+      // Para WhatsApp Business, setInstance é async e precisa ser aguardado
+      if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
+        await (instance as any).setInstance({
</code_context>

<issue_to_address>
**suggestion:** The Promise detection around setInstance can be simplified and made more robust.

You can drop the `instanceof Promise` check and always `await` the result of `setInstance`:

```ts
const maybePromise = (instance as any).setInstance({ ... });
await maybePromise;
```
This also handles non-native thenables and matches the controller’s `await (instance as any).setInstance(...)` pattern.
</issue_to_address>

### Comment 2
<location> `src/api/services/monitor.service.ts:351-354` </location>
<code_context>
             return;
           }

+          // Para WhatsApp Business, tenta carregar token completo do cache
+          let token = instanceData.token;
+          if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
+            const cacheKey = `instance:${instanceData.name}:fullToken`;
+            const fullToken = await this.cache.get(cacheKey);
+            if (fullToken) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Cache key uses `instanceData.name` while the rest of this block relies on `instanceName`, which may cause key mismatches.

The cache key here uses `instanceData.name`, while nearby code and other usages build `instance:${instanceName}:fullToken` (with `instanceName` derived from `k.split(':')[2]` or `instance.instanceName`). If `name` and `instanceName` ever differ, tokens written under one key won’t be readable with the other. Please standardize on a single field (likely `instanceName`) for all cache key construction for this token.
</issue_to_address>

### Comment 3
<location> `src/api/integrations/channel/meta/whatsapp.business.service.ts:61-69` </location>
<code_context>
+  }
+
+  // Override setInstance para armazenar/carregar token completo
+  public async setInstance(instance: any) {
+    super.setInstance(instance);
+
+    // Se o token fornecido é maior que 255, é o token completo - armazena imediatamente
+    if (instance.token && instance.token.length > 255) {
+      this.fullToken = instance.token;
+      const cacheKey = `instance:${instance.instanceName}:fullToken`;
+      await this.cache.set(cacheKey, instance.token, 0);
+      this.logger.log(`Stored full token in cache for ${instance.instanceName}`);
+    } else {
+      // Tenta carregar token completo do cache
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider making the async contract of setInstance explicit and consistent with the base class.

Here `setInstance` is now `async` and some callers are awaiting it, but the base `ChannelStartupService.setInstance` is still effectively synchronous. This split contract can be confusing and lead to timing issues around when `fullToken` is available.

I’d suggest either:
1) Making the async contract explicit on both base and override (e.g. `Promise<void>`) so it’s clear this is part of the instance lifecycle, or
2) Clearly documenting that only this subclass performs async cache IO and ensuring all relevant call sites consistently `await` it when they depend on cache state (e.g. `token` getter, connection startup).

Suggested implementation:

```typescript
  // Override setInstance para armazenar/carregar token completo
  public async setInstance(instance: any): Promise<void> {
    // Garante que qualquer lógica assíncrona definida na classe base seja respeitada
    await super.setInstance(instance);

    // Se o token fornecido é maior que 255, é o token completo - armazena imediatamente

```

Para tornar o contrato assíncrono explícito e consistente com a classe base, também será necessário:

1. Atualizar a assinatura de `setInstance` na classe base (provavelmente `ChannelStartupService` ou similar), por exemplo em `src/api/integrations/channel/...`:
   - Mudar de `public setInstance(instance: any) { ... }` para `public async setInstance(instance: any): Promise<void> { ... }`.
   - Se o corpo atual é totalmente síncrono, ele pode permanecer igual; apenas a assinatura passa a ser `async`/`Promise<void>`.

2. Verificar todos os locais onde `setInstance(...)` é chamado:
   - Para todos os call sites que dependem de estado inicializado por `setInstance` (como acesso a `token` ou início de conexão), garantir que a chamada seja `await this.setInstance(...)` e que o contexto seja assíncrono.
   - Se existirem pontos onde `setInstance` é chamado mas o resultado não é relevante (fire-and-forget), decidir explicitamente se:
     - deve ser aguardado com `await`, ou
     - deve ser chamado sem `await`, mas com comentários deixando claro que a inicialização é assíncrona e não é necessária imediatamente.

3. Atualizar qualquer interface/abstração que declare `setInstance` (por exemplo, uma interface de serviço de canal) para usar a assinatura `setInstance(instance: any): Promise<void>;` para manter o contrato de tipo consistente.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +297 to +306
// Para WhatsApp Business, setInstance é async e precisa ser aguardado
if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
const setInstanceResult = (instance as any).setInstance({
instanceId: instanceData.instanceId,
instanceName: instanceData.instanceName,
integration: instanceData.integration,
token: instanceData.token,
number: instanceData.number,
businessId: instanceData.businessId,
ownerJid: instanceData.ownerJid,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The Promise detection around setInstance can be simplified and made more robust.

You can drop the instanceof Promise check and always await the result of setInstance:

const maybePromise = (instance as any).setInstance({ ... });
await maybePromise;

This also handles non-native thenables and matches the controller’s await (instance as any).setInstance(...) pattern.

Comment on lines +351 to +354
// Para WhatsApp Business, tenta carregar token completo do cache
let token = instanceData.token;
if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
const cacheKey = `instance:${instanceData.name}:fullToken`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Cache key uses instanceData.name while the rest of this block relies on instanceName, which may cause key mismatches.

The cache key here uses instanceData.name, while nearby code and other usages build instance:${instanceName}:fullToken (with instanceName derived from k.split(':')[2] or instance.instanceName). If name and instanceName ever differ, tokens written under one key won’t be readable with the other. Please standardize on a single field (likely instanceName) for all cache key construction for this token.

Comment on lines 61 to 69
public async setInstance(instance: any) {
super.setInstance(instance);

// Se o token fornecido é maior que 255, é o token completo - armazena imediatamente
if (instance.token && instance.token.length > 255) {
this.fullToken = instance.token;
const cacheKey = `instance:${instance.instanceName}:fullToken`;
await this.cache.set(cacheKey, instance.token, 0);
this.logger.log(`Stored full token in cache for ${instance.instanceName}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider making the async contract of setInstance explicit and consistent with the base class.

Here setInstance is now async and some callers are awaiting it, but the base ChannelStartupService.setInstance is still effectively synchronous. This split contract can be confusing and lead to timing issues around when fullToken is available.

I’d suggest either:

  1. Making the async contract explicit on both base and override (e.g. Promise<void>) so it’s clear this is part of the instance lifecycle, or
  2. Clearly documenting that only this subclass performs async cache IO and ensuring all relevant call sites consistently await it when they depend on cache state (e.g. token getter, connection startup).

Suggested implementation:

  // Override setInstance para armazenar/carregar token completo
  public async setInstance(instance: any): Promise<void> {
    // Garante que qualquer lógica assíncrona definida na classe base seja respeitada
    await super.setInstance(instance);

    // Se o token fornecido é maior que 255, é o token completo - armazena imediatamente

Para tornar o contrato assíncrono explícito e consistente com a classe base, também será necessário:

  1. Atualizar a assinatura de setInstance na classe base (provavelmente ChannelStartupService ou similar), por exemplo em src/api/integrations/channel/...:

    • Mudar de public setInstance(instance: any) { ... } para public async setInstance(instance: any): Promise<void> { ... }.
    • Se o corpo atual é totalmente síncrono, ele pode permanecer igual; apenas a assinatura passa a ser async/Promise<void>.
  2. Verificar todos os locais onde setInstance(...) é chamado:

    • Para todos os call sites que dependem de estado inicializado por setInstance (como acesso a token ou início de conexão), garantir que a chamada seja await this.setInstance(...) e que o contexto seja assíncrono.
    • Se existirem pontos onde setInstance é chamado mas o resultado não é relevante (fire-and-forget), decidir explicitamente se:
      • deve ser aguardado com await, ou
      • deve ser chamado sem await, mas com comentários deixando claro que a inicialização é assíncrona e não é necessária imediatamente.
  3. Atualizar qualquer interface/abstração que declare setInstance (por exemplo, uma interface de serviço de canal) para usar a assinatura setInstance(instance: any): Promise<void>; para manter o contrato de tipo consistente.

Replace template string interpolation in logger calls with object-based
logging to prevent format string injection vulnerabilities detected by CodeQL.

- Use object-based logging instead of template strings
- Prevents external control of format strings in logs
- Maintains same logging functionality with better security
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant