How It Works
AJAX polling, encrypted storage and safe previews.
Delivery
The widget polls the module's own admin controller through WHMCS. No WebSocket server, daemon, extra port or reverse-proxy rule is involved.
admin/addonmodules.php?module=StaffChat&p=chat&a=history
admin/addonmodules.php?module=StaffChat&p=chat&a=poll
admin/addonmodules.php?module=StaffChat&p=chat&a=send
admin/addonmodules.php?module=StaffChat&p=chat&a=read
admin/addonmodules.php?module=StaffChat&p=chat&a=active
All chat actions are POST-only, CSRF-protected and require X-Requested-With: XMLHttpRequest. A plain navigation request returns 404 {"ok":false,"error":"Not found"}.
Polling and presence
- The client polls for messages newer than its cursor, so each request only returns new data.
- Presence is stored with a TTL and refreshed on every poll; expired admins drop off the online list.
- Unread counters are persisted server-side per admin and room, so they survive a refresh.
- Typing state is short-lived and pruned automatically.
- The polling interval is configurable from 1 to 60 seconds.
Storage files
data/history.json— encrypted rooms, messages, edits and reactions.data/attachments.json— encrypted attachment metadata.data/uploads/*.enc— encrypted attachment files.data/unread.json— per-admin unread counters.data/presence.json— online and presence state.data/typing.json— typing state.data/previews.json— cached link previews.data/analytics-cache.json— encrypted dashboard aggregates, invalidated by the history and attachment signatures.data/rate-limits.json— encrypted per-admin send, preview and upload counters, shared across sessions.
The whole data/ directory is denied over HTTP by the bundled .htaccess. The settings and activity log use the hsc_staffchat_settings and hsc_staffchat_log tables.
Encryption
History, attachment metadata, previews, presence and typing data are stored with the WHMCS encrypt()/decrypt() helpers. Attachment bytes are encrypted at rest and decrypted only inside the authenticated download endpoint; no plaintext copy is written to disk.
Attachments
- One attachment per message, uploaded over an authenticated POST with a CSRF token.
- MIME type is checked against the allowlist; images are additionally validated with
getimagesize(). - Files are stored under random names with an
.encextension; the original name is kept only for display. - Downloads require an authenticated request and are streamed with the correct headers.
- Orphaned files are pruned by the daily cron.
Link previews
- Built server-side and rendered as text-only title and description.
- Only HTTP and HTTPS URLs, and never URLs containing credentials.
- Private, loopback, link-local and reserved IP ranges are rejected.
- Redirects are not followed; the resolved IP is pinned for the request.
- A 3-second timeout, a 512 KB response limit and a one-day cache apply.
Notifications
Desktop notifications use a service worker when the browser supports it and fall back to new Notification() otherwise. The worker is registered under the module asset scope and only handles notification clicks; it does not cache pages or intercept chat traffic.
Cron
- Activity log retention and row-cap enforcement.
- Chat history retention and per-room trimming.
- Live-only history wipe.
- Attachment encryption migration and orphan pruning.
Performance
- Polling returns only messages newer than the client cursor.
- History files are read and written under a lock to avoid partial writes.
- Link previews are cached so repeated URLs do not trigger new outbound requests.
- For large teams, raise the polling interval and lower the per-room message cap.