fix: enable same-origin voice capture (Closes #933)
This commit is contained in:
parent
785fb990b4
commit
bd637d0141
|
|
@ -62,7 +62,15 @@ function createVoiceIssueCapture({
|
||||||
elements.start.hidden = false;
|
elements.start.hidden = false;
|
||||||
elements.stop.hidden = true;
|
elements.stop.hidden = true;
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
recognition.start();
|
recognition.start();
|
||||||
|
} catch (_) {
|
||||||
|
recognition = null;
|
||||||
|
elements.start.hidden = false;
|
||||||
|
elements.stop.hidden = true;
|
||||||
|
elements.status.textContent = 'Voice capture could not start. Your draft and transcript are unchanged.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
elements.start.hidden = true;
|
elements.start.hidden = true;
|
||||||
elements.stop.hidden = false;
|
elements.stop.hidden = false;
|
||||||
elements.status.textContent = 'Listening… Tap stop when you are finished.';
|
elements.status.textContent = 'Listening… Tap stop when you are finished.';
|
||||||
|
|
|
||||||
|
|
@ -1364,7 +1364,7 @@ async def enforce_browser_security_boundary(request: Request, call_next):
|
||||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
response.headers["Referrer-Policy"] = "no-referrer"
|
response.headers["Referrer-Policy"] = "no-referrer"
|
||||||
response.headers["Permissions-Policy"] = (
|
response.headers["Permissions-Policy"] = (
|
||||||
"camera=(), microphone=(), geolocation=(), payment=(), usb=()"
|
"camera=(), microphone=(self), geolocation=(), payment=(), usb=()"
|
||||||
)
|
)
|
||||||
response.headers["X-Frame-Options"] = "DENY"
|
response.headers["X-Frame-Options"] = "DENY"
|
||||||
if dashboard_auth.mode() == dashboard_auth.OPERATOR_MODE:
|
if dashboard_auth.mode() == dashboard_auth.OPERATOR_MODE:
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ REQUIRED_HEADERS = {
|
||||||
def assert_browser_security_boundary(response: httpx.Response) -> None:
|
def assert_browser_security_boundary(response: httpx.Response) -> None:
|
||||||
for name, value in REQUIRED_HEADERS.items():
|
for name, value in REQUIRED_HEADERS.items():
|
||||||
assert response.headers[name] == value
|
assert response.headers[name] == value
|
||||||
assert "camera=()" in response.headers["permissions-policy"]
|
permissions_policy = response.headers["permissions-policy"]
|
||||||
|
assert "camera=()" in permissions_policy
|
||||||
|
assert "microphone=(self)" in permissions_policy
|
||||||
|
assert "microphone=()" not in permissions_policy
|
||||||
policy = response.headers["content-security-policy"]
|
policy = response.headers["content-security-policy"]
|
||||||
assert "default-src 'self'" in policy
|
assert "default-src 'self'" in policy
|
||||||
assert "script-src 'self'" in policy
|
assert "script-src 'self'" in policy
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,43 @@ process.stdout.write(JSON.stringify({{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_recognition_start_rejection_restores_controls_and_preserves_draft():
|
||||||
|
script = f"""
|
||||||
|
const {{createVoiceIssueCapture}} = require({json.dumps(str(VOICE_CAPTURE))});
|
||||||
|
class Element {{
|
||||||
|
constructor() {{ this.hidden=false; this.value=''; this.textContent=''; this.listeners={{}}; }}
|
||||||
|
addEventListener(name, callback) {{ this.listeners[name]=callback; }}
|
||||||
|
click() {{ this.listeners.click?.({{currentTarget:this}}); }} dispatchEvent() {{}}
|
||||||
|
}}
|
||||||
|
class Recognition {{
|
||||||
|
start() {{ throw new Error('microphone blocked by browser policy'); }}
|
||||||
|
stop() {{}} abort() {{}}
|
||||||
|
}}
|
||||||
|
const elements=Object.fromEntries(
|
||||||
|
['root','start','stop','review','transcript','append','replace','status','title','body'].map(key=>[key,new Element()])
|
||||||
|
);
|
||||||
|
elements.title.value='Existing title';
|
||||||
|
elements.body.value='Existing note';
|
||||||
|
elements.transcript.value='Recovered transcript.';
|
||||||
|
elements.start.hidden=false; elements.stop.hidden=true;
|
||||||
|
createVoiceIssueCapture({{Recognition,elements}});
|
||||||
|
elements.start.click();
|
||||||
|
process.stdout.write(JSON.stringify({{
|
||||||
|
startHidden:elements.start.hidden, stopHidden:elements.stop.hidden,
|
||||||
|
status:elements.status.textContent, title:elements.title.value,
|
||||||
|
body:elements.body.value, transcript:elements.transcript.value
|
||||||
|
}}));
|
||||||
|
"""
|
||||||
|
assert run_node(script) == {
|
||||||
|
"startHidden": False,
|
||||||
|
"stopHidden": True,
|
||||||
|
"status": "Voice capture could not start. Your draft and transcript are unchanged.",
|
||||||
|
"title": "Existing title",
|
||||||
|
"body": "Existing note",
|
||||||
|
"transcript": "Recovered transcript.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_stop_error_and_sheet_close_release_recognition_without_losing_transcript():
|
def test_stop_error_and_sheet_close_release_recognition_without_losing_transcript():
|
||||||
script = f"""
|
script = f"""
|
||||||
const {{createVoiceIssueCapture}} = require({json.dumps(str(VOICE_CAPTURE))});
|
const {{createVoiceIssueCapture}} = require({json.dumps(str(VOICE_CAPTURE))});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user