refactor: use Link HTTP header for pagination

This commit is contained in:
mae taylor 2025-07-15 16:34:23 +01:00
parent 99def58c8b
commit 3b8ca902f1
Signed by: mae
GPG key ID: 3C80D76BA7A3B9BD
2 changed files with 74 additions and 32 deletions

View file

@ -2,6 +2,31 @@ const errors = {
AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED", AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED",
}; };
/**
* Parses a HTTP Link header
* @param {string} header - the HTTP Link header string
*/
function _parseLinkHeader(header) {
// remove whitespace and split
let links = header.replace(/\ /g, "").split(",");
return links.map(l => {
let parts = l.split(";");
// assuming 0th is URL, removing <>
let url = new URL(parts[0].slice(1, -1));
// get rel inbetween double-quotes
let rel = parts[1].match(/"(.*?)"/g)[0].slice(1, -1);
return {
url, rel
}
})
}
_parseLinkHeader(`<https://wetdry.world/api/v1/timelines/home?max_id=114857293229157171>; rel="next", <https://wetdry.world/api/v1/timelines/home?min_id=114857736990577458>; rel="prev"`)
/** /**
* GET /api/v1/instance * GET /api/v1/instance
* @param {string} host - The domain of the target server. * @param {string} host - The domain of the target server.
@ -258,9 +283,43 @@ export async function getTimeline(host, token, timeline, max_id, local_only, rem
const data = await fetch(url, { const data = await fetch(url, {
method: 'GET', method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null } headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json()); })
return data; let links = _parseLinkHeader(data.headers.get("Link"));
return {
data: await data.json(),
prev: links.find(f=>f.rel=="prev"),
next: links.find(f=>f.rel=="next")
};
}
/**
* GET /api/v1/favourites
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} max_id - If provided, only shows posts after this ID.
*/
export async function getFavourites(host, token, max_id) {
let url = `https://${host}/api/v1/favourites`;
let params = new URLSearchParams();
if (max_id) params.append("max_id", max_id);
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
})
let links = _parseLinkHeader(data.headers.get("Link"));
return {
data: await data.json(),
prev: links.find(f=>f.rel=="prev"),
next: links.find(f=>f.rel=="next")
};
} }
/** /**
@ -559,26 +618,4 @@ export async function getUserPinnedPosts(host, token, user_id) {
}).then(res => res.json()); }).then(res => res.json());
return data; return data;
} }
/**
* GET /api/v1/favourites
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} max_id - If provided, only shows posts after this ID.
*/
export async function getFavourites(host, token, timeline, max_id) {
let url = `https://${host}/api/v1/favourites`;
let params = new URLSearchParams();
if (max_id) params.append("max_id", max_id);
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}

View file

@ -10,14 +10,14 @@ export const timeline = writable([]);
const lang = Lang(); const lang = Lang();
let loading = false; let loading = false;
let last_post = false;
export async function getTimeline(timelineType = "home", clean, localOnly = false, remoteOnly = false) { export async function getTimeline(timelineType = "home", clean, localOnly = false, remoteOnly = false) {
if (loading) return; // no spamming!! if (loading) return; // no spamming!!
loading = true; loading = true;
let last_post = false; // if (!clean && get(timeline).length > 0)
if (!clean && get(timeline).length > 0) // last_post = get(timeline)[get(timeline).length - 1].id;
last_post = get(timeline)[get(timeline).length - 1].id;
let timeline_data; let timeline_data;
switch(timelineType) { switch(timelineType) {
@ -25,7 +25,7 @@ export async function getTimeline(timelineType = "home", clean, localOnly = fals
timeline_data = await api.getFavourites( timeline_data = await api.getFavourites(
get(server).host, get(server).host,
get(app).token, get(app).token,
last_post, last_post
) )
break; break;
@ -47,10 +47,15 @@ export async function getTimeline(timelineType = "home", clean, localOnly = fals
return; return;
} }
if (clean) timeline.set([]); if (clean) {
timeline.set([]);
last_post = false;
} else {
last_post = timeline_data.next.url.searchParams.get("max_id")
}
for (let i in timeline_data) { for (let i in timeline_data.data) {
const post_data = timeline_data[i]; const post_data = timeline_data.data[i];
const post = await parsePost(post_data, 1); const post = await parsePost(post_data, 1);
if (!post) { if (!post) {
if (post === null || post === undefined) { if (post === null || post === undefined) {