From f25fe501406ee9e82a46753362f2129bcc8a630b Mon Sep 17 00:00:00 2001 From: SKINMAKER Date: Sun, 23 Apr 2023 19:00:43 +0900 Subject: [PATCH] feat: retry webhook (#557) * feat: retry webhook * fix: check condition to disable immediately * feat: start timeout from 2 squared * fix: not return for 5xx status code * chore: update docs --------- Co-authored-by: Eunwoo Choi --- api-docs | 2 +- utils/Webhook.ts | 117 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/api-docs b/api-docs index 093a4bb..8b77196 160000 --- a/api-docs +++ b/api-docs @@ -1 +1 @@ -Subproject commit 093a4bbcecde9f3587fa90c60ed02b0972bacbbf +Subproject commit 8b77196572399132ae3d9c10d27f5269ad819d39 diff --git a/utils/Webhook.ts b/utils/Webhook.ts index 3da4175..2c043ae 100644 --- a/utils/Webhook.ts +++ b/utils/Webhook.ts @@ -1,10 +1,11 @@ import { APIEmbed, ButtonStyle, Colors, ComponentType, DiscordAPIError, parseWebhookURL, Snowflake, WebhookClient } from 'discord.js' +import { setTimeout } from 'timers/promises' import { get, update } from './Query' import { DiscordBot, ServerListDiscordBot, webhookClients } from './DiscordBot' import { DiscordEnpoints } from './Constants' import fetch, { Response } from 'node-fetch' -import { Bot, Server, WebhookStatus, WebhookType } from '@types' +import { Bot, Server, Webhook, WebhookStatus, WebhookType } from '@types' import { makeBotURL, makeDiscordCodeblock, makeServerURL } from './Tools' import crypto from 'crypto' @@ -15,6 +16,75 @@ type RelayOptions = { secret: string, } +async function sendRequest({ + retryCount, + webhook, + target, + payload, +}: { + retryCount: number + webhook: Webhook + target: Bot | Server + payload: WebhookPayload +}) { + const id = target.id + const isBot = payload.type === 'bot' + + if(retryCount) { + await setTimeout(Math.pow(2, retryCount + 2) * 1000) + } + const result = await relayedFetch({ + dest: webhook.url, + method: 'POST', + data: JSON.stringify(payload), + secret: webhook.secret + }).then(async r => { + if(!r.ok) { + return null + } + return r.json() + }).catch(() => null) + + if(result === null) return + + if(result.success) { + const data = result.data + if((200 <= result.status && result.status < 300) && data.length === 0) { + await update.webhook(id, isBot ? 'bots' : 'servers', { failedSince: null }) + return + } else if((400 <= result.status && result.status < 500) || data.length !== 0) { + await update.webhook(id, isBot ? 'bots' : 'servers', { + status: WebhookStatus.Disabled, + failedSince: null, + secret: null + }) + sendFailedMessage(target) + return + } + } + if(retryCount === 10) { + if(!webhook.failedSince) { + await update.webhook(id, isBot ? 'bots' : 'servers', { + failedSince: Math.floor(Date.now() / 1000) + }) + } else if(Date.now() - webhook.failedSince * 1000 > 1000 * 60 * 60 * 24) { + await update.webhook(id, isBot ? 'bots' : 'servers', { + status: WebhookStatus.Disabled, + failedSince: null, + secret: null + }) + sendFailedMessage(target) + } + return + } + sendRequest({ + retryCount: retryCount + 1, + webhook, + target, + payload + }) +} + export function destroyWebhookClient(id: string, type: 'bot' | 'server') { const client = webhookClients[type].get(id) if(client) { @@ -87,7 +157,7 @@ export const verifyWebhook = async(webhookURL: string): Promise => { +export const sendWebhook = async (target: Bot | Server, payload: WebhookPayload): Promise => { const id = target.id const isBot = payload.type === 'bot' @@ -118,43 +188,16 @@ export const sendWebhook = async (target: Bot | Server, payload: WebhookPayload) if(!result) { await update.webhook(id, isBot ? 'bots' : 'servers', { status: WebhookStatus.Disabled }) sendFailedMessage(target) - return false + return } } else if(webhook.status === WebhookStatus.HTTP) { - const result = await relayedFetch({ - dest: webhook.url, - method: 'POST', - data: JSON.stringify(payload), - secret: webhook.secret - }).then(async r => { - if(!r.ok) { - return null - } - return r.json() - }).catch(() => null) - if(result === null) return - - if(result.success) { - const data = result.data - if((200 <= result.status && result.status < 300) && data.length === 0) { - await update.webhook(id, isBot ? 'bots' : 'servers', { failedSince: null }) - return true - } - } - - if(!webhook.failedSince) { - await update.webhook(id, isBot ? 'bots' : 'servers', { - failedSince: Math.floor(Date.now() / 1000) - }) - } else if(Date.now() - webhook.failedSince * 1000 > 1000 * 60 * 60 * 24) { - await update.webhook(id, isBot ? 'bots' : 'servers', { - status: WebhookStatus.Disabled, - failedSince: null, - secret: null - }) - sendFailedMessage(target) - } - return false + sendRequest({ + retryCount: 0, + webhook, + target, + payload + }) + return } }