Appearance
Troubleshooting
Common issues and how to resolve them.
WebSocket Connection Fails
Symptom: The widget never connects, or client.connectionState stays at "connecting".
Cause: Wrong WebSocket endpoint protocol or URL.
Fix:
- Use
wss://when your server is behind TLS (HTTPS). Usews://only for local development. - Ensure the endpoint URL points to the root path of the Pulse server (e.g.,
wss://pulse.yourdomain.com), not to/api/v1or any sub-path. - If using a reverse proxy, confirm it is configured to pass WebSocket upgrade headers (
UpgradeandConnection).
html
<!-- Local development -->
<pulse-widget endpoint="ws://localhost:4567" ...></pulse-widget>
<!-- Production (behind TLS) -->
<pulse-widget endpoint="wss://pulse.yourdomain.com" ...></pulse-widget>Expired JWT Token
Symptom: The server sends an auth:error message, or the widget shows a connection error after working previously.
Cause: The user token issued by /auth/token has expired.
Fix:
- Tokens are short-lived by design. Your backend should mint a fresh token each time the user loads the page.
- Do not cache tokens on the client for extended periods. Fetch a new token on each page load or when re-initializing the widget.
javascript
// Always fetch a fresh token before initializing
const res = await fetch('/api/collab-token');
const { token } = await res.json();
const widget = document.createElement('pulse-widget');
widget.setAttribute('token', token);
// ...CORS Errors
Symptom: Browser console shows Access-Control-Allow-Origin errors when the widget tries to upload a file or when calling the REST API.
Cause: The Pulse server's ALLOWED_ORIGINS environment variable does not include your frontend's origin.
Fix:
- Set
ALLOWED_ORIGINSto a comma-separated list of your frontend origins:
bash
ALLOWED_ORIGINS=https://yourdomain.com,https://app.yourdomain.com- During development, you can omit
ALLOWED_ORIGINS(defaults to allowing all origins), but always restrict it in production. - Make sure the origin includes the protocol (
https://) and does not include a trailing slash.
Rate Limiting (429 Responses)
Symptom: API requests return 429 Too Many Requests.
Cause: You've exceeded the rate limit of 100 requests per minute per IP address (or 5/min for the admin login endpoint).
Fix:
- Add backoff/retry logic in your integration code. Wait and retry after the rate limit window resets.
- If you're making bulk API calls from a server, throttle your requests to stay under the limit.
- Requests from
127.0.0.1and::1(localhost) are excluded from rate limiting.
File Upload Fails
Symptom: Uploading a file returns an error (400, 403, or 413).
Common causes and fixes:
| Status | Message | Fix |
|---|---|---|
400 | "No file provided" | Ensure the request sends a file field in multipart/form-data |
400 | "Unsupported file type" | Only JPEG, PNG, GIF, WebP images; WebM, OGG, MP4, MPEG, WAV audio; and WebM, MP4 video are supported |
403 | "Image/Audio/Video uploads are disabled" | Enable the media type in Environment Config via the admin panel |
413 | "File too large" | The file exceeds maxFileSizeMb (default 10 MB). Increase the limit in environment config, or reduce the file size |
Missing Cursors or Presence
Symptom: Users don't see each other's cursors or presence avatars.
Fix:
- Confirm both users are connected to the same room (the
roomattribute must match exactly). - Check that
showCursorsandshowPresenceare enabled in your Environment Config. - Users appear offline after 60 seconds of no heartbeat. If a user's tab is suspended by the browser, they may drop from presence.
Data Not Syncing Across Instances
Symptom: Users on different server instances don't see each other's changes in real time.
Cause: Redis is not configured for pub/sub across instances.
Fix:
- Set the
REDIS_URLenvironment variable so all instances share a Redis pub/sub channel. See the Deployment guide for details.
Admin Panel Not Loading
Symptom: Navigating to /panel shows a blank page or 404.
Cause: The admin panel static files are not built or not in the expected location.
Fix:
- Run the full build so the admin panel assets are generated.
- The server expects the admin dist at
packages/admin/distrelative to the server source. If using Docker, the Dockerfile handles this automatically.