跪拜 Guibai
← All articles
Frontend · Backend

CaoIM: Self-Hosted Chat on a Single Flutter Codebase

By 小码哥_常 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Developers who want to run their own chat get a deployable system with feature parity across desktop, mobile, and browser from one Flutter codebase. But putting it into production means changing the default admin password and JWT secret, and most public-network voice/video calls will require adding a TURN server to the provided STUN-only setup.

Summary

CaoIM ships a complete self-hosted chat system. The server side splits into im-server on port 8080 (WebSocket, presence, history, read receipts, call signaling) and app-server on port 8081 (registration, login, profiles, file upload), with an optional push-server for offline notifications and a Vue 3 admin console for user and message moderation. The client is a single Flutter codebase that builds for Android, iOS, Windows, macOS, and Web; the browser gets the same interface instead of a separate web app.

Messaging covers text, emoji, images, camera capture, voice, files, and short video, with quoting, forwarding, recalling, and favoriting. One-on-one voice and video calls exist, but group calls don't. Media stays peer-to-peer; the server only forwards signaling. The default setup uses public STUN, which handles LAN, but public networks will often need a TURN server. After login, clients hold two tokens: token for app-server, imToken for im-server. Text goes over the long connection; files upload to app-server and messages carry the URL, stored locally or in S3-compatible object storage.

Deployment requires JDK 17, Maven 3.6, MySQL 5.7 with utf8mb4, Flutter 3, Dart 3.11+, and Node.js 18 for the admin page. The default admin account is admin/admin123, and both that password and the JWT secret need changing before going live. The project is free for personal use; commercial use needs authorization.

Takeaways
— One Flutter client covers Android, iOS, Windows, macOS, and Web; the web version is Flutter Web, not a separate codebase.
— Server code splits into im-server (port 8080) and app-server (port 8081), plus an optional push-server and a Vue 3 admin console.
— Messages support text, emoji, images, camera, voice, files, and short video, with quote, forward, recall, and favorite actions.
— Voice and video calls are one-on-one only; group calls are absent from the current code.
— Default setup uses public STUN; LAN calls work, but public networks likely require a TURN server.
— Login issues two tokens: token for app-server and imToken for im-server.
— Text messages use the long connection; images and files upload to app-server, with storage either local or S3-compatible.
— Offline users receive messages on reconnect, and read receipts show sender status and group reader counts.
— Deployment needs JDK 17, Maven 3.6, MySQL 5.7 (utf8mb4), Flutter 3, Dart 3.11+, and Node.js 18.
— Default admin is admin/admin123; change the password and JWT secret before going live.
Conclusions

Reusing the same Flutter code for the browser instead of building a dedicated web client gives perfect feature parity, but it also commits every platform to Flutter Web's rendering and performance characteristics.

Separating auth into token and imToken lets the API and the realtime channel be managed independently, so one can be rotated or revoked without dropping the other.

Concepts & terms
STUN
A protocol that lets a peer discover its public IP and port for establishing direct media connections; works on LAN without extra setup.
TURN
A relay server that forwards media when direct peer-to-peer connections fail, which is often necessary for calls across restrictive public networks.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗