Skip to content

Commit

Permalink
feat: better notification logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jahabeebs committed Nov 4, 2024
1 parent 139da81 commit 27c1f7d
Show file tree
Hide file tree
Showing 8 changed files with 443 additions and 213 deletions.
3 changes: 2 additions & 1 deletion apps/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ const main = async (): Promise<void> => {
logger.debug("Protocol provider initialized.");

logger.debug("Initializing notifier...");
logger.debug(`Discord webhook ${config.DISCORD_WEBHOOK}`);

const notifier = new DiscordNotifier(config.DISCORD_WEBHOOK);

logger.debug("Notifier initialized...");

const actorsManager = new EboActorsManager();

logger.debug("Initializing EBO processor...");
Expand Down
8 changes: 7 additions & 1 deletion packages/automated-dispute/src/exceptions/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ export class ErrorHandler {
this.logger.error(`Error executing custom action: ${actionError}`);
} finally {
if (strategy.shouldNotify) {
await this.notificationService.notifyError(error, context);
const errorMessage = this.notificationService.createErrorMessage(
error,
context,
"An error occurred in the custom contract",
);

await this.notificationService.send(errorMessage);
}

if (strategy.shouldReenqueue && context.reenqueueEvent) {
Expand Down
43 changes: 41 additions & 2 deletions packages/automated-dispute/src/interfaces/notificationService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,52 @@
/**
* Represents a notification message.
*/
export interface IMessage {
/**
* The main content of the message.
*/
title: string;
/**
* An optional subtitle for the message.
*/
subtitle?: string;
/**
* An optional description providing more details.
*/
description?: string;
/**
* The username to display as the sender.
*/
username?: string;
/**
* The URL of the avatar image to display.
*/
avatarUrl?: string;
/**
* An optional URL associated with the message.
*/
actionUrl?: string;
}

/**
* Interface representing a notification service capable of sending notifications.
*/
export interface NotificationService {
// send(message: IMessage): Promise<void>;
notifyError(err: Error, context: unknown): Promise<void>;
/**
* Sends a notification message.
*
* @param {IMessage} message - The message to send.
* @returns {Promise<void>} A promise that resolves when the message is sent.
*/
send(message: IMessage): Promise<void>;

/**
* Creates an IMessage from an error.
*
* @param err - The error object of type unknown.
* @param context - Additional context for the error.
* @param defaultMessage - A default message describing the error context.
* @returns An IMessage object ready to be sent via the notifier.
*/
createErrorMessage(err: unknown, context: unknown, defaultMessage: string): IMessage;
}
130 changes: 78 additions & 52 deletions packages/automated-dispute/src/services/discordNotifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isNativeError } from "util/types";
import type { APIEmbed, JSONEncodable } from "discord.js";
import { WebhookClient, WebhookMessageCreateOptions } from "discord.js";
import { stringify } from "viem";

import { NotificationFailureException } from "../exceptions/index.js";
import { IMessage, NotificationService } from "../interfaces/index.js";
Expand All @@ -10,82 +10,108 @@ export type WebhookMessage = WebhookMessageCreateOptions & {
};

/**
* TODO: Refactor me, this was a quick and dirty implementation
*
* `notifiyError` and `formatErrorMessage` should not exist.
* `send` should be the method to use across the codebase. (added as comment here and in the interface)
* If there is a new Notifier service (Ex. TelegramNotifier) it should implement the same interface (NotificationService).
* A notifier class for sending notifications to Discord via webhooks.
*/
export class DiscordNotifier implements NotificationService {
private client: WebhookClient;

/**
* Creates an instance of DiscordNotifier.
*
* @param {string} url - The Discord webhook URL.
*/
constructor(url: string) {
this.client = new WebhookClient({ url });
}

async notifyError(err: Error, context: unknown): Promise<void> {
const message = this.formatErrorMessage(err, context);

const webhookMessage = this.buildWebhookMessage({ title: message });
/**
* Sends a notification message to Discord.
*
* @param {IMessage} message - The message to send.
* @returns {Promise<void>} A promise that resolves when the message is sent.
*/
async send(message: IMessage): Promise<void> {
const webhookMessage = this.buildWebhookMessage(message);
try {
await this.client.send(webhookMessage);
console.error(this.client, this.client);
} catch (error) {
throw new NotificationFailureException("An error occured with Discord client.");
throw new NotificationFailureException("An error occurred with the Discord client.");
}
}

/**
* Formats the error message to be sent to Discord.
* @param {Error} error - The error object.
* @param {any} context - Additional context information.
* @returns {string} The formatted error message.
*/
private formatErrorMessage(error: Error, context: unknown): string {
return `**Error:** ${error.name} - ${error.message}\n**Context:**\n\`\`\`json\n${stringify(
context,
null,
2,
)}\n\`\`\``;
}

// /**
// * Sends a webhook message to Discord.
// * @param {WebhookMessage} message - The message to send.
// * @returns A promise that resolves when the message is sent.
// */
// async send(message: IMessage) {
// const webhookMessage = this.buildWebhookMessage(message);
// try {
// await this.client.send(webhookMessage);
// } catch (error) {
// throw new NotificationFailureException("An error occured with Discord client.");
// }
// }

/**
* Builds a Discord webhookMessage for an arbitrage opportunity.
* @param {ArbitrageOpportunity} data - The arbitrage data.
* Builds a Discord webhook message from the given IMessage.
*
* @param {IMessage} message - The message details.
* @returns {WebhookMessage} The built Discord webhook message.
*/
buildWebhookMessage(message: IMessage): WebhookMessage {
private buildWebhookMessage(message: IMessage): WebhookMessage {
return {
username: message.username || "EBO Agent",
content: message.title,
avatarURL:
message.avatarUrl || "https://i.ibb.co/x3HV1Tj/The-Graph-GRT-Symbol-Color.png", // TODO: change to a better image source
// embeds: [this.buildWebhookMessageEmbed(message)],
avatarURL: message.avatarUrl || "https://cryptologos.cc/logos/the-graph-grt-logo.png",
embeds:
message.subtitle || message.description
? [this.buildWebhookMessageEmbed(message)]
: undefined,
} as WebhookMessage;
}

buildWebhookMessageEmbed(message: IMessage) {
const title = message.subtitle;
const description = message.description;

/**
* Builds a Discord embed message from the given IMessage.
*
* @param {IMessage} message - The message details.
* @returns {APIEmbed | JSONEncodable<APIEmbed>} The built Discord embed message.
*/
private buildWebhookMessageEmbed(message: IMessage): APIEmbed | JSONEncodable<APIEmbed> {
return {
title,
description,
title: message.subtitle,
description: message.description,
color: 2326507,
url: message.actionUrl,
} as APIEmbed | JSONEncodable<APIEmbed>;
};
}

/**
* Creates an IMessage from an error.
*
* @param err - The error object of type unknown.
* @param context - Additional context for the error.
* @param defaultMessage - A default message describing the error context.
* @returns An IMessage object ready to be sent via the notifier.
*/
public createErrorMessage(
err: unknown,
context: Record<string, unknown>,
defaultMessage: string,
): IMessage {
if (isNativeError(err)) {
return {
title: `**Error:** ${err.name} - ${err.message}`,
description: `**Context:**\n\`\`\`json\n${JSON.stringify(
{
message: defaultMessage,
stack: err.stack,
...context,
},
null,
2,
)}\n\`\`\``,
};
} else {
return {
title: `**Error:** Unknown error`,
description: `**Context:**\n\`\`\`json\n${JSON.stringify(
{
message: defaultMessage,
error: String(err),
...context,
},
null,
2,
)}\n\`\`\``,
};
}
}
}
Loading

0 comments on commit 27c1f7d

Please sign in to comment.