voice: fail loud on insecure-context join (mic blocked over HTTP) instead of silent reject — TDD
getUserMedia is only exposed in a secure context (HTTPS, or the localhost exemption). Reached over plain HTTP on a LAN IP — the dev server from a phone at http://192.168.x.x:8000 — iOS/Android leave navigator.mediaDevices undefined, so join() fetched TURN creds (a confusing 200) then silently rejected deep in a .then() with no mic prompt. Desktop works because 127.0.0.1 IS a secure context. (Not a regression — voice never worked on the HTTP dev server from mobile.) - voice-mesh.js: _micSupported() seam + an early INSECURE_CONTEXT reject in join() before any network, so the failure is fast + diagnosable. - burger-btn.js: bindVoiceBtn catches the rejected join, rolls back the optimistic .in-call/dataset.inCall (so the next click retries), and surfaces a Brief — 'Voice needs HTTPS (or localhost) — your browser blocked the mic here.' — instead of failing invisibly. - VoiceMeshSpec: +2 specs (join rejects INSECURE_CONTEXT; btn rolls back + Briefs on reject). Jasmine green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,26 @@
|
||||
// INACTIVE click is left to the delegated fan handler's 2-pulse flash.
|
||||
// No stopPropagation on active — the delegated handler then closes the
|
||||
// fan (its existing .active behaviour).
|
||||
// Surface a join failure to the user instead of failing silently — most
|
||||
// often the secure-context block (INSECURE_CONTEXT) when the dev server is
|
||||
// reached over plain HTTP from a phone. Prefers the Brief banner; falls
|
||||
// back to console.
|
||||
function _voiceJoinFailed(vbtn, e) {
|
||||
vbtn.classList.remove('in-call');
|
||||
delete vbtn.dataset.inCall; // let the next click retry the join
|
||||
var msg = (e && e.code === 'INSECURE_CONTEXT')
|
||||
? 'Voice needs HTTPS (or localhost) — your browser blocked the mic here.'
|
||||
: 'Couldn’t start voice — mic unavailable or permission denied.';
|
||||
if (window.Brief && typeof window.Brief.showBanner === 'function') {
|
||||
window.Brief.showBanner({
|
||||
title: 'Voice', line_text: msg, kind: 'NUDGE',
|
||||
post_url: '', created_at: '',
|
||||
});
|
||||
} else if (window.console && console.warn) {
|
||||
console.warn('[voice] ' + msg, e || '');
|
||||
}
|
||||
}
|
||||
|
||||
function bindVoiceBtn() {
|
||||
var vbtn = document.getElementById('id_voice_btn');
|
||||
if (!vbtn) return;
|
||||
@@ -132,7 +152,10 @@
|
||||
if (!vbtn.dataset.inCall) {
|
||||
vbtn.dataset.inCall = '1';
|
||||
vbtn.classList.add('in-call');
|
||||
window.VoiceRoom.join(roomId);
|
||||
var p = window.VoiceRoom.join(roomId);
|
||||
if (p && typeof p.catch === 'function') {
|
||||
p.catch(function (e) { _voiceJoinFailed(vbtn, e); });
|
||||
}
|
||||
} else {
|
||||
var muted = window.VoiceRoom.toggleMute();
|
||||
vbtn.classList.toggle('muted', muted);
|
||||
|
||||
Reference in New Issue
Block a user