Skip to content

Commit 25ea498

Browse files
ralyodioclaude
andcommitted
fix(irc): complete SASL PLAIN login over WebSocket (v3.3.3)
The client could connect to Ergo (origin fix deployed) but failed registration with "You must log in with SASL". Fixes: - CAP LS: accumulate caps across 302 multiline (use the trailing param, honor the `*` continuation) so `sasl` is actually detected. - CAP ACK: AUTHENTICATE PLAIN whenever sasl is acked (don't gate on a truthy password var); send creds, then CAP END only on 903 success. - SASL failure (902/904/905/906): error + disconnect instead of CAP END (the network is SASL-required, so completing registration just gets rejected). - Connect form now requires a password. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b9ba797 commit 25ea498

27 files changed

Lines changed: 43 additions & 34 deletions

File tree

apps/desktop/extensions/ai-sidebar/chat.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ async function doConnect() {
8787
password: $('irc-pass').value,
8888
channels: $('irc-chans').value.split(/[,\s]+/).filter(Boolean),
8989
};
90-
if (!cfg.nick) { setStatus('Enter your username', 'err'); return; }
90+
if (!cfg.nick) { setStatus('Enter your BBS username', 'err'); return; }
91+
if (!cfg.password) { setStatus('Enter your IRC password (this network requires SASL login)', 'err'); return; }
9192
if ($('irc-remember').checked) {
9293
chrome.storage.local.set({ [STORE_KEY]: cfg });
9394
}

apps/desktop/extensions/ai-sidebar/irc.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class IrcClient extends EventTarget {
5252
connect(opts) {
5353
this.opts = { url: DEFAULT_URL, channels: ['#general'], ...opts };
5454
this.nick = this.opts.nick;
55+
this._caps = new Set();
5556
this.emit('status', { state: 'connecting' });
5657
let ws;
5758
try {
@@ -84,12 +85,18 @@ export class IrcClient extends EventTarget {
8485
case 'PING': this.send(`PONG :${params[0] || ''}`); break;
8586
case 'CAP': {
8687
const sub = params[1];
88+
const list = params[params.length - 1] || '';
8789
if (sub === 'LS') {
88-
const offered = (params[2] || '').split(' ');
89-
const want = ['sasl', 'server-time', 'message-tags'].filter((c) => offered.includes(c));
90-
this.send(`CAP REQ :${want.join(' ')}`);
90+
// 302 multiline: a `*` param before the trailing list means more lines.
91+
list.split(' ').filter(Boolean).forEach((c) => this._caps.add(c.split('=')[0]));
92+
if (params[2] !== '*') {
93+
const want = ['sasl', 'server-time', 'message-tags'].filter((c) => this._caps.has(c));
94+
if (want.length) this.send(`CAP REQ :${want.join(' ')}`);
95+
else this.send('CAP END');
96+
}
9197
} else if (sub === 'ACK') {
92-
if ((params[2] || '').includes('sasl') && this.opts.password) this.send('AUTHENTICATE PLAIN');
98+
// Authenticate as soon as SASL is acknowledged. CAP END comes after.
99+
if (list.includes('sasl')) this.send('AUTHENTICATE PLAIN');
93100
else this.send('CAP END');
94101
} else if (sub === 'NAK') {
95102
this.send('CAP END');
@@ -98,14 +105,15 @@ export class IrcClient extends EventTarget {
98105
}
99106
case 'AUTHENTICATE':
100107
if (params[0] === '+') {
101-
const token = btoa(`\0${this.nick}\0${this.opts.password}`);
108+
const token = btoa(`\0${this.nick}\0${this.opts.password || ''}`);
102109
this.send(`AUTHENTICATE ${token}`);
103110
}
104111
break;
112+
case '900': break; // RPL_LOGGEDIN
105113
case '903': this.send('CAP END'); break; // SASL success
106-
case '904': case '905': // SASL failed
107-
this.emit('status', { state: 'error', error: 'Login failed (check username/password)' });
108-
this.send('CAP END');
114+
case '902': case '904': case '905': case '906': // SASL failed/aborted
115+
this.emit('status', { state: 'error', error: 'Login failed check your username and IRC password.' });
116+
this.quit(); // do NOT CAP END; the server requires SASL
109117
break;
110118
case '001': // welcome
111119
this.connected = true;

apps/desktop/extensions/ai-sidebar/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "TronBrowser",
4-
"version": "3.3.2",
4+
"version": "3.3.3",
55
"description": "TronBrowser — privacy-first, AI-native. Branded new tab, private search, CoinPay login, and a bring-your-own-keys AI sidebar.",
66
"icons": {
77
"16": "icons/icon-16.png",

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/desktop",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "Desktop shell for the TronBrowser Chromium fork",
66
"type": "module",

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/docs",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "Documentation site",
66
"type": "module",

apps/extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/extensions",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "TronBrowser extension store — pay $1, list your MV3 extension (tronbrowser.dev/store)",
66
"type": "module",

apps/mobile/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"slug": "tronbrowserdev",
55
"owner": "profullstack",
66
"scheme": "tronbrowser",
7-
"version": "3.3.2",
7+
"version": "3.3.3",
88
"orientation": "portrait",
99
"userInterfaceStyle": "dark",
1010
"platforms": [

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/mobile",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "TronBrowser mobile (Expo / React Native) — Phase 2",
66
"type": "module",

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/web",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "TronBrowser marketing site + web dashboard",
66
"type": "module",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tronbrowser",
3-
"version": "3.3.2",
3+
"version": "3.3.3",
44
"private": true,
55
"description": "TronBrowser.dev — open-source, privacy-first, AI-native browser",
66
"packageManager": "pnpm@9.12.0",

0 commit comments

Comments
 (0)