Ticketanizer-Versand: automatische Wiederholung bei Rate-Limit (429)
Fremde Bewerber koennen theoretisch kurz hintereinander absenden (z.B. wenn Dogi live "bewerbt euch jetzt" sagt) und dabei dasselbe Rate-Limit treffen, das beim Testen aufgefallen ist. Jetzt wird ein 429 automatisch bis zu 2x mit kurzer Pause wiederholt (Retry-After-Header wird beachtet, sonst 2s/5s Standard-Wartezeit), bevor endgueltig aufgegeben wird - laeuft komplett im Hintergrund ueber ctx.waitUntil, blockiert nie die Antwort an den Bewerber. Die Bewerbung selbst landet ohnehin immer sicher in der eigenen Datenbank, unabhaengig vom Ticketanizer-Ergebnis. Getestet mit 3 gleichzeitigen Bewerbungen - alle liefen sauber durch.
This commit is contained in:
@@ -26,6 +26,14 @@
|
|||||||
Rein informativ, kein Ausfallpunkt: Fehlt der API-Key, oder schlägt der
|
Rein informativ, kein Ausfallpunkt: Fehlt der API-Key, oder schlägt der
|
||||||
Versand fehl (Ticketanizer down, falscher Key, ...), wird die Bewerbung
|
Versand fehl (Ticketanizer down, falscher Key, ...), wird die Bewerbung
|
||||||
trotzdem ganz normal in der eigenen Datenbank gespeichert.
|
trotzdem ganz normal in der eigenen Datenbank gespeichert.
|
||||||
|
|
||||||
|
Automatische Wiederholung bei Rate-Limit (HTTP 429): Wenn mehrere
|
||||||
|
Menschen kurz hintereinander ein Formular abschicken, kann Ticketanizer
|
||||||
|
kurzzeitig 429 antworten (selbst erlebt beim Testen). In dem Fall wird
|
||||||
|
der Versand automatisch bis zu 2x mit kurzer Pause wiederholt (siehe
|
||||||
|
ticketAnfrageSenden), bevor endgültig aufgegeben wird — läuft komplett
|
||||||
|
im Hintergrund über ctx.waitUntil, blockiert also nie die Antwort an
|
||||||
|
die Bewerberin/den Bewerber.
|
||||||
===================================================================== */
|
===================================================================== */
|
||||||
|
|
||||||
const TICKETANIZER_BASIS_URL = "https://api.ticketanizer.com";
|
const TICKETANIZER_BASIS_URL = "https://api.ticketanizer.com";
|
||||||
@@ -67,6 +75,47 @@ function ersteNachrichtAufbauen(type, data) {
|
|||||||
return text.length > 3500 ? text.slice(0, 3500) + "…" : text;
|
return text.length > 3500 ? text.slice(0, 3500) + "…" : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const warten = (ms) => new Promise((fertig) => setTimeout(fertig, ms));
|
||||||
|
|
||||||
|
// Wenn viele Menschen kurz hintereinander ein Formular abschicken (z.B. weil
|
||||||
|
// Dogi gerade live "bewerbt euch jetzt!" sagt), kann Ticketanizer mit
|
||||||
|
// HTTP 429 ("zu viele Anfragen") antworten. Die Bewerbung selbst ist davon
|
||||||
|
// nie betroffen (steht schon sicher in unserer DB) — hier wird nur der
|
||||||
|
// Ticket-Versand selbst automatisch 1–2x mit kurzer Pause wiederholt,
|
||||||
|
// bevor endgültig aufgegeben wird. Nutzt Retry-After, falls Ticketanizer
|
||||||
|
// das mitschickt, sonst eine feste, ansteigende Wartezeit.
|
||||||
|
const MAX_VERSUCHE = 3;
|
||||||
|
const STANDARD_WARTEZEITEN_MS = [2000, 5000];
|
||||||
|
|
||||||
|
async function ticketAnfrageSenden(apiKey, panelId, visitorName, firstMessage) {
|
||||||
|
for (let versuch = 1; versuch <= MAX_VERSUCHE; versuch++) {
|
||||||
|
const antwort = await fetch(`${TICKETANIZER_BASIS_URL}/v1/tickets`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-API-Key": apiKey,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
panel_id: panelId,
|
||||||
|
visitor_name: visitorName,
|
||||||
|
first_message: firstMessage,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (antwort.status !== 429 || versuch === MAX_VERSUCHE) {
|
||||||
|
return antwort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryAfterHeader = Number(antwort.headers.get("Retry-After"));
|
||||||
|
const wartezeit = Number.isFinite(retryAfterHeader) && retryAfterHeader > 0
|
||||||
|
? retryAfterHeader * 1000
|
||||||
|
: STANDARD_WARTEZEITEN_MS[versuch - 1] || 5000;
|
||||||
|
|
||||||
|
console.log(`[ticketanizer] Rate-Limit (Versuch ${versuch}/${MAX_VERSUCHE}) — warte ${wartezeit}ms und versuche es erneut.`);
|
||||||
|
await warten(wartezeit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function bewerbungAlsTicketAnlegen(env, type, data) {
|
export async function bewerbungAlsTicketAnlegen(env, type, data) {
|
||||||
const apiKey = env.TICKETANIZER_API_KEY;
|
const apiKey = env.TICKETANIZER_API_KEY;
|
||||||
const panelId = PANEL_IDS[type];
|
const panelId = PANEL_IDS[type];
|
||||||
@@ -83,18 +132,7 @@ export async function bewerbungAlsTicketAnlegen(env, type, data) {
|
|||||||
const firstMessage = ersteNachrichtAufbauen(type, data);
|
const firstMessage = ersteNachrichtAufbauen(type, data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const antwort = await fetch(`${TICKETANIZER_BASIS_URL}/v1/tickets`, {
|
const antwort = await ticketAnfrageSenden(apiKey, panelId, visitorName, firstMessage);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"X-API-Key": apiKey,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
panel_id: panelId,
|
|
||||||
visitor_name: visitorName,
|
|
||||||
first_message: firstMessage,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const antwortText = await antwort.text().catch(() => "");
|
const antwortText = await antwort.text().catch(() => "");
|
||||||
if (!antwort.ok) {
|
if (!antwort.ok) {
|
||||||
console.error(`[ticketanizer] Fehler beim Ticket-Anlegen (${type}, panel ${panelId}): HTTP ${antwort.status} — ${antwortText}`);
|
console.error(`[ticketanizer] Fehler beim Ticket-Anlegen (${type}, panel ${panelId}): HTTP ${antwort.status} — ${antwortText}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user