Rudimentary error reporting

This commit is contained in:
Natty 2024-05-17 14:25:19 +02:00
parent 5ae5c2f8d6
commit 1ea9d65571
No known key found for this signature in database
GPG key ID: BF6CB659ADEE60EC

View file

@ -18,10 +18,13 @@ async function apiRequestWithHeaders(url, options = null) {
throw new Error(`Error fetching ${url}: ${response.status} ${response.statusText}`);
})
.then(async response => ({ headers: response.headers, body: await response.json() }))
.then(async response => ({ response: { headers: response.headers, body: await response.json(), error: undefined }}))
.catch(error => {
console.error(`Error fetching ${url}: ${error}`);
return null;
console.error(`Error fetching ${url}:`, error);
return {
response: undefined,
error: `Error fetching ${url}: ${error}`
};
});
}
@ -32,7 +35,16 @@ async function apiRequestWithHeaders(url, options = null) {
*/
async function apiRequest(url, options = null) {
const reply = await apiRequestWithHeaders(url, options);
return reply?.body;
if (!reply.response) {
return {
error: reply.error
};
}
return {
response: reply.response.body
};
}
function Handle(name, instance) {
@ -80,7 +92,7 @@ Handle.prototype.webFinger = async function () {
resource: `acct:${this}`
});
let webFinger = await apiRequest(defaultWebfingerUrl);
let { response: webFinger } = await apiRequest(defaultWebfingerUrl);
if (!webFinger) {
const contentTypeXrd = "application/xrd+xml";
@ -104,7 +116,7 @@ Handle.prototype.webFinger = async function () {
?.getAttribute("template");
if (webfingerTemplate)
webFinger = await apiRequest(webfingerTemplate.replace("{uri}", `acct:${this}`));
webFinger = (await apiRequest(webfingerTemplate.replace("{uri}", `acct:${this}`)))?.response;
}
if (!webFinger)
@ -204,7 +216,7 @@ class ApiClient {
}
let url = `https://${instance}/.well-known/nodeinfo`;
let nodeInfo = await apiRequest(url);
let { response: nodeInfo } = await apiRequest(url);
if (!nodeInfo || !Array.isArray(nodeInfo.links)) {
const client = new MastodonApiClient(instance, true);
@ -226,11 +238,11 @@ class ApiClient {
return client;
}
let apiResponse = await apiRequest(apiLink.href);
let { response: apiResponse } = await apiRequest(apiLink.href);
if (!apiResponse) {
// Guess from API endpoints
const misskeyMeta = await apiRequest(`https://${instance}/api/meta`, {
const { response: misskeyMeta } = await apiRequest(`https://${instance}/api/meta`, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -270,7 +282,7 @@ class ApiClient {
return client;
}
let features = apiResponse?.metadata?.features;
let features = apiResponse?.metadata?.["features"];
if (Array.isArray(features) && features.includes("pleroma_api")) {
const has_emoji_reacts = features.includes("pleroma_emoji_reactions");
const client = new PleromaApiClient(instance, has_emoji_reacts);
@ -286,14 +298,14 @@ class ApiClient {
/**
* @param {Handle} handle
*
* @returns {Promise<FediUser>}
* @returns {Promise<{ response: FediUser, error: undefined } | { response: undefined, error: string }>}
*/
async getUserIdFromHandle(handle){ throw new Error("Not implemented"); }
/**
* @param {FediUser} user
*
* @returns {Promise<Note[]>}
* @returns {Promise<{ response: Note[], error: undefined } | { response: undefined, error: string }>}
*/
async getNotes(user){ throw new Error("Not implemented"); }
@ -427,9 +439,13 @@ class MastodonApiClient extends ApiClient {
let remaining = targetCount;
let data = [];
while (remaining > 0 && nextUrl !== null) {
const reply = await apiRequestWithHeaders(nextUrl);
if (reply?.body === null) {
const { response: reply, error } = await apiRequestWithHeaders(nextUrl);
if (reply?.body == null || error) {
console.error(`Error while gathering entries. Returning incomplete data!`);
if (data.length === 0) {
return { error };
}
break;
}
nextUrl = MastodonApiClient.getNextPage(reply.headers);
@ -443,37 +459,45 @@ class MastodonApiClient extends ApiClient {
break;
}
return data.length === 0 ? null : data.flat();
return { response: data.length === 0 ? null : data.flat() };
}
async getUserIdFromHandle(handle) {
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle.baseHandle}`;
let response = await apiRequest(url, null);
let { response } = await apiRequest(url, null);
if (!response) {
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle}`;
response = await apiRequest(url, null);
const { response: res, error } = (await apiRequest(url, null));
if (error) {
return { error };
}
response = res;
}
if (!response) {
return null;
if (typeof response !== "object") {
return { error: `Could not parse user lookup response from server, invalid object: ${response}` };
}
return {
id: response.id,
avatar: response.avatar,
bot: response.bot,
name: response["display_name"],
handle: handle,
response: {
id: response.id,
avatar: response.avatar,
bot: response.bot,
name: response["display_name"],
handle: handle,
}
};
}
async getNotes(user) {
const url = `https://${this._instance}/api/v1/accounts/${user.id}/statuses?exclude_replies=true&exclude_reblogs=true&limit=${this._API_LIMIT_SMALL}`;
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_NOTES, this._API_LIMIT_SMALL, true);
const { response, error} = await MastodonApiClient.apiRequestPaged(url, this._CNT_NOTES, this._API_LIMIT_SMALL, true);
if (!response) {
return null;
if (error) {
return { error };
}
if (response?.some(note => note?.["pleroma"]?.["emoji_reactions"]?.length)) {
@ -482,15 +506,17 @@ class MastodonApiClient extends ApiClient {
this._flavor = MastodonFlavor.FEDIBIRD;
}
return response.map(note => ({
id: note.id,
replies: note["replies_count"] || 0,
renotes: note["reblogs_count"] || 0,
favorites: note["favourites_count"],
extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0,
instance: this._instance,
author: user
}));
return {
response: response.map(note => ({
id: note.id,
replies: note["replies_count"] || 0,
renotes: note["reblogs_count"] || 0,
favorites: note["favourites_count"],
extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0,
instance: this._instance,
author: user
}))
};
}
async getRenotes(note) {
@ -513,7 +539,7 @@ class MastodonApiClient extends ApiClient {
async getReplies(noteIn) {
// The context endpoint has no limit parameter or pages
const url = `https://${this._instance}/api/v1/statuses/${noteIn.id}/context`;
const response = await apiRequest(url);
const response = (await apiRequest(url)).response;
if (!response) {
return null;
@ -549,7 +575,7 @@ class MastodonApiClient extends ApiClient {
async getFavs(note) {
const url = `https://${this._instance}/api/v1/statuses/${note.id}/favourited_by?limit=${this._API_LIMIT}`;
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT);
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT)).response;
if (!response) {
return null;
@ -592,7 +618,7 @@ class PleromaApiClient extends MastodonApiClient {
// The documentation doesn't specify the hardcoded limit, so just use the lowest known one
const url = `https://${this._instance}/api/v1/pleroma/statuses/${note.id}/reactions?limit=${this._API_LIMIT_SMALL}`;
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL) ?? [];
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL)).response ?? [];
/**
* @type {Map<string, FediUser>}
@ -642,7 +668,7 @@ class FedibirdApiClient extends MastodonApiClient {
// Could not locate documentation for Fedibird API, so just use the lowest known limit
const url = `https://${this._instance}/api/v1/statuses/${note.id}/emoji_reactioned_by?limit=${this._API_LIMIT_SMALL}`;
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL) ?? [];
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL)).response ?? [];
for (const reaction of response) {
let account = reaction["account"];
@ -682,7 +708,7 @@ class MisskeyApiClient extends ApiClient {
async getUserIdFromHandle(handle) {
const lookupUrl = `https://${this._instance}/api/users/search-by-username-and-host`;
const lookup = await apiRequest(lookupUrl, {
const lookup = (await apiRequest(lookupUrl, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -691,7 +717,7 @@ class MisskeyApiClient extends ApiClient {
username: handle.name,
host: null
}
});
})).response;
let id = null;
@ -707,7 +733,7 @@ class MisskeyApiClient extends ApiClient {
}
const url = `https://${this._instance}/api/users/show`;
const response = await apiRequest(url, {
const { response, error } = await apiRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -718,22 +744,24 @@ class MisskeyApiClient extends ApiClient {
}
});
if (!response) {
return null;
if (error) {
return { error };
}
return {
id: response.id,
avatar: response["avatarUrl"],
bot: response["isBot"],
name: response["name"],
handle: handle,
response: {
id: response.id,
avatar: response["avatarUrl"],
bot: response["isBot"],
name: response["name"],
handle: handle,
}
};
}
async getNotes(user) {
const url = `https://${this._instance}/api/users/notes`;
const response = await apiRequest(url, {
const { response, error } = await apiRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -748,24 +776,26 @@ class MisskeyApiClient extends ApiClient {
}
});
if (!response) {
return null;
if (error) {
return { error };
}
return response.map(note => ({
id: note.id,
replies: note["repliesCount"],
renotes: note["renoteCount"],
favorites: Object.values(note["reactions"]).reduce((a, b) => a + b, 0),
extra_reacts: false,
instance: this._instance,
author: user
}));
return {
response: response.map(note => ({
id: note.id,
replies: note["repliesCount"],
renotes: note["renoteCount"],
favorites: Object.values(note["reactions"]).reduce((a, b) => a + b, 0),
extra_reacts: false,
instance: this._instance,
author: user
}))
};
}
async getRenotes(note) {
const url = `https://${this._instance}/api/notes/renotes`;
const response = await apiRequest(url, {
const response = (await apiRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -774,7 +804,7 @@ class MisskeyApiClient extends ApiClient {
noteId: note.id,
limit: this._CNT_RENOTES,
}
});
})).response;
if (!response) {
return null;
@ -791,7 +821,7 @@ class MisskeyApiClient extends ApiClient {
async getReplies(note) {
const url = `https://${this._instance}/api/notes/replies`;
const response = await apiRequest(url, {
const response = (await apiRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -800,7 +830,7 @@ class MisskeyApiClient extends ApiClient {
noteId: note.id,
limit: this._CNT_REPLIES,
}
});
})).response;
if (!response) {
return null;
@ -829,7 +859,7 @@ class MisskeyApiClient extends ApiClient {
async getFavs(note) {
const url = `https://${this._instance}/api/notes/reactions`;
const response = await apiRequest(url, {
const response = (await apiRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -838,7 +868,7 @@ class MisskeyApiClient extends ApiClient {
noteId: note.id,
limit: this._CNT_FAVS,
}
});
})).response;
if (!response) {
return null;
@ -921,10 +951,10 @@ async function circleMain() {
progress.innerText = "Fetching your user...";
const user = await client.getUserIdFromHandle(selfUser);
const { response: user, error: userError } = await client.getUserIdFromHandle(selfUser);
if (!user) {
alert("Something went horribly wrong, couldn't fetch your user.");
if (userError) {
alert(`Something went horribly wrong, couldn't fetch your user:\n\n${userError}`);
fediHandle.disabled = false;
for (const radio of backend) {
radio.disabled = false;
@ -937,10 +967,10 @@ async function circleMain() {
progress.innerText = "Fetching your latest posts...";
const notes = await client.getNotes(user);
const { response: notes, error: noteError } = await client.getNotes(user);
if (!notes) {
alert("Something went horribly wrong, couldn't fetch your notes.");
if (noteError) {
alert(`Something went horribly wrong, couldn't fetch your notes:\n\n${noteError}`);
return;
}