feat: improve error handling for authentication flow
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import { z } from "zod";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import { PromiseTimedOutError, promiseTimeout } from "~/libs/promiseTimeout";
|
||||
import { readEnvVariable } from "~/libs/readEnvVariable";
|
||||
import { sortByProperty } from "~/libs/sortByProperty";
|
||||
|
||||
import type { EpisodesResponse } from "./episode";
|
||||
import { getValue, setValue } from "~/models/kv";
|
||||
import type { Env } from "~/types/env";
|
||||
import type { EpisodesResponse } from "~/types/episode";
|
||||
|
||||
export async function getEpisodesFromAnify(
|
||||
isAnifyEnabled: boolean,
|
||||
env: Env,
|
||||
aniListId: number,
|
||||
): Promise<EpisodesResponse | null> {
|
||||
if (shouldSkipAnify(isAnifyEnabled, aniListId)) {
|
||||
if (await shouldSkipAnify(env, aniListId)) {
|
||||
console.log("Skipping Anify for title", aniListId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -19,9 +22,25 @@ export async function getEpisodesFromAnify(
|
||||
response = await promiseTimeout(
|
||||
fetch(`https://anify.eltik.cc/episodes/${aniListId}`, {
|
||||
signal: abortController.signal,
|
||||
}).then((res) => res.json<AnifyEpisodesResponse[]>()),
|
||||
}).then((res) => res.json() as Promise<AnifyEpisodesResponse[]>),
|
||||
30 * 1000,
|
||||
);
|
||||
if ("error" in response) {
|
||||
const error = response.error;
|
||||
if (error === "Too many requests") {
|
||||
console.log(
|
||||
"Sending too many requests to Anify, setting killswitch until",
|
||||
DateTime.now().plus({ minutes: 1 }).toISO(),
|
||||
);
|
||||
setValue(
|
||||
env,
|
||||
"anify_killswitch_till",
|
||||
DateTime.now().plus({ minutes: 1 }).toISO(),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof PromiseTimedOutError) {
|
||||
abortController.abort("Loading episodes from Anify timed out");
|
||||
@@ -73,11 +92,11 @@ export async function getEpisodesFromAnify(
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldSkipAnify(
|
||||
isAnifyEnabled: boolean,
|
||||
export async function shouldSkipAnify(
|
||||
env: Env,
|
||||
aniListId: number,
|
||||
): boolean {
|
||||
if (!isAnifyEnabled) {
|
||||
): Promise<boolean> {
|
||||
if (!readEnvVariable(env, "ENABLE_ANIFY")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -92,7 +111,13 @@ export function shouldSkipAnify(
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return await getValue(env, "anify_killswitch_till").then((dateTime) => {
|
||||
if (!dateTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DateTime.fromISO(dateTime).diffNow().as("minutes") > 0;
|
||||
});
|
||||
}
|
||||
|
||||
interface AnifyEpisodesResponse {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { findBestMatchingTitle } from "~/libs/findBestMatchingTitle";
|
||||
import { sleep } from "~/libs/sleep";
|
||||
import { Episode, type EpisodesResponse } from "~/types/episode";
|
||||
|
||||
export async function getEpisodesFromAniwatch(
|
||||
aniListId: number,
|
||||
shouldRetry: boolean = false,
|
||||
): Promise<EpisodesResponse | null> {
|
||||
try {
|
||||
const animeTitle = await import("~/libs/anilist/getTitle")
|
||||
@@ -48,6 +50,16 @@ export async function getEpisodesFromAniwatch(
|
||||
|
||||
return { providerId: "aniwatch", episodes };
|
||||
} catch (error) {
|
||||
if (shouldRetry && "response" in error && error.response.status === 429) {
|
||||
console.log(
|
||||
"429, retrying in",
|
||||
error.response.headers.get("Retry-After"),
|
||||
);
|
||||
return sleep(
|
||||
Number(error.response.headers.get("Retry-After")!) * 1000,
|
||||
).then(() => getEpisodesFromAniwatch(aniListId));
|
||||
}
|
||||
|
||||
console.error(
|
||||
new Error(
|
||||
`Error trying to load episodes from aniwatch; aniListId: ${aniListId}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
||||
import { env } from "hono/adapter";
|
||||
|
||||
import { fetchFromMultipleSources } from "~/libs/fetchFromMultipleSources";
|
||||
import { readEnvVariable } from "~/libs/readEnvVariable";
|
||||
@@ -45,13 +46,13 @@ const app = new OpenAPIHono<Env>();
|
||||
|
||||
export function fetchEpisodesFromAllProviders(
|
||||
aniListId: number,
|
||||
isAnifyEnabled: boolean,
|
||||
env: Env,
|
||||
): Promise<EpisodesResponse[]> {
|
||||
return Promise.allSettled([
|
||||
import("./aniwatch").then(({ getEpisodesFromAniwatch }) =>
|
||||
getEpisodesFromAniwatch(aniListId),
|
||||
),
|
||||
getEpisodesFromAnify(isAnifyEnabled, aniListId),
|
||||
getEpisodesFromAnify(env, aniListId),
|
||||
]).then((episodeResults) =>
|
||||
episodeResults
|
||||
.filter((result) => result.status === "fulfilled")
|
||||
@@ -60,16 +61,20 @@ export function fetchEpisodesFromAllProviders(
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchEpisodes(aniListId: number, isAnifyEnabled: boolean) {
|
||||
export function fetchEpisodes(
|
||||
aniListId: number,
|
||||
env: Env,
|
||||
shouldRetry: boolean = false,
|
||||
) {
|
||||
return fetchFromMultipleSources([
|
||||
() => getEpisodesFromAnify(isAnifyEnabled, aniListId),
|
||||
() => getEpisodesFromAnify(env, aniListId),
|
||||
// () =>
|
||||
// import("./consumet").then(({ getEpisodesFromConsumet }) =>
|
||||
// getEpisodesFromConsumet(aniListId),
|
||||
// ),
|
||||
() =>
|
||||
import("./aniwatch").then(({ getEpisodesFromAniwatch }) =>
|
||||
getEpisodesFromAniwatch(aniListId),
|
||||
getEpisodesFromAniwatch(aniListId, shouldRetry),
|
||||
),
|
||||
]);
|
||||
}
|
||||
@@ -79,7 +84,7 @@ app.openapi(route, async (c) => {
|
||||
|
||||
const { result: episodes, errorOccurred } = await fetchEpisodes(
|
||||
aniListId,
|
||||
readEnvVariable<boolean>(c.env, "ENABLE_ANIFY"),
|
||||
env(c, "workerd"),
|
||||
);
|
||||
|
||||
if (errorOccurred) {
|
||||
|
||||
Reference in New Issue
Block a user