mirror of
https://gitverse.ru/kpa39l/xmpp-server-prosody.git
synced 2026-09-29 09:45:02 +00:00
161 lines
7.0 KiB
Markdown
161 lines
7.0 KiB
Markdown
# Converse.js v14 blank page: websocket_url fix + auth verification
|
|
|
|
Follow-up to `webchat-conversejs-and-caddy-trap.md` (same ICQ project, 2026-08-28).
|
|
These lessons were confirmed after the initial web client deployment: the page served
|
|
HTML/CSS/JS fine but rendered NOTHING.
|
|
|
|
## ⚠️ ROOT CAUSE #1 (FIXED 2026-08-28): Converse v14 is an ES MODULE, not UMD
|
|
|
|
The v14 dist bundle (`converse.min.js`) is built as a genuine ESM module:
|
|
- it uses `import.meta.url` (webpack auto-publicPath) and ends with `export{c as default}`;
|
|
- it lazy-loads chunks via dynamic `import("./" + chunkName)` (e.g. `chunkjs/locales/...`).
|
|
|
|
Loading it with a classic `<script src="converse.min.js">` throws in Chrome/Firefox:
|
|
|
|
```
|
|
Uncaught SyntaxError: Cannot use 'import.meta' outside a module
|
|
```
|
|
|
|
→ the bundle NEVER executes → `converse` is undefined → blank page, and **no request to
|
|
`/xmpp-websocket` ever appears in nginx logs** (the client dies before connecting).
|
|
|
|
`node --check converse.min.js` passes (valid ES syntax), so syntax-checking gives FALSE
|
|
confidence. **Check for `import.meta` / `export{c as default}` instead** — build two
|
|
distinct bundles and shipped both:
|
|
- `dist/converse.min.js`: ESM entry — load ONLY via `<script type="module">` +
|
|
`import converse from '/dist/converse.min.js'` in a second inline module script.
|
|
- Serve the FULL dist directory from the release tarball (GitHub
|
|
`conversejs/converse` → release `converse.js-14.0.0.tgz` → `package/dist/`), which
|
|
contains: `chunkjs/locales/*`, `libomemo.esm.min.js`, `curve25519_compiled.wasm`,
|
|
`sounds/`, `webfonts/`, `images/`. Dynamic imports resolve relative to the module
|
|
URL, so `/dist/converse.min.js` ⇒ chunks at `/dist/chunkjs/...` automatically.
|
|
|
|
Minimal working skeleton (see /opt/icq/webchat/index.html for the full version):
|
|
|
|
```html
|
|
<link rel="stylesheet" href="/dist/converse.min.css">
|
|
<div id="conversejs"></div>
|
|
<script type="module" src="/dist/converse.min.js"></script>
|
|
<script type="module">
|
|
import converse from '/dist/converse.min.js';
|
|
converse.initialize({ websocket_url: 'wss://chat.nixg.ru/xmpp-websocket',
|
|
view_mode: 'fullscreen', i18n: 'ru' });
|
|
</script>
|
|
```
|
|
|
|
Verification (headless chromium, no extra installs — chromium is in snap on bigbox):
|
|
|
|
```bash
|
|
chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=15000 \
|
|
--dump-dom https://chat.nixg.ru/ | grep -c converse-login-form # 1 = UI rendered
|
|
# console errors BEFORE the fix:
|
|
chromium --headless --no-sandbox --enable-logging=stderr --virtual-time-budget=20000 \
|
|
--dump-dom https://chat.nixg.ru/ 2>&1 | grep -iE 'CONSOLE|Uncaught'
|
|
```
|
|
|
|
## ROOT CAUSE #2 (earlier, already fixed): `bosh_service_url` vs `websocket_url`
|
|
|
|
| Option | What Converse does |
|
|
|--------|-------------------|
|
|
| `websocket_url: 'wss://host/xmpp-websocket'` | Connects via WebSocket. **Use this.** |
|
|
| `bosh_service_url: 'wss://host/xmpp-websocket'` | Treats the URL as BOSH (HTTP long-polling endpoint). With a wss:// URL and no BOSH handler on the server, the connection fails SILENTLY and the UI never renders. |
|
|
|
|
Evidence from the v14 bundle:
|
|
|
|
```js
|
|
function d1() {
|
|
return ("WebSocket" in window || "MozWebSocket" in window)
|
|
&& cZ.get("websocket_url") ? cZ.get("websocket_url")
|
|
: cZ.get("bosh_service_url") ? cZ.get("bosh_service_url") : "";
|
|
}
|
|
```
|
|
|
|
So `websocket_url` takes priority; `bosh_service_url` is only a BOSH fallback. Passing a
|
|
wss:// URL via `bosh_service_url` is a guaranteed blank page.
|
|
|
|
## Locales are embedded in v14
|
|
|
|
The bundle contains all translations as lazy-loaded webpack chunks
|
|
(`./src/i18n/locales/*/LC_MESSAGES/converse.po`). `i18n: 'ru'` needs NO external locale
|
|
files — do not go hunting for .po files or a locales_path.
|
|
|
|
## `assets_path` default is `/dist`
|
|
|
|
Defaults contain `assets_path: "/dist"`. If you deploy bare files (converse.min.js/css in
|
|
the web root) rather than the full dist layout, set `assets_path` explicitly to a path
|
|
that exists, or Converse may look for extra assets under /dist and fail. This is the
|
|
leading hypothesis when the page is STILL blank after the websocket_url fix.
|
|
|
|
## Diagnosing a blank page without a browser
|
|
|
|
Check the nginx (webchat container) access log: **a healthy Converse loads JS/CSS with
|
|
200s AND then issues a GET to /xmpp-websocket** (the WS upgrade). If css/js get 200s but
|
|
NO /xmpp-websocket request ever appears, the client is failing during
|
|
initialize/rendering BEFORE it attempts the socket — i.e. a client-side config/asset
|
|
problem, not a server problem. Confirm the server side separately (see below) so you know
|
|
the failure is client-side.
|
|
|
|
```bash
|
|
docker logs icq-webchat --since 10m | grep -vE "GET /converse\.min" | tail -20
|
|
# look for: GET /xmpp-websocket (or absence of it)
|
|
```
|
|
|
|
## Verify auth end-to-end over WebSocket (slixmpp)
|
|
|
|
Proves the whole path (browser → Caddy → nginx → Prosody → SASL) works even when the web
|
|
client is broken. slixmpp needs a no-verify SSL context for self-signed certs:
|
|
|
|
```bash
|
|
pip install slixmpp --break-system-packages
|
|
```
|
|
|
|
```python
|
|
import asyncio, ssl, slixmpp
|
|
|
|
class Bot(slixmpp.ClientXMPP):
|
|
def __init__(self, jid, password):
|
|
super().__init__(jid, password)
|
|
self.add_event_handler('session_start', self.on_start)
|
|
self.add_event_handler('failed_auth', self.on_failed)
|
|
self.add_event_handler('disconnected', self.on_disconnect)
|
|
async def on_start(self, event):
|
|
print("AUTH OK:", self.boundjid)
|
|
await self.disconnect()
|
|
def on_failed(self, event):
|
|
print("AUTH FAIL"); self.disconnect()
|
|
def on_disconnect(self, event):
|
|
self.stop()
|
|
|
|
async def main():
|
|
bot = Bot('user@chat.nixg.ru', 'PASSWORD')
|
|
ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
|
|
bot.ssl_context = ctx
|
|
bot.connect(('wss://chat.nixg.ru/xmpp-websocket',)) # full external path incl. TLS
|
|
await asyncio.wait_for(bot.disconnected, timeout=25)
|
|
|
|
asyncio.run(main())
|
|
```
|
|
|
|
Expect `AUTH OK: user@chat.nixg.ru/...`. Notes:
|
|
- Direct 5222 TCP test will ALSO hit `SSLCertVerificationError` on self-signed certs —
|
|
the `ssl_context` override fixes both, but prefer the wss:// test (validates Caddy TLS
|
|
and the WS proxy too).
|
|
- `slixmpp.connect()` has NO `reattempt=` kwarg in current versions — omit it.
|
|
- `prosodyctl register user domain pass` inside the container is how you set/reset a
|
|
password (idempotent, works even if the account exists).
|
|
|
|
## CDN-hardening: local assets
|
|
|
|
cdn.conversejs.org may be unreachable/slow from RF. Download the release once and serve
|
|
locally:
|
|
|
|
```bash
|
|
cd /opt/icq/webchat
|
|
curl -sL -o converse.min.js https://cdn.conversejs.org/dist/converse.min.js
|
|
curl -sL -o converse.min.css https://cdn.conversejs.org/dist/converse.min.css
|
|
# NOTE: https://cdn.conversejs.org/css/converse.min.css does NOT exist (404); the css
|
|
# lives under /dist/ in v14. Check with `curl -sI` and `head -c 100` — a 1.4KB "css" is a 404.
|
|
```
|
|
|
|
Verify the downloaded JS parses: `node --check converse.min.js` → "JS СИНТАКСИС OK".
|
|
(If `node` isn't present, `which node` first; the Hermes sandbox runs it from ~/.local/bin.) |