Skip to content

feat: add Zalo Personal channel integration via zca-js#32

Open
suminhthanh wants to merge 4 commits into
Hesper-Labs:mainfrom
suminhthanh:main
Open

feat: add Zalo Personal channel integration via zca-js#32
suminhthanh wants to merge 4 commits into
Hesper-Labs:mainfrom
suminhthanh:main

Conversation

@suminhthanh
Copy link
Copy Markdown

What does this PR do?

Adds Zalo Personal as a new messaging channel, allowing agents to receive and respond to Zalo messages directly from
the Owly dashboard. This is the 6th supported channel alongside WhatsApp, Email, Phone, SMS, and Telegram.

Changes

Zalo Personal channel (src/lib/channels/zalo-personal.ts)

  • QR code login flow via zca-js library
  • WebSocket listener for real-time incoming messages
  • Credential persistence (encrypted) and automatic reconnect with exponential backoff
  • Send/receive text messages
  • Customer identity resolution (Zalo display name, phone extraction)

API routes

  • POST/GET /api/channels/zalo-personal — connect, disconnect, status polling
  • Updated /api/channels/[type] and /api/channels to include Zalo Personal
  • Updated /api/conversations/[id]/messages to support sending via Zalo

Dashboard

  • New channel management page (src/app/(dashboard)/channels/page.tsx) with cards for all channels
  • Zalo card with QR code display, real-time connection status polling, and disconnect action
  • Credential sanitization in API responses (tokens never exposed to frontend)

Schema & config

  • Added ZALO_PERSONAL to channel enum in Prisma schema
  • Added zca-js dependency
  • Updated Dockerfile
  • Updated README with 6-channel documentation and roadmap progress

Test plan

  • Connect Zalo Personal via QR code scan and verify login succeeds
  • Send a message from Zalo — verify it appears in the conversations inbox
  • Reply from the dashboard — verify message delivered to Zalo user
  • Disconnect and verify status updates in the channel card
  • Verify credential sanitization — API responses must not leak tokens
  • Verify reconnect logic — kill WebSocket and confirm automatic reconnect with backoff

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • npm run build passes without errors
  • Code follows the project's style guidelines
  • Self-reviewed the code
  • Added/updated relevant documentation (if applicable)

    QR code login, credential persistence, WebSocket listener, message
    send/receive, customer identity resolution, and dashboard card with
    real-time status polling. Includes credential sanitization in API
    responses and reconnect logic with backoff.
hsperus

This comment was marked as duplicate.

Copy link
Copy Markdown
Collaborator

@hsperus hsperus left a comment

Choose a reason for hiding this comment

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

Hey, I went through the entire PR in detail. Overall it's a well structured feature, things like exponential backoff reconnect, credential sanitization and cache eviction are nicely done. But there are a few important things that need to be addressed before merge.

The most critical issue is credential storage. The PR description mentions "encrypted" but looking at the code, imei, cookie and userAgent are written to the database as plain text. I couldn't find any encryption in the saveCredentials function. Anyone with database access can read these credentials. At minimum something like AES-256-GCM should be applied here, this is a blocker from a security standpoint.

The Prisma schema has been updated with the ZALO_PERSONAL enum and zalo field but there's no migration file in the PR. This will cause issues during production deploy. Please run prisma migrate dev and include the migration file.

The scope of the PR is also quite broad. On top of the Zalo integration there are data.data transformations across 10+ dashboard pages. These fixes aren't directly related to Zalo and they make the review harder to follow. I'd suggest moving those into a separate PR, it makes review easier and keeps rollbacks clean if anything goes wrong.

On the architecture side, there are global mutable variables like zaloApi, qrImageBase64 and connectionStatus defined at module level. This works fine with a single Node.js process but in serverless or multi instance environments they'll reset on every cold start. Not a blocker right now but this limitation should be documented and ideally moved to something like Redis down the road.

The startZaloQRLogin function wraps loginQR (which already returns a Promise) inside a new Promise constructor. If the callback never fires the Promise will hang forever. A timeout mechanism should be added there.

The sanitizeConfig function is duplicated across three files (channels/[type]/route.ts, channels/route.ts, channels/zalo-personal/route.ts). This could be extracted into a single shared utility.

Lastly the PR is opened from main to main. Using a feature branch like feat/zalo-personal would be better practice going forward.

In short: credential encryption and the migration file are must haves, and splitting out the data.data fixes into their own PR would really help. Happy to take another look once those are sorted, nice work overall.

… channels, and improved conversation handling
…ment

- Add connection management for Zalo API with QR login and saved credentials.
- Implement message handling for incoming messages, including self-messages and AI responses.
- Introduce filtering mechanism for allowlist/denylist based on sender and thread IDs.
- Enrich customer profiles with data from Zalo, including display names and phone numbers.
- Create helper functions for thread metadata updates and user/group name resolution.
- Set up a listener for Zalo API events to manage connection status and message processing.
- Add validation schemas for Zalo actions and configuration updates.
- Implement caching for user and group information to optimize API calls.
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.

2 participants