Support hosting behind a reverse proxy (https/subpath)
BLS.relayUrl() picks the relay endpoint: bundled server.py keeps ws://host:8787; served over https or from a subpath it becomes same-origin wss://host/<base>/ws. Patient/join links are now path-relative so subpath hosting works. server.py gains --bind and --http-port for reverse-proxy setups where 8000 is taken. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dadda8765d
commit
55d1686d8d
@ -146,7 +146,7 @@
|
||||
}
|
||||
function connect(){
|
||||
var ws;
|
||||
try{ ws = new WebSocket('ws://' + location.hostname + ':8787') }catch(_){ return }
|
||||
try{ ws = new WebSocket(BLS.relayUrl()) }catch(_){ return }
|
||||
ws.onopen = function(){
|
||||
net.ws = ws;
|
||||
try{ ws.send(JSON.stringify({type:'join', sessionId: net.sid, role:'admin'})) }catch(_){ }
|
||||
@ -253,7 +253,7 @@
|
||||
window.open('patient.html' + (net.sid ? '#' + net.sid : ''), 'bls-patient');
|
||||
});
|
||||
document.getElementById('copyLinkBig').addEventListener('click', function(){
|
||||
var link = location.origin + '/patient.html#' + net.sid;
|
||||
var link = new URL('patient.html#' + net.sid, location.href).href;
|
||||
var b = this;
|
||||
try{ navigator.clipboard.writeText(link) }catch(_){ try{ prompt('Patient link:', link) }catch(__){ } }
|
||||
b.textContent = '✅ Copied — open it on the patient device';
|
||||
|
||||
@ -439,17 +439,18 @@ if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded
|
||||
else if(wsConnected && ws){ ws.send(JSON.stringify(payload)) }
|
||||
}catch(e){}
|
||||
}
|
||||
function updateJoinLink(){ var sid = (wsSession.value||'').trim(); var base = location.origin + '/patient.html'; if(sid){ joinLink.value = base + '#' + encodeURIComponent(sid) } else { joinLink.value = '' } }
|
||||
function updateJoinLink(){ var sid = (wsSession.value||'').trim(); var base = new URL('patient.html', location.href).href; if(sid){ joinLink.value = base + '#' + encodeURIComponent(sid) } else { joinLink.value = '' } }
|
||||
function renderOsHelp(){ var ua = navigator.userAgent||''; var isWin = /Windows/i.test(ua); var isMac = /Macintosh|Mac OS/i.test(ua); var start = (isWin? 'start-relay.bat' : isMac? 'start-relay.command' : './start-relay.sh'); var open = (isWin? 'double-click' : isMac? 'double-click' : 'run'); return { setup: start, start: start, open: open } }
|
||||
(function(){ var el = document.getElementById('osHelp'); if(!el) return; var h = renderOsHelp(); var text = 'Start the server: ' + h.open + ' ' + h.start + ' — nothing to install, first time or any time'; el.innerText = text })();
|
||||
function copyText(t){ var ok=false; if(navigator.clipboard && navigator.clipboard.writeText){ try{ navigator.clipboard.writeText(t); ok=true }catch(e){} } if(!ok){ var ta=document.createElement('textarea'); ta.value=t; ta.setAttribute('readonly',''); ta.style.position='fixed'; ta.style.bottom='0'; ta.style.left='0'; ta.style.opacity='0.01'; document.body.appendChild(ta); ta.focus(); ta.select(); try{ ok=document.execCommand('copy') }catch(e){} document.body.removeChild(ta) } if(!ok){ try{ alert(t) }catch(_){ } } return ok }
|
||||
function setRelayStatus(on){ if(relayStatusDot){ relayStatusDot.classList.toggle('on', !!on) } if(relayStatusText){ relayStatusText.textContent = on? 'Running' : 'Not running' } }
|
||||
(function(){ var btn = document.getElementById('wsConnect'); if(btn){ btn.disabled = true } })();
|
||||
(function(){ if(wsUrl && !(wsUrl.value||'').trim()){ wsUrl.value = BLS.relayUrl() } })();
|
||||
(function(){
|
||||
var pollMs = 3000;
|
||||
function schedule(){ setTimeout(run, pollMs) }
|
||||
function run(){ checkRelayOnce().then(function(ok){ setRelayStatus(!!ok); var btn = document.getElementById('wsConnect'); if(btn){ btn.disabled = !ok } pollMs = ok ? 3000 : Math.min(Math.round(pollMs*1.5), 30000); schedule() }).catch(function(){ setRelayStatus(false); var btn = document.getElementById('wsConnect'); if(btn){ btn.disabled = true } pollMs = Math.min(Math.round(pollMs*1.5), 30000); schedule() }) }
|
||||
async function checkRelayOnce(){ var host = location.hostname || 'localhost'; var url = 'ws://' + host + ':8787'; try{ return await new Promise(function(resolve){ var temp = new WebSocket(url); var done=false; var timer = setTimeout(function(){ if(!done){ resolve(false); if(temp && temp.readyState===1){ try{ temp.close() }catch(e){} } } }, 1500);
|
||||
async function checkRelayOnce(){ var url = BLS.relayUrl(); try{ return await new Promise(function(resolve){ var temp = new WebSocket(url); var done=false; var timer = setTimeout(function(){ if(!done){ resolve(false); if(temp && temp.readyState===1){ try{ temp.close() }catch(e){} } } }, 1500);
|
||||
temp.onopen = function(){ try{ temp.send(JSON.stringify({type:'ping'})) }catch(e){} };
|
||||
temp.onmessage = function(ev){ try{ var msg = JSON.parse(ev.data); if(msg && msg.type==='pong'){ done=true; clearTimeout(timer); if(temp && temp.readyState===1){ try{ temp.close() }catch(e){} } resolve(true); return } }catch(e){} resolve(false) };
|
||||
temp.onerror = function(){ resolve(false) };
|
||||
@ -458,7 +459,7 @@ if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded
|
||||
run()
|
||||
})();
|
||||
if(startRelayBtn){ startRelayBtn.addEventListener('click', function(){ var h = renderOsHelp(); var ok = copyText(h.start); var old = startRelayBtn.textContent; startRelayBtn.textContent = ok? 'Copied!' : 'Copy Start Command'; setTimeout(function(){ startRelayBtn.textContent = old }, 1200) }) }
|
||||
if(stopRelayBtn){ stopRelayBtn.addEventListener('click', function(){ var host = location.hostname || 'localhost'; var url = 'ws://' + host + ':8787'; try{ var temp = new WebSocket(url); temp.onopen = function(){ try{ temp.send(JSON.stringify({type:'shutdown'})) }catch(e){} }; setTimeout(function(){ setRelayStatus(false) }, 1500) }catch(e){} }) }
|
||||
if(stopRelayBtn){ stopRelayBtn.addEventListener('click', function(){ var url = BLS.relayUrl(); try{ var temp = new WebSocket(url); temp.onopen = function(){ try{ temp.send(JSON.stringify({type:'shutdown'})) }catch(e){} }; setTimeout(function(){ setRelayStatus(false) }, 1500) }catch(e){} }) }
|
||||
function wsConnect(){ var url = (wsUrl.value||'').trim(); var sid = (wsSession.value||'').trim(); if(!sid){ sid = randSid(); wsSession.value = sid; updateJoinLink() } var btn = document.getElementById('wsConnect'); if(btn && btn.disabled){ wsStatus.textContent = 'Relay not running'; return } if(ws){ try{ ws.close() }catch(e){} ws=null } if(!url){ wsStatus.textContent = 'Missing URL'; return } try{ ws = new WebSocket(url) }catch(e){ wsStatus.textContent = 'Error'; return } wsSid = sid; wsStatus.textContent = 'Connecting...'; ws.onopen = function(){ wsConnected = true; wsStatus.textContent = 'Connected'; try{ ws.send(JSON.stringify({type:'join', sessionId:sid, role:'admin'})) }catch(e){} publishState() }; ws.onclose = function(){ wsConnected = false; wsStatus.textContent = 'Disconnected' }; ws.onerror = function(){ wsStatus.textContent = 'Error' }; ws.onmessage = function(ev){ try{ var msg = JSON.parse(ev.data); if(msg && msg.type==='joined'){ wsStatus.textContent = 'Connected' } else if(msg && msg.type==='patientViewport'){ var pv = msg.payload||{}; try{ window.PatientViewport = pv }catch(_){ } var r = window.AdminRenderer; if(r){ r.resize(parseInt(pv.canvasW||640,10), parseInt(pv.canvasH||360,10)) } var pvShort = document.getElementById('patientViewportShort'); if(pvShort){ pvShort.textContent = (pv.canvasW||'?')+'×'+(pv.canvasH||'?') } } }catch(e){} } }
|
||||
function wsDisconnect(){ if(ws){ try{ ws.close() }catch(e){} } ws=null; wsConnected=false; wsStatus.textContent = 'Disconnected' }
|
||||
function refreshNetMode(){ var m = (netModeSel && netModeSel.value) || 'relay'; if(rtcControls) rtcControls.style.display = (m==='webrtc'? 'flex':'none'); var relayRows = document.querySelectorAll('#osHelp, #wsUrl, #wsSession, #detectIP, #wsConnect, #wsDisconnect'); for(var i=0;i<relayRows.length;i++){ var el=relayRows[i]; if(!el) continue }
|
||||
|
||||
10
js/bls.js
10
js/bls.js
@ -310,6 +310,16 @@
|
||||
// --- Export ---
|
||||
window.bls = {
|
||||
createChannel: function(name){ return new Channel(name); },
|
||||
// where's the relay? bundled server.py = ws://host:8787; hosted behind a
|
||||
// reverse proxy (https and/or subpath) = same-origin <base>/ws
|
||||
relayUrl: function(){
|
||||
if(!/^https?:$/.test(location.protocol)) return 'ws://localhost:8787';
|
||||
var base = location.pathname.replace(/[^/]*$/, '');
|
||||
if(location.protocol === 'https:' || base !== '/'){
|
||||
return (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + base + 'ws';
|
||||
}
|
||||
return 'ws://' + (location.hostname || 'localhost') + ':8787';
|
||||
},
|
||||
defaultState: defaultState,
|
||||
loadState: loadState,
|
||||
saveState: saveState,
|
||||
|
||||
@ -160,7 +160,7 @@
|
||||
var qs = new URLSearchParams(location.search);
|
||||
var hashSid = (location.hash || '').replace('#', '');
|
||||
var wsSid = qs.get('sid') || hashSid || 'default';
|
||||
var wsUrl = qs.get('url') || ('ws://' + (location.hostname || 'localhost') + ':8787');
|
||||
var wsUrl = qs.get('url') || BLS.relayUrl();
|
||||
var rtcMode = !!qs.get('p2p');
|
||||
|
||||
if(!rtcMode && wsUrl){
|
||||
|
||||
17
server.py
17
server.py
@ -213,27 +213,34 @@ class _QuietHTTP(SimpleHTTPRequestHandler):
|
||||
pass
|
||||
|
||||
|
||||
def _argval(flag, default):
|
||||
args = sys.argv[1:]
|
||||
return args[args.index(flag) + 1] if flag in args else default
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
bind = _argval('--bind', '0.0.0.0') # e.g. a tailnet IP behind a reverse proxy
|
||||
http_port = int(_argval('--http-port', HTTP_PORT))
|
||||
|
||||
socketserver.ThreadingTCPServer.allow_reuse_address = True
|
||||
ThreadingHTTPServer.allow_reuse_address = True
|
||||
httpd = ThreadingHTTPServer(('0.0.0.0', HTTP_PORT), _QuietHTTP)
|
||||
wsd = socketserver.ThreadingTCPServer(('0.0.0.0', WS_PORT), WSHandler)
|
||||
httpd = ThreadingHTTPServer((bind, http_port), _QuietHTTP)
|
||||
wsd = socketserver.ThreadingTCPServer((bind, WS_PORT), WSHandler)
|
||||
httpd.daemon_threads = True
|
||||
wsd.daemon_threads = True
|
||||
_SERVERS.extend([httpd, wsd])
|
||||
|
||||
ip = local_ip()
|
||||
ip = bind if bind not in ('0.0.0.0', '') else local_ip()
|
||||
print('backnforth running:')
|
||||
print(' Therapist page: http://%s:%d/' % (ip, HTTP_PORT))
|
||||
print(' Therapist page: http://%s:%d/' % (ip, http_port))
|
||||
print(' Session relay: ws://%s:%d' % (ip, WS_PORT))
|
||||
print('Press Ctrl+C to stop.')
|
||||
|
||||
threading.Thread(target=wsd.serve_forever, daemon=True).start()
|
||||
if '--no-browser' not in sys.argv:
|
||||
try:
|
||||
webbrowser.open('http://%s:%d/easy.html' % (ip, HTTP_PORT))
|
||||
webbrowser.open('http://%s:%d/easy.html' % (ip, http_port))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user