Skip to content

Commit d64af95

Browse files
committed
attempt to make end webhooks replies to the start webhooks
1 parent b6a3540 commit d64af95

5 files changed

Lines changed: 20 additions & 6 deletions

File tree

web/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class BaseSession extends Container<Env> {
5353
// In-memory only: on DO hibernation we lose geo/ISP fields in the end
5454
// embed, which is cosmetic — never a correctness concern.
5555
protected obsContext?: RequestContext;
56+
private startMessageId?: string;
5657

5758
private async fetchCapacitySnapshot(): Promise<CapacitySnapshot | undefined> {
5859
try {
@@ -107,7 +108,7 @@ export class BaseSession extends Container<Env> {
107108
this.obsContext = parseContext(request.headers.get(OBS_CONTEXT_HEADER));
108109
void (async () => {
109110
const capacity = await this.fetchCapacitySnapshot();
110-
await notify.sessionStarted(this.env, {
111+
this.startMessageId = await notify.sessionStarted(this.env, {
111112
sessionId,
112113
branch: this.branch,
113114
context: this.obsContext ?? { ip: "unknown" },
@@ -414,6 +415,7 @@ export class BaseSession extends Container<Env> {
414415
extensionGranted: this.extensionGranted,
415416
context: this.obsContext,
416417
capacity,
418+
replyTo: this.startMessageId,
417419
});
418420
})();
419421
}

web/src/observability/embeds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface DiscordEmbed {
2727

2828
export interface DiscordWebhookPayload {
2929
embeds: DiscordEmbed[];
30+
message_reference?: { message_id: string };
3031
}
3132

3233
const COLORS = {

web/src/observability/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export const notify = {
2323
sessionStarted: (env: Env, event: SessionStartedEvent) =>
2424
post(env, { embeds: [buildSessionStartedEmbed(event)] }),
2525
sessionEnded: (env: Env, event: SessionEndedEvent) =>
26-
post(env, { embeds: [buildSessionEndedEmbed(event)] }),
26+
post(env, {
27+
embeds: [buildSessionEndedEmbed(event)],
28+
...(event.replyTo ? { message_reference: { message_id: event.replyTo } } : {}),
29+
}),
2730
queueEntered: (env: Env, event: QueueEnteredEvent) =>
2831
post(env, { embeds: [buildQueueEnteredEmbed(event)] }),
2932
error: (env: Env, event: ErrorEvent) =>

web/src/observability/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface SessionEndedEvent {
4646
extensionGranted: boolean;
4747
context?: RequestContext;
4848
capacity?: CapacitySnapshot;
49+
replyTo?: string;
4950
}
5051

5152
export interface QueueEnteredEvent {

web/src/observability/webhook.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@ import type { DiscordWebhookPayload } from "./embeds";
22

33
const WEBHOOK_TIMEOUT_MS = 3000;
44

5-
export async function post(env: { DISCORD_WEBHOOK_URL?: string }, payload: DiscordWebhookPayload): Promise<void> {
5+
// Fire-and-forget. No-op when the secret is unset. Never throws —
6+
// observability must not be able to break the request path.
7+
// Returns the Discord message ID when available (for threading replies).
8+
export async function post(env: { DISCORD_WEBHOOK_URL?: string }, payload: DiscordWebhookPayload): Promise<string | undefined> {
69
const url = env.DISCORD_WEBHOOK_URL;
7-
if (!url) return;
10+
if (!url) return undefined;
811

912
let body: string;
1013
try {
1114
body = JSON.stringify(payload);
1215
} catch (e) {
1316
console.error("[obs] webhook payload JSON.stringify failed:", e);
14-
return;
17+
return undefined;
1518
}
1619

1720
const controller = new AbortController();
1821
const timeout = setTimeout(() => controller.abort(), WEBHOOK_TIMEOUT_MS);
1922

2023
try {
21-
const res = await fetch(url, {
24+
const res = await fetch(`${url}?wait=true`, {
2225
method: "POST",
2326
headers: { "Content-Type": "application/json" },
2427
body,
@@ -29,7 +32,10 @@ export async function post(env: { DISCORD_WEBHOOK_URL?: string }, payload: Disco
2932
console.error(
3033
`[obs] webhook non-2xx: status=${res.status} statusText=${res.statusText} body=${resBody.slice(0, 300)}`,
3134
);
35+
return undefined;
3236
}
37+
const data = await res.json<{ id: string }>();
38+
return data.id;
3339
} catch (e) {
3440
if (e instanceof Error && e.name === "AbortError") {
3541
console.error(`[obs] webhook timed out after ${WEBHOOK_TIMEOUT_MS}ms`);
@@ -38,6 +44,7 @@ export async function post(env: { DISCORD_WEBHOOK_URL?: string }, payload: Disco
3844
const msg = e instanceof Error ? e.message : String(e);
3945
console.error(`[obs] webhook post failed: ${name}: ${msg}`);
4046
}
47+
return undefined;
4148
} finally {
4249
clearTimeout(timeout);
4350
}

0 commit comments

Comments
 (0)