Handle connecting directly to the event stream URL

This commit is contained in:
Samuel Elliott 2022-11-16 12:13:48 +00:00
parent a52c45af36
commit 20bab43981
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
5 changed files with 30 additions and 2 deletions

View File

@ -213,7 +213,7 @@ export type PresenceUrlResponse =
Friend | {friend: Friend};
export async function getPresenceFromUrl(presence_url: string, useragent?: string) {
const [signal, cancel] = timeoutSignal();
const [signal, cancel, controller] = timeoutSignal();
const response = await fetch(presence_url, {
headers: {
'User-Agent': getUserAgent(useragent),
@ -227,6 +227,11 @@ export async function getPresenceFromUrl(presence_url: string, useragent?: strin
throw new ErrorResponse('[zncproxy] Unknown error', response, await response.text());
}
if (!response.headers.get('Content-Type')?.match(/^application\/json(;|$)$/)) {
controller.abort();
throw new ErrorResponse('[zncproxy] Unacceptable content type', response);
}
const data = await response.json() as PresenceUrlResponse;
const user: CurrentUser | Friend | undefined =

View File

@ -515,6 +515,7 @@ class Server extends HttpServer {
while (!req.socket.closed) {
try {
debug('Updating data for event stream %d', stream.id);
const result = await this.handlePresenceRequest(req, null, presence_user_nsaid, true);
stream.sendEvent('update', 'debug: timestamp ' + new Date().toISOString());
@ -553,6 +554,8 @@ class Server extends HttpServer {
});
}
debug('Error in event stream %d', stream.id, err);
res.end();
break;
}

View File

@ -91,12 +91,25 @@ export class ResponseError extends Error {
export class EventStreamResponse {
json_replacer: ((key: string, value: unknown) => any) | null = null;
private static id = 0;
readonly id = EventStreamResponse.id++;
constructor(
readonly req: Request,
readonly res: Response,
) {
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Content-Type', 'text/event-stream');
console.log('[%s] Event stream %d connected to %s from %s, port %d%s, %s',
new Date(), this.id, req.url,
req.socket.remoteAddress, req.socket.remotePort,
req.headers['x-forwarded-for'] ? ' (' + req.headers['x-forwarded-for'] + ')' : '',
req.headers['user-agent']);
res.on('close', () => {
console.log('[%s] Event stream %d closed', new Date(), this.id);
});
}
sendEvent(event: string | null, ...data: unknown[]) {

View File

@ -577,6 +577,12 @@ export class ZncProxyDiscordPresence extends Loop {
}
} catch (err) {
if (err instanceof ErrorResponse) {
if (err.response.headers.get('Content-Type')?.match(/^text\/event-stream(;|$)/)) {
this.is_sse = true;
debug('Presence URL responded with an event stream');
return LoopResult.OK_SKIP_INTERVAL;
}
const retry_after = err.response.headers.get('Retry-After');
if (!retry_after || !/^\d+$/.test(retry_after)) throw err;
@ -635,6 +641,7 @@ export class ZncProxyDiscordPresence extends Loop {
let user: CurrentUser | Friend | undefined = undefined;
let presence: Presence | null = null;
this.last_data = {};
events.onmessage = event => {
if (event.type === 'message') {

View File

@ -34,5 +34,5 @@ export function timeoutSignal(ms = 60 * 1000) {
controller.abort(new Error('Timeout'));
}, ms);
return [controller.signal, () => clearTimeout(timeout)] as const;
return [controller.signal, () => clearTimeout(timeout), controller] as const;
}