feat(ui): authenticated POST->SSE reader
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
33
public/sse.js
Normal file
33
public/sse.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Authenticated POST -> SSE reader. Calls onEvent({ type, ...data }) per frame.
|
||||||
|
const TOKEN_KEY = 'void_token';
|
||||||
|
|
||||||
|
export async function streamTurn(path, body, onEvent) {
|
||||||
|
const res = await fetch(path, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + (localStorage.getItem(TOKEN_KEY) || ''),
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if (!res.ok || !res.body) throw new Error('stream failed: ' + res.status);
|
||||||
|
|
||||||
|
const reader = res.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buf = '';
|
||||||
|
for (;;) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
buf += decoder.decode(value, { stream: true });
|
||||||
|
const frames = buf.split('\n\n');
|
||||||
|
buf = frames.pop(); // keep the trailing partial
|
||||||
|
for (const frame of frames) {
|
||||||
|
let event = 'message', data = '';
|
||||||
|
for (const line of frame.split('\n')) {
|
||||||
|
if (line.startsWith('event:')) event = line.slice(6).trim();
|
||||||
|
else if (line.startsWith('data:')) data += line.slice(5).trim();
|
||||||
|
}
|
||||||
|
if (data) onEvent({ type: event, ...JSON.parse(data) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user