1Summary
What the product is and where it stands today.
Fun Chat is a real-time messaging product: one-to-one and group conversations, media, read receipts, and the group-management controls people expect once a chat has more than a handful of participants. It ships as a native Android app, a native iOS app, and two web clients that share one codebase — a phone layout and a three-pane desktop layout — plus a separate console for moderation.
The server is a single Go binary exposing 93 HTTP routes and one WebSocket endpoint. Every client speaks the same API; the web clients are served from the same origin as the API they call, so no client needs cross-origin permission to work.
Status. The feature set below is built and verified end to end. What separates this from a launch candidate is listed in §7 — chiefly unauthenticated media delivery, and a storage backend that currently keeps everything in memory.
2Test environments & accounts
Where to test, what is signed in already, and what a restart takes away.
2.1 Environments
Two deployments run the same build. The first is the one to use; the second answers on its own address and is how to tell a server fault apart from something the CDN in front of the first one did.
| Environment | Address | What it serves |
|---|---|---|
| Primary | api.youxin.chat | API and WebSocket, behind a CDN. Web clients at /h5/, /pc/ and /admin/. |
| Marketing | youxin.chat | Landing page. The Android build downloads from /downloads/funchat-release.apk. |
| Direct | 155.138.164.68.nip.io | The same three web clients on the same paths, with no CDN in the way. |
Use the path form, not the subdomain. h5.youxin.chat, pc.youxin.chat and admin.youxin.chat appear in the deployment design (§12) but have no DNS record yet, so they do not resolve. Everything is reachable today under api.youxin.chat/h5/, /pc/ and /admin/.
2.2 Standing accounts
Three member accounts are recreated automatically after every server start. The password for all three is FunChat2026.
| Account | Display name | Starts out |
|---|---|---|
| test01 | Test One 测试一号 | Contact of test02, with a two-message 1:1 thread. Owns the seeded group. Has a request from test03 waiting. |
| test02 | Test Two 测试二号 | Contact of test01. Plain member of the group. |
| test03 | Test Three 测试三号 | Nobody’s contact yet. Plain member of the group. Its pending request to test01 is the fixture for the accept / decline flow. |
The seeded group is 产品设计组 (“Product Design”) — all three accounts, an announcement set, three messages. Registration is open, so more accounts can be created freely; nothing has to be requested.
Console credentials are deliberately not printed in this document. They live in the environment file on the server; ask whoever owns the deployment.
Everything is held in memory. A restart erases every account, message and upload, and then recreates exactly the state above. So a defect that depends on data you created is only reproducible until the next restart: capture the request and the response while you still have them, and prefer a reproduction that starts from the seeded state. This is OI-2 in §7 — it is known, and not something to report.
2.3 The Android build
The release APK is signed and minified and refuses cleartext HTTP, so it will only talk to an HTTPS host. Installing it needs “install unknown apps” granted to whichever browser downloads it. It is not a debug build: stack traces come back obfuscated, so record the steps rather than relying on the trace.
3Platform scope
Where each capability is available. The two web clients share one implementation.
| Capability | Android | iOS | Web phone | Web desktop | Console |
|---|---|---|---|---|---|
| Accounts & sessions | ● | ● | ● | ● | — |
| 1:1 and group messaging | ● | ● | ● | ● | — |
| Media send & preview | ● | ● | ● | ● | — |
| Group administration | ● | ● | ● | ● | — |
| In-conversation search | ● | ● | ● | ● | — |
| Push notifications | ● | ● | — | — | — |
| Moderation & audit | — | — | — | — | ● |
● available — not applicable to that surface
4Functional requirements
Grouped by area. Status reflects verified behaviour on the current build, not intent.
4.1 Accounts & identity
Authorization header.4.2 Contacts & safety
4.3 Conversations & groups
single-<low>-<high>) and are therefore guessable, so every read is authorised against membership rather than against the identifier.4.4 Messaging
4.5 Media
4.6 Real-time delivery & notifications
4.7 Moderation console
5Limits, timings & error codes
The boundary values worth testing at. Every figure here is read out of the shipped source, not out of an intention.
5.1 Input limits
| Field | Accepted | Rejected with |
|---|---|---|
| Account name | 3–32 characters of letters, digits, dot, underscore or dash | 400 |
| Password | 8–128 characters | 400 |
| Message text | up to 8000 bytes — not characters, so a Chinese character costs three | 400 |
| Quoted text | the same 8000 bytes | 400 |
| Upload | up to 50 MB | 400 |
| Image dimensions | 40 megapixels — a small file with enormous dimensions is refused | 400 |
| JSON request body | 1 MB | 400 |
Uploads are accepted only for these extensions, and the bytes have to match the type the name claims:
.aac .bin .doc .docx .gif .jpeg .jpg .m4a .mov .mp3 .mp4 .ogg .pdf .png .ppt .pptx .txt .wav .webm .webp .xls .xlsx .zip
5.2 Rate limits
A sliding one-minute window per client IP address, not per account — so a whole test team behind one office address shares every bucket below. Worth knowing before reporting a mysterious 429.
| Action | Per minute |
|---|---|
| Register | 5 |
| Sign in | 10 |
| Refresh a token | 30 |
| Send a message | 120 |
| Recall a message | 60 |
| Upload media | 30 |
| Push receipts | 120 |
Exceeding one returns 429 and too many requests. Separately, five failed sign-ins lock an account for fifteen minutes; that lock is on the account, so it follows the person to another device.
5.3 Timings
| Behaviour | Value | What happens at the edge |
|---|---|---|
| Recall window | 120 s | After two minutes recall is refused and the message stays as it was. |
| Socket authentication | 10 s | A socket that has not authenticated within ten seconds of connecting is closed. |
| Heartbeat | 30 s | The server pings every thirty seconds; a client silent for ninety is dropped. |
| Socket frame size | 32 KB | A larger frame closes the connection rather than truncating it. |
| Console session | 12 h | Then the console drops back to its sign-in screen. |
| Concurrent sockets | 2000–4000 | Per deployment. Past the ceiling the upgrade is refused, not queued. |
5.4 Responses
Every endpoint answers in the same envelope. code is 0 on success and 1 on failure; err and msg repeat code and message for an older client generation. The HTTP status carries the class of failure:
| Status | Means | Usual cause |
|---|---|---|
| 200 | Success | Body is { "code": 0, "message": "ok", "data": … }. A list is always an array, never null. |
| 400 | The request is wrong | A limit from §5.1, a missing field, or a value the server will not take. |
| 401 | Not signed in | No bearer token, or an expired one. Sign in again — on its own this is not a defect. |
| 403 | Signed in, not allowed | Not a member of the group, not an admin of it, or muted in it. |
| 404 | No such thing | Also what routes that exist but are switched off in this deployment return, single sign-on among them. |
| 429 | Rate limited | §5.2. |
| 500 | Server fault | Always worth reporting. Include the time — the server log is correlated by timestamp. |
What makes a report actionable. The request (method, path, body), the whole response body including the envelope, which account it happened on, and the time. A screenshot of a client on its own rarely separates a client fault from a server one.
6Non-functional requirements
Constraints the implementation has to satisfy regardless of feature.
6.1 Security
unsafe-inline for scripts, frame denial, MIME sniffing off, referrer policy, and HSTS — the last only over real TLS or from a proxy the deployment is configured to trust.6.2 Reliability & performance
[], never null — a null once surfaced in the Android client as an error banner reading "ok".6.3 Compatibility
7Known defects — do not re-report
Every one of these is already logged. Reproducing one is not a finding — please skip past them and spend the time on what is not listed here. Ordered by what would block a public launch.
Uploaded media is served without authorisation OI-1 · blocker
Anyone with the URL can fetch a file from a private conversation. File names are random, so the URLs are unguessable but not secret — and they travel to every member of a conversation. A complete fix needs authorisation at fetch time plus a file-to-conversation index; a partial fix that only obscures the URL would look like a fix without being one, so nothing has been changed yet.
Deployments run the in-memory backend OI-2 · blocker
A restart clears every account and message. The persistent backend is implemented and tested, but MongoDB refuses to start on the current hosts' kernel version, and no packaged release yet supports it. Resolution is a host with an older kernel, a managed MongoDB, or an upstream release — not a code change.
The conversation list does not update live OI-3 · high
Incoming messages update the open conversation but not the list's preview, timestamp or unread badge. Affects Android and the web clients, which share the behaviour. Tracked as FR-4.9.
iOS has no automated tests OI-4 · medium
Eleven source files, no test target, and no continuous-integration job. Android has twelve unit tests but the build pipeline does not execute them.
Cross-client checks are not automated OI-5 · medium
Roughly 260 browser and end-to-end assertions exist and pass, but they are run by hand. Until they run on every change they protect nothing.
The landing page’s legal links are dead OI-7 · low
youxin.chat links to /legal/privacy and /legal/terms; both return 404. The documents themselves exist in the repository but nothing serves them at those paths. Confirmed against the live site on 7 September 2026.
A conversation with oneself can be created OI-6 · low
Blocking yourself and adding yourself as a contact are both refused; opening a conversation with yourself is not. No client offers it, so it looks like a missing guard rather than a feature. Behaviour is pinned by a test either way.
8What is already covered automatically
Ground that machine checks hold, so manual effort can go elsewhere. None of it runs on every change yet — that is OI-5.
8.1 Server tests
117 Go test functions. The largest groups are the HTTP layer (43), storage (32) and the console (14). Within storage, 61 of those cases are a single behavioural suite run against both storage implementations — the in-memory one these deployments use and the database-backed one — so the two cannot quietly disagree. Writing that suite is what turned up the blocking, session-revocation and form-decoding defects already fixed in this build.
8.2 End-to-end and browser checks
Roughly 263 assertions, run by hand against a live deployment:
| Harness | Assertions | Covers |
|---|---|---|
| endpoint_sweep | 92 | Every member and console route, with its authorisation rules. |
| qa_suite | 78 | The main user journeys, end to end. |
| web_group_check | 25 | Group management in the desktop web client. |
| qa_retest | 21 | Defects fixed earlier, held fixed. |
| audit_probe | 15 | Security boundaries: tokens, headers, forged proxy headers. |
| web_h5_check | 12 | The phone web layout. |
| web_member_view | 11 | What a plain member may and may not see. |
| admin_ui_check | 9 | The console shell and its session handling. |
8.3 Where manual testing pays
- iOS. No automated coverage at all. Everything there is unverified until a person runs it.
- Android on real hardware. The unit tests never touch the interface, and nothing exercises notifications, backgrounding, or losing the network mid-send.
- Two devices at once. Read receipts, unread counts and live delivery only mean anything with two sessions open, which no harness arranges.
- Media that is awkward rather than invalid. A 49 MB file; a file whose extension lies about its contents; a photo straight off a phone camera at full resolution.
- Anything visual. Layout, truncation, long display names, right-to-left text, dark mode.
9Goals & non-goals
What this release is trying to be — and what it deliberately is not.
Goals
- One conversation, every device. Unread state, read receipts and history stay consistent whether a person opens the phone app or a browser.
- Messages arrive now, not on refresh. A persistent socket with automatic reconnection; push notification when the socket is gone.
- Groups people can actually run. Announcements, admins, mutes, invitations and removals — not just a shared thread.
- An operator can answer for the platform. Every moderation action is attributable, reversible where it should be, and written to an audit log.
- Safe by default. No built-in credential, no fallback token, nothing that only works because a setting was forgotten.
Non-goals for this release
- Voice and video calling.
- End-to-end encryption. Transport is TLS; the server can read message content, which is what the moderation console depends on.
- Federation or self-hosting by third parties.
- Threads, reactions, scheduled messages, message editing.
- Desktop applications. The desktop experience is the web client.
10Users
Three audiences, three different jobs.
| Who | What they come to do | What failure looks like |
|---|---|---|
| Member | Reach one person or a small group quickly; send a photo or a file; know whether it was seen. | A message that silently didn't send, or a conversation list that isn't current. |
| Group owner | Set the topic, promote a helper, quiet a disruptive member, remove someone, wind the group up. | Having to ask an administrator to do something they should be able to do themselves. |
| Operator | Work a report queue, ban an account, tune the word filter, and later explain what was done and why. | An action with no record of who took it. |
11Architecture
One backend, five front ends, two interchangeable storage backends.
| Component | Built with | Notes |
|---|---|---|
| API server | Go 1.22 | Standard-library routing, gorilla/websocket. 93 routes plus /v1/ws. |
| Push gateway | Go 1.22 | Separate process. Talks to FCM, APNs and vendor endpoints; posts signed receipts back. |
| Android | Kotlin · Compose | Material 3, OkHttp, Coil, kotlinx.serialization. |
| iOS | Swift · SwiftUI | URLSession, native WebSocket. |
| Web clients | Vanilla JS | One implementation, two layouts. No framework, no build step. |
| Console | Vanilla JS | Separate application; delegated event handling so the CSP needs no inline-script exception. |
| Storage | MySQL · MongoDB · Redis | Relational for accounts and structure, document store for messages, Redis for sessions. An in-memory implementation of the same interface exists for test and preview. |
Both storage backends implement one repository interface of 92 methods and are held to a single shared behavioural test suite, so a change verified against the in-memory implementation cannot silently diverge in the one production runs.
12Deployment
How the current environment is arranged.
| Host | Serves |
|---|---|
| youxin.chat | Marketing site and Android download |
| api.youxin.chat | API and WebSocket |
| h5.youxin.chat | Web client, phone layout |
| pc.youxin.chat | Web client, desktop layout |
| admin.youxin.chat | Moderation console |
Of these, youxin.chat and api.youxin.chat resolve today; the three client hostnames do not yet, which is why §2 gives the path form instead.
Each hostname is proxied to the same backend, so every web client is same-origin with the API it calls and no cross-origin permission is required. The API binds to the loopback interface only; the reverse proxy is the sole public listener, which is what makes trusting its forwarded client address safe.
Shared host. This machine also runs unrelated services. Each project owns exactly one reverse-proxy configuration file and no project rewrites the shared entry point — a convention adopted after a deployment script overwrote it and took a neighbouring site offline.
13Deferred
Considered and consciously postponed.
- Schema migrations. Tables are created inline at start-up. Workable now; unworkable once the schema evolves across running instances.
- Field-name consistency. Several endpoints accept two names for the same field, kept for the previous client generation. Harmless but a reliable source of integration mistakes — a reference table should ship with the API documentation.
- Registration control. Anyone can create an account. An invitation code or approval step is needed before an environment holds real users.
- Web unit tests. The pure functions in the web client — row grouping, highlight escaping, URL scheme checks — are testable in isolation and currently covered only end to end.
14Appendix
14.1 API surface
| Group | Prefix | Routes |
|---|---|---|
| Authentication & account | /v1/auth · /v1/account | 9 |
| Contacts & safety | /v1/friends · /v1/blocks · /v1/reports | 10 |
| Conversations & groups | /v1/conversations · /v1/groups | 18 |
| Messaging | /v1/messages | 6 |
| Media, devices, metrics | /v1/media · /v1/devices · /v1/client | 3 |
| Real time | /v1/ws | 1 |
| Moderation console | /admin/api | 29 |
| Legacy aliases | /im | 4 |
14.2 Terms
| Term | Meaning |
|---|---|
| Conversation | A thread. Either 1:1, whose identifier is derived from the two participant ids, or a group with its own identifier. |
| Sequence | A per-conversation counter on every message. The basis for ordering, resumption and read state. |
| Recall | Withdrawing a sent message within two minutes. The message remains in history as a marker. |
| Mute | Two unrelated things: a person silencing a conversation for themselves, and a group owner preventing a member from sending. |
| Operator | A console account. Distinct from a member account; the two credential stores are separate. |