← back to Qwen38 Viewer
debug+refactor: fix 3D fallback setState + WebGL try/catch + render throttle; auth no-colon guard + upstream cancel + listener cleanup; mail-bridge fd-leak, dead-line, SMTP reuse+try/except, plus-address guard
bc74a9050d598c47857ff1e8e1245280cd99631c · 2026-08-19 11:30:06 -0700 · steve
Files touched
A __pycache__/mail-bridge.cpython-314.pycM mail-bridge.pyM public/index.htmlM server.js
Diff
commit bc74a9050d598c47857ff1e8e1245280cd99631c
Author: steve <steve@designerwallcoverings.com>
Date: Wed Aug 19 11:30:06 2026 -0700
debug+refactor: fix 3D fallback setState + WebGL try/catch + render throttle; auth no-colon guard + upstream cancel + listener cleanup; mail-bridge fd-leak, dead-line, SMTP reuse+try/except, plus-address guard
---
__pycache__/mail-bridge.cpython-314.pyc | Bin 0 -> 14140 bytes
mail-bridge.py | 35 ++++++++++++++++++++++----------
public/index.html | 16 ++++++++++-----
server.js | 13 ++++++++----
4 files changed, 44 insertions(+), 20 deletions(-)
diff --git a/__pycache__/mail-bridge.cpython-314.pyc b/__pycache__/mail-bridge.cpython-314.pyc
new file mode 100644
index 0000000..e1348ba
Binary files /dev/null and b/__pycache__/mail-bridge.cpython-314.pyc differ
diff --git a/mail-bridge.py b/mail-bridge.py
index 11790df..4562d8f 100644
--- a/mail-bridge.py
+++ b/mail-bridge.py
@@ -43,7 +43,8 @@ LOG = pathlib.Path.home() / ".qwen-mail-bridge.log"
def log(m):
line = f"{datetime.datetime.now().isoformat(timespec='seconds')} {m}"
print(line)
- try: LOG.open("a").write(line + "\n")
+ try:
+ with LOG.open("a") as f: f.write(line + "\n")
except OSError: pass
def get_pw():
@@ -89,7 +90,6 @@ def plain_body(msg):
if ct == "text/plain" and not text: text = _decode(part)
elif ct == "text/html" and not html: html = _decode(part)
else:
- (text if msg.get_content_type() == "text/plain" else html) and None
if msg.get_content_type() == "text/html": html = _decode(msg)
else: text = _decode(msg)
return text.strip() or _strip_html(html)
@@ -127,16 +127,18 @@ def main():
ids = data[0].split() if data and data[0] else []
log(f"poll: {len(ids)} unseen")
+ smtp = None
for num in ids:
if replied >= MAX_PER_RUN:
log("rate: MAX_PER_RUN hit, stopping"); break
typ, md = M.fetch(num, "(RFC822)")
msg = email.message_from_bytes(md[0][1])
frm = parseaddr(msg.get("From", ""))[1].lower()
+ base = re.sub(r"\+[^@]*@", "@", frm) # strip +tag so plus-addresses can't dodge the self-guard
subj = msg.get("Subject", "(no subject)")
# --- safety gates ---
- if frm == MAILBOX or "mailer-daemon" in frm or frm.startswith("no-reply") or frm.startswith("noreply"):
+ if base == MAILBOX or "mailer-daemon" in frm or frm.startswith(("no-reply", "noreply")):
log(f"skip loop-guard from={frm}"); M.store(num, "+FLAGS", "\\Seen"); continue
if is_bulk(msg):
log(f"skip bulk/auto from={frm}"); M.store(num, "+FLAGS", "\\Seen"); continue
@@ -167,14 +169,25 @@ def main():
reply["Auto-Submitted"] = "auto-replied" # be a good citizen; prevents remote loops
reply.set_content(answer + "\n\n— qwen3.8-27b-heretic (local, uncensored) via agentabrams.com")
- s = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=60)
- s.starttls(context=ctx); s.login(MAILBOX, pw); s.send_message(reply); s.quit()
-
- M.store(num, "+FLAGS", "\\Seen")
- counts[frm] = counts.get(frm, 0) + 1
- replied += 1
- log(f"replied to {frm} ({len(answer)} chars)")
-
+ try:
+ if smtp is None: # connect once, reuse across replies
+ smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=60)
+ smtp.starttls(context=ctx); smtp.login(MAILBOX, pw)
+ smtp.send_message(reply)
+ M.store(num, "+FLAGS", "\\Seen")
+ counts[frm] = counts.get(frm, 0) + 1
+ replied += 1
+ log(f"replied to {frm} ({len(answer)} chars)")
+ except Exception as e:
+ log(f"smtp error to {frm}: {e}")
+ M.store(num, "+FLAGS", "\\Seen") # mark seen -> no retry storm / dup replies
+ try: smtp.quit()
+ except Exception: pass
+ smtp = None # force fresh connection next time
+
+ if smtp is not None:
+ try: smtp.quit()
+ except Exception: pass
M.logout()
state = {today: counts} # keep only today's counts
save_state(state)
diff --git a/public/index.html b/public/index.html
index f9fe7dc..6b07040 100644
--- a/public/index.html
+++ b/public/index.html
@@ -158,7 +158,8 @@ function thinkingIndicator(){
// ---- three.js thinking scene: a pulsing wireframe icosahedron core +
// orbiting particle swarm. Renders while the model is thinking. ------------
function makeThink3D(mount){
- if(!window.THREE){ return {stop(){}, energy(){}}; } // graceful fallback
+ const NOOP = {stop(){}, energy(){}, setState(){}};
+ if(!window.THREE || !mount){ return NOOP; } // graceful fallback (same shape)
const W = mount.clientWidth || 700, H = mount.clientHeight || 180;
const scene = new THREE.Scene();
const cam = new THREE.PerspectiveCamera(50, W/H, 0.1, 100); cam.position.z = 5;
@@ -254,8 +255,10 @@ async function send(){
scroll.scrollTop = scroll.scrollHeight;
const stream = content.querySelector('.stream');
let acc='';
- let t3d = makeThink3D(content.querySelector('.t3d')); // <-- three.js scene
- let lastTok = performance.now();
+ let t3d;
+ try { t3d = makeThink3D(content.querySelector('.t3d')); } // three.js scene
+ catch(e){ console.warn('3D init failed:', e); t3d = {stop(){}, energy(){}, setState(){}}; }
+ let lastTok = performance.now(), lastRender = 0;
const bodyOf = s => { // visible answer (outside <think>)
const o=s.indexOf('<think>'), c=s.indexOf('</think>');
@@ -311,8 +314,11 @@ async function send(){
else if(j.message && j.message.content){ acc += j.message.content; }
lastTok = performance.now();
if(t3d){ t3d.energy(); t3d.setState(bodyOf(acc).trim()!==''?'running':'thinking'); }
- stream.innerHTML = render(acc, true); // stream text below the live 3D panel
- scroll.scrollTop = scroll.scrollHeight;
+ if(lastTok - lastRender > 60){ // throttle: avoid O(n) reparse every token
+ lastRender = lastTok;
+ stream.innerHTML = render(acc, true); // stream text below the live 3D panel
+ scroll.scrollTop = scroll.scrollHeight;
+ }
}
}
killScene();
diff --git a/server.js b/server.js
index e6fa079..70c297b 100644
--- a/server.js
+++ b/server.js
@@ -35,9 +35,11 @@ app.use((req, res, next) => {
if (scheme === 'Basic' && b64) {
const s = Buffer.from(b64, 'base64').toString();
const i = s.indexOf(':');
- const u = s.slice(0, i), p = s.slice(i + 1);
- if (USERS[u] !== undefined && USERS[u] === p) return next(); // exact user:pass
- if (CI_CODES.has(p.toLowerCase())) return next(); // case-insensitive shared code
+ if (i > 0) { // require a colon + non-empty user
+ const u = s.slice(0, i), p = s.slice(i + 1);
+ if (USERS[u] === p) return next(); // exact user:pass
+ if (CI_CODES.has(p.toLowerCase())) return next(); // case-insensitive shared code
+ }
}
res.set('WWW-Authenticate', 'Basic realm="qwen38"');
return res.status(401).send('Auth required');
@@ -80,18 +82,21 @@ app.post('/api/chat', async (req, res) => {
keep_alive: KEEP_ALIVE === '-1' ? -1 : KEEP_ALIVE }), // -1 (number) = keep forever
});
if (!upstream.ok || !upstream.body) {
+ if (upstream.body) upstream.body.cancel().catch(() => {}); // don't leak the TCP conn
res.write(JSON.stringify({ error: `ollama ${upstream.status}` }) + '\n');
return res.end();
}
// Abort upstream if the client disconnects
const reader = upstream.body.getReader();
- req.on('close', () => reader.cancel().catch(() => {}));
+ const onClose = () => reader.cancel().catch(() => {});
+ req.once('close', onClose);
const dec = new TextDecoder();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
res.write(dec.decode(value, { stream: true }));
}
+ req.off('close', onClose);
res.end();
} catch (e) {
res.write(JSON.stringify({ error: String(e) }) + '\n');
← 3718af9 self-host three.js (local /vendor/three.min.js) with CDN fal
·
back to Qwen38 Viewer
·
add npm test: 18 regression checks (auth matrix incl no-colo 114f772 →