mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-31 06:40:50 +01:00
refactor: use base http client for notifications, parser, autocomp
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import type { Notification } from '../types.ts';
|
||||
import type { Notifier } from './Notifier.ts';
|
||||
import { getWebhookClient } from './webhookClient.ts';
|
||||
|
||||
/**
|
||||
* Base class for HTTP-based notification services (webhooks)
|
||||
@@ -9,7 +10,6 @@ import type { Notifier } from './Notifier.ts';
|
||||
export abstract class BaseHttpNotifier implements Notifier {
|
||||
private lastSentAt: Date | null = null;
|
||||
private readonly minInterval: number = 1000; // 1 second between notifications
|
||||
private readonly timeout: number = 10000; // 10 second timeout
|
||||
|
||||
/**
|
||||
* Get the webhook URL for this service
|
||||
@@ -47,43 +47,14 @@ export abstract class BaseHttpNotifier implements Notifier {
|
||||
const payload = this.formatPayload(notification);
|
||||
const url = this.getWebhookUrl();
|
||||
|
||||
// Create abort controller for timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
await getWebhookClient().sendWebhook(url, payload);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal
|
||||
});
|
||||
await logger.debug(`Notification sent`, {
|
||||
source: this.getName(),
|
||||
meta: { type: notification.type }
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'Unknown error');
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
await logger.debug(`Notification sent`, {
|
||||
source: this.getName(),
|
||||
meta: { type: notification.type }
|
||||
});
|
||||
|
||||
this.lastSentAt = new Date();
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// Handle timeout
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error('Request timeout');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
this.lastSentAt = new Date();
|
||||
} catch (error) {
|
||||
// Log error but don't throw (fire-and-forget)
|
||||
await logger.error(`Failed to send notification`, {
|
||||
|
||||
42
src/lib/server/notifications/base/webhookClient.ts
Normal file
42
src/lib/server/notifications/base/webhookClient.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Shared HTTP client for webhook-based notifications
|
||||
* Uses BaseHttpClient for connection pooling
|
||||
*/
|
||||
|
||||
import { BaseHttpClient } from '../../utils/http/client.ts';
|
||||
|
||||
/**
|
||||
* Webhook HTTP client
|
||||
* Extends BaseHttpClient with webhook-specific settings:
|
||||
* - No retries (webhooks should either work or not)
|
||||
* - 10 second timeout
|
||||
*/
|
||||
class WebhookClient extends BaseHttpClient {
|
||||
constructor() {
|
||||
// Empty base URL - we pass full webhook URLs as paths
|
||||
super('', {
|
||||
timeout: 10000,
|
||||
retries: 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST to a webhook URL
|
||||
*/
|
||||
sendWebhook<T = void>(url: string, payload: unknown): Promise<T> {
|
||||
return this.post<T>(url, payload);
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance - lazy initialized
|
||||
let webhookClient: WebhookClient | null = null;
|
||||
|
||||
/**
|
||||
* Get the shared webhook client
|
||||
*/
|
||||
export function getWebhookClient(): WebhookClient {
|
||||
if (!webhookClient) {
|
||||
webhookClient = new WebhookClient();
|
||||
}
|
||||
return webhookClient;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import type { DiscordConfig, Notification } from '../../types.ts';
|
||||
import { Colors, type DiscordEmbed } from './embed.ts';
|
||||
import { getWebhookClient } from '../../base/webhookClient.ts';
|
||||
|
||||
const RATE_LIMIT_DELAY = 1000; // 1 second between messages
|
||||
|
||||
@@ -104,30 +105,10 @@ export class DiscordNotifier {
|
||||
*/
|
||||
private async sendWebhook(payload: unknown): Promise<void> {
|
||||
const payloadObj = payload as { embeds?: unknown[] };
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.webhook_url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'Unknown error');
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
await getWebhookClient().sendWebhook(this.config.webhook_url, payload);
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error('Request timeout');
|
||||
}
|
||||
|
||||
const embedCharCounts = payloadObj.embeds?.map((e, i) => `${i}:${getEmbedCharCount(e as DiscordEmbed)}`).join(', ') || 'none';
|
||||
await logger.error('Failed to send notification', {
|
||||
source: this.getName(),
|
||||
|
||||
Reference in New Issue
Block a user