mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-05 05:18:12 +03:00
kai-api traverser
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { KtLogger } from "../../../../types";
|
||||
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
|
||||
import nodeFetch from "../../../../fetch";
|
||||
import { KaiAuthDocument } from "kamaitachi-common";
|
||||
|
||||
/**
|
||||
* Traverses a Kai-like personal_bests api.
|
||||
* @param baseUrl The base URL to fetch requests from. - https://google.com
|
||||
* @param subUrl The endpoint for the scores - /api/v1/personal_bests
|
||||
* @param authDoc The users' authentication document.
|
||||
* @param fetch This is so `fetch` can be mocked with something
|
||||
* that wont make http requests during tests.
|
||||
*/
|
||||
export async function* TraverseKaiAPI(
|
||||
baseUrl: string,
|
||||
subUrl: string,
|
||||
authDoc: KaiAuthDocument,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
) {
|
||||
let fetchMoreData = true;
|
||||
let url = `${baseUrl}${subUrl}`;
|
||||
|
||||
while (fetchMoreData) {
|
||||
// Rough SSRF check - KAI apis could potentially get us to perform
|
||||
// SSRF. We check if the origin of the next request is exactly the
|
||||
// original, expected, base URL.
|
||||
const origin = new URL(url).origin;
|
||||
|
||||
if (origin !== baseUrl) {
|
||||
// this might be critical, to be honest.
|
||||
logger.severe(`${baseUrl} attempted SSRF with url ${url}?`);
|
||||
throw new ScoreImportFatalError(500, `${baseUrl} returned invalid data.`);
|
||||
}
|
||||
|
||||
let json;
|
||||
// wrap all this in a try catch just incase the fetch or the res
|
||||
// json call fails.
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authDoc.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
json = await res.json();
|
||||
} catch (err) {
|
||||
logger.error(`Recieved invalid response from ${url}.`, { err });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Recieved invalid response from ${url}. Are they down?`
|
||||
);
|
||||
}
|
||||
|
||||
if (json._links === null || typeof json._links !== "object") {
|
||||
logger.error(`Recieved invalid JSON from ${url}. Invalid _links.`);
|
||||
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Recieved no _links prop from ${url}. This is not an error with Kamaitachi.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof json._links._next === "string") {
|
||||
url = json._links._next;
|
||||
} else if (json._links._next === null) {
|
||||
// exit the loop after this, we're on the last page.
|
||||
fetchMoreData = false;
|
||||
} else {
|
||||
logger.error(`Recieved invalid response from ${url}. Invalid _links._next.`);
|
||||
|
||||
throw new ScoreImportFatalError(500, `Recieved invalid _links._next prop from ${url}.`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(json._items)) {
|
||||
throw new ScoreImportFatalError(500, `Recieved invalid _items from ${url}.`);
|
||||
}
|
||||
|
||||
// yield everything out of the score array
|
||||
for (const score of json._items) {
|
||||
yield score as unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user