From 20bab43981f6b8e75a80ca94a03369b7dfbb7322 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Wed, 16 Nov 2022 12:13:48 +0000 Subject: [PATCH] Handle connecting directly to the event stream URL --- src/api/znc-proxy.ts | 7 ++++++- src/cli/presence-server.ts | 3 +++ src/cli/util/http-server.ts | 13 +++++++++++++ src/common/presence.ts | 7 +++++++ src/util/misc.ts | 2 +- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index 23c0b6b..1eb1fdb 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -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 = diff --git a/src/cli/presence-server.ts b/src/cli/presence-server.ts index 182ca11..156b4ca 100644 --- a/src/cli/presence-server.ts +++ b/src/cli/presence-server.ts @@ -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; } diff --git a/src/cli/util/http-server.ts b/src/cli/util/http-server.ts index 237face..42213a1 100644 --- a/src/cli/util/http-server.ts +++ b/src/cli/util/http-server.ts @@ -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[]) { diff --git a/src/common/presence.ts b/src/common/presence.ts index 686dc03..2a852e6 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -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') { diff --git a/src/util/misc.ts b/src/util/misc.ts index a65097a..ee3f79d 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -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; }