Merge branch 'mastodon-response' into 'main'

MastodonClient: return a MastodonResponse object with `.pagination()` convenience method

See merge request soapbox-pub/soapbox!3245
This commit is contained in:
Alex Gleason 2024-11-13 18:59:29 +00:00
commit a80238275f
6 changed files with 31 additions and 33 deletions

View File

@ -1,4 +1,5 @@
import { HTTPError } from './HTTPError.ts'; import { HTTPError } from './HTTPError.ts';
import { MastodonResponse } from './MastodonResponse.ts';
interface Opts { interface Opts {
searchParams?: URLSearchParams | Record<string, string | number | boolean>; searchParams?: URLSearchParams | Record<string, string | number | boolean>;
@ -19,35 +20,35 @@ export class MastodonClient {
this.accessToken = accessToken; this.accessToken = accessToken;
} }
async get(path: string, opts: Opts = {}): Promise<Response> { async get(path: string, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('GET', path, undefined, opts); return this.request('GET', path, undefined, opts);
} }
async post(path: string, data?: unknown, opts: Opts = {}): Promise<Response> { async post(path: string, data?: unknown, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('POST', path, data, opts); return this.request('POST', path, data, opts);
} }
async put(path: string, data?: unknown, opts: Opts = {}): Promise<Response> { async put(path: string, data?: unknown, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('PUT', path, data, opts); return this.request('PUT', path, data, opts);
} }
async delete(path: string, opts: Opts = {}): Promise<Response> { async delete(path: string, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('DELETE', path, undefined, opts); return this.request('DELETE', path, undefined, opts);
} }
async patch(path: string, data: unknown, opts: Opts = {}): Promise<Response> { async patch(path: string, data: unknown, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('PATCH', path, data, opts); return this.request('PATCH', path, data, opts);
} }
async head(path: string, opts: Opts = {}): Promise<Response> { async head(path: string, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('HEAD', path, undefined, opts); return this.request('HEAD', path, undefined, opts);
} }
async options(path: string, opts: Opts = {}): Promise<Response> { async options(path: string, opts: Opts = {}): Promise<MastodonResponse> {
return this.request('OPTIONS', path, undefined, opts); return this.request('OPTIONS', path, undefined, opts);
} }
async request(method: string, path: string, data: unknown, opts: Opts = {}): Promise<Response> { async request(method: string, path: string, data: unknown, opts: Opts = {}): Promise<MastodonResponse> {
const url = new URL(path, this.baseUrl); const url = new URL(path, this.baseUrl);
if (opts.searchParams) { if (opts.searchParams) {
@ -89,7 +90,7 @@ export class MastodonClient {
throw new HTTPError(response, request); throw new HTTPError(response, request);
} }
return response; return new MastodonResponse(response.body, response);
} }
} }

View File

@ -0,0 +1,16 @@
import LinkHeader from 'http-link-header';
export class MastodonResponse extends Response {
/** Parses the `Link` header and returns URLs for the `prev` and `next` pages of this response, if any. */
pagination(): { prev?: string; next?: string } {
const header = this.headers.get('link');
const links = header ? new LinkHeader(header) : undefined;
return {
next: links?.refs.find((link) => link.rel === 'next')?.uri,
prev: links?.refs.find((link) => link.rel === 'prev')?.uri,
};
}
}

View File

@ -13,7 +13,6 @@ import { normalizeChatMessage } from 'soapbox/normalizers/index.ts';
import toast from 'soapbox/toast.tsx'; import toast from 'soapbox/toast.tsx';
import { ChatMessage } from 'soapbox/types/entities.ts'; import { ChatMessage } from 'soapbox/types/entities.ts';
import { reOrderChatListItems, updateChatMessage } from 'soapbox/utils/chats.ts'; import { reOrderChatListItems, updateChatMessage } from 'soapbox/utils/chats.ts';
import { getPagination } from 'soapbox/utils/pagination.ts';
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries.ts'; import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries.ts';
import { queryClient } from './client.ts'; import { queryClient } from './client.ts';
@ -91,7 +90,7 @@ const useChatMessages = (chat: IChat) => {
const response = await api.get(uri); const response = await api.get(uri);
const data = await response.json(); const data = await response.json();
const { next } = getPagination(response); const { next } = response.pagination();
const hasMore = !!next; const hasMore = !!next;
const result = data.map(normalizeChatMessage); const result = data.map(normalizeChatMessage);
@ -144,7 +143,7 @@ const useChats = (search?: string) => {
}); });
const data: IChat[] = await response.json(); const data: IChat[] = await response.json();
const { next } = getPagination(response); const { next } = response.pagination();
const hasMore = !!next; const hasMore = !!next;
setUnreadChatsCount(Number(response.headers.get('x-unread-messages-count')) || sumBy(data, (chat) => chat.unread)); setUnreadChatsCount(Number(response.headers.get('x-unread-messages-count')) || sumBy(data, (chat) => chat.unread));

View File

@ -2,7 +2,6 @@ import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query';
import { useApi } from 'soapbox/hooks/useApi.ts'; import { useApi } from 'soapbox/hooks/useApi.ts';
import { Account } from 'soapbox/types/entities.ts'; import { Account } from 'soapbox/types/entities.ts';
import { getPagination } from 'soapbox/utils/pagination.ts';
import { flattenPages, PaginatedResult } from 'soapbox/utils/queries.ts'; import { flattenPages, PaginatedResult } from 'soapbox/utils/queries.ts';
export default function useAccountSearch(q: string) { export default function useAccountSearch(q: string) {
@ -21,7 +20,7 @@ export default function useAccountSearch(q: string) {
}); });
const data = await response.json(); const data = await response.json();
const { next } = getPagination(response); const { next } = response.pagination();
const hasMore = !!next; const hasMore = !!next;
return { return {

View File

@ -4,7 +4,6 @@ import { fetchRelationships } from 'soapbox/actions/accounts.ts';
import { importFetchedAccounts } from 'soapbox/actions/importer/index.ts'; import { importFetchedAccounts } from 'soapbox/actions/importer/index.ts';
import { useApi } from 'soapbox/hooks/useApi.ts'; import { useApi } from 'soapbox/hooks/useApi.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { getPagination } from 'soapbox/utils/pagination.ts';
import { PaginatedResult, removePageItem } from '../utils/queries.ts'; import { PaginatedResult, removePageItem } from '../utils/queries.ts';
@ -34,7 +33,7 @@ const useSuggestions = () => {
const getV2Suggestions = async (pageParam: PageParam): Promise<PaginatedResult<Result>> => { const getV2Suggestions = async (pageParam: PageParam): Promise<PaginatedResult<Result>> => {
const endpoint = pageParam?.link || '/api/v2/suggestions'; const endpoint = pageParam?.link || '/api/v2/suggestions';
const response = await api.get(endpoint); const response = await api.get(endpoint);
const { next } = getPagination(response); const { next } = response.pagination();
const hasMore = !!next; const hasMore = !!next;
const data: Suggestion[] = await response.json(); const data: Suggestion[] = await response.json();
@ -93,7 +92,7 @@ function useOnboardingSuggestions() {
const getV2Suggestions = async (pageParam: any): Promise<{ data: Suggestion[]; link: string | undefined; hasMore: boolean }> => { const getV2Suggestions = async (pageParam: any): Promise<{ data: Suggestion[]; link: string | undefined; hasMore: boolean }> => {
const link = pageParam?.link || '/api/v2/suggestions'; const link = pageParam?.link || '/api/v2/suggestions';
const response = await api.get(link); const response = await api.get(link);
const { next } = getPagination(response); const { next } = response.pagination();
const hasMore = !!next; const hasMore = !!next;
const data: Suggestion[] = await response.json(); const data: Suggestion[] = await response.json();

View File

@ -1,16 +0,0 @@
import LinkHeader from 'http-link-header';
interface Pagination {
next?: string;
prev?: string;
}
export function getPagination(response: Response): Pagination {
const header = response.headers.get('link');
const links = header ? new LinkHeader(header) : undefined;
return {
next: links?.refs.find((link) => link.rel === 'next')?.uri,
prev: links?.refs.find((link) => link.rel === 'prev')?.uri,
};
}