KickUser and CloseAll only wrote a close frame and called Conn.Close,
which fasthttp turns into a no-op on a hijacked conn, so a client that
ignores the frame stayed connected and stayed in the hub. Expire the read
deadline and tear the client down so Serve returns.
The agent app had the same replaced-socket problem as the widget, where a
stale socket's close event cleared the live socket's ping timer and the
server dropped it 60s later. Also check the widget client's closed flag
before refreshing the read deadline, and reset inbox and user id on
re-join.
SendMessage checked a separate Closed flag before sending, so a close
between the check and the send panicked on a closed channel. SendError
could also close the channel and then let Listen close it again on exit.
Both sends now go through trySend, which takes the same lock the close
takes, and close is idempotent.
A failed join response in the widget socket returned a nil client, so the
caller skipped cleanup and left the forwarder goroutine blocked forever.
It now removes the client and closes its channel first.
Close the widget conn when a write fails since the library marks it unusable, bail out of Listen through the cleanup path if the initial read deadline cannot be set, and nil the trailing slot in RemoveClient so the removed client is garbage-collected right away.
Hijacked websocket connections hold their fasthttp ctx until close, and
fasthttp keeps each recycled response buffer at full capacity, so memory
grew with every large response. Cap the pooled body size at 64 KiB and
share a write-buffer pool across both upgraders instead of allocating
8 KiB per connection at upgrade.
On the hot broadcast path, convToBroadcastMap did a marshal-unmarshal
round trip per event just to drop two per-user fields. Replace it with a
broadcastConv struct that shadows those fields via omitempty. Inbound
frames now decode once through json.RawMessage instead of re-marshaling
map[string]any per handler.
Also fix liveness: agent clients had no read deadline, so a peer that
vanished without closing (slept laptop, dropped wifi) blocked Listen
forever. Add ping/pong (25s ping, 60s pong wait), write deadlines on
both agent and widget writers, a 64 KiB read limit, and delete the
empty clients map entry in RemoveClient so user IDs don't leak.
Reject disabled agents in FilterAuthorizedListUUIDs, kick role members on
permission removal or role delete, and re-sub the list and open conv on
ws reconnect using local state. Also plug the app logger into the ws
package so kicks no longer log when no connections exist.
Convs and messages now push the full list-row payload to perm-authorized agents on create/assign/message, so livechat returning customers bubble up instantly without waiting for the timer refresh. Background list refresh bumped 30s -> 60s as a drift safety net.
Targets broadcasts via per-tab list and open subs (validated at subscribe time)
instead of broadcasting message uuid to all agents. Adds frontend list typing, throttled sound,
and many perf improvements to reduce cpu memory & database queries.
Other fixes: Enforce message-to-conversation binding in handleGetMessage
- Gate widget inbox_id on UUID to prevent enumeration.
- Throttle inbound WS frames per connection (typing/page_visit/ping)
- Restrict admin-set URLs to http/https
- Apply closed-conversation reply guard to media upload path (was text-only)
- Reject empty uploads and use io.ReadAll
- Validate inbox config before DB create; drop unused func VerifySignature
feat: Add HTTP utility functions for trusted origin checks
feat: Implement typing status broadcasting for live chat clients and agents.
feat: Add support for signed URLs in media manager
fix: Update database migration to handle duplicate visitors with same email address.
feat: Add conversation subscription and typing message models for WebSocket communication
feat: Implement conversation subscription management in WebSocket hub this is used for broadcasting typing indicator.
feat: Revamp widget JavaScript to improve mobile responsiveness and show unread messages if any.
- new vue app for serving live chat widget, created subdirectories inside frontend dir `main` and `widget`
- vite changes for both main app and widget app.
- new backend live chat channel
- apis for live chat widget
New columns in users table to store user availability status.
Websocket pings sets the last active at timestamp, once user stops sending pings (on disconnect) after 5 minutes the user availalbility status changes to offline.
Detects auto away by checking for mouse, keyboard events and sets user status to away.
User can also set their status to away manually from the sidebar.
Migrations for v0.3.0
Minor visual fixes.
Bump version in package.json
- feat: sets inter as the app font
- feat: update colour theme
- remove unncesary redirects after form is saved
- feat: addsconv reference number in information accordion
- feat: adds reference number filter to conversation list
- feat: adds title for router components
- chore: remove unused uuid columns from tables
- feat: adds filters support on conversations list
- refactor middlewares.go
- Adds new paginate.go for generating filtered paginated SQL queries this removes sql generation code from the conversations package.
- rename some components
- removes hardcoded `/uploads/`
- refactor: conversations store.
- feat: Show conversation subject on top of conversation messages list
- feat: adds shadow to panels
- update: schema.sql adds insert statements for priority / status
- fix: trim canned response content to 100 chars.
- revert: rename team conversations tab to unassigned.