From 5df511a784ebe61ee3a9b97b1ee52396ad4560ea Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 8 Oct 2024 21:38:58 -0500 Subject: [PATCH 1/3] Switch to ky --- package.json | 1 + src/hooks/useClient.ts | 20 ++++++++++++++++++++ yarn.lock | 5 +++++ 3 files changed, 26 insertions(+) create mode 100644 src/hooks/useClient.ts diff --git a/package.json b/package.json index aeec5ff4d..1475b0202 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "intl-messageformat": "10.5.11", "intl-pluralrules": "^2.0.0", "isomorphic-dompurify": "^2.3.0", + "ky": "^1.7.2", "leaflet": "^1.8.0", "lexical": "^0.18.0", "line-awesome": "^1.3.0", diff --git a/src/hooks/useClient.ts b/src/hooks/useClient.ts new file mode 100644 index 000000000..e81023bc5 --- /dev/null +++ b/src/hooks/useClient.ts @@ -0,0 +1,20 @@ +import ky, { KyInstance } from 'ky'; + +import { useAppSelector } from './useAppSelector'; +import { useOwnAccount } from './useOwnAccount'; + +export function useClient(): KyInstance { + const { account } = useOwnAccount(); + const accessToken = useAppSelector((state) => account ? state.auth.users.get(account.url)?.access_token : undefined); + + const headers: Record = {}; + + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}`; + } + + return ky.create({ + prefixUrl: account ? new URL(account.url).origin : undefined, + headers, + }); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e59e33255..677cc3216 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5938,6 +5938,11 @@ known-css-properties@^0.29.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f" integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== +ky@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/ky/-/ky-1.7.2.tgz#b97d9b997ba51ff1e152f0815d3d27b86513eb1c" + integrity sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg== + kysely@^0.27.3: version "0.27.3" resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276" From 66075ee28a822519c60a7b985c3d86bca4033613 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 9 Oct 2024 01:46:46 -0500 Subject: [PATCH 2/3] Update useApi hook to use ky --- src/api/hooks/accounts/useAccount.ts | 3 +- src/api/hooks/accounts/useAccountLookup.ts | 3 +- src/api/hooks/accounts/useFollow.ts | 7 +- src/api/hooks/accounts/usePatronUser.ts | 2 +- src/api/hooks/accounts/useRelationships.ts | 3 +- src/api/hooks/admin/useAnnouncements.ts | 24 +++---- src/api/hooks/admin/useCreateDomain.ts | 2 +- src/api/hooks/admin/useDomains.ts | 24 +++---- src/api/hooks/admin/useManageZapSplit.ts | 4 +- src/api/hooks/admin/useModerationLog.ts | 2 +- src/api/hooks/admin/useRelays.ts | 16 ++--- src/api/hooks/admin/useRules.ts | 24 +++---- src/api/hooks/admin/useSuggest.ts | 4 +- src/api/hooks/admin/useUpdateDomain.ts | 2 +- src/api/hooks/admin/useVerify.ts | 4 +- .../hooks/announcements/useAnnouncements.ts | 2 +- src/api/hooks/groups/useCreateGroup.ts | 3 +- .../groups/useGroupMembershipRequests.ts | 2 +- src/api/hooks/groups/useGroupSearch.ts | 2 +- src/api/hooks/groups/useGroupValidation.ts | 24 +++---- src/api/hooks/groups/useGroups.ts | 2 +- src/api/hooks/groups/usePendingGroups.ts | 2 +- src/api/hooks/groups/useUpdateGroup.ts | 3 +- .../hooks/statuses/useCreateBookmarkFolder.ts | 3 +- .../hooks/statuses/useUpdateBookmarkFolder.ts | 3 +- src/api/hooks/zap-split/useZapSplit.ts | 10 +-- src/entity-store/hooks/types.ts | 3 +- src/entity-store/hooks/useBatchedEntities.ts | 3 +- src/entity-store/hooks/useCreateEntity.ts | 8 +-- src/entity-store/hooks/useEntities.ts | 13 ++-- src/entity-store/hooks/useEntity.ts | 9 +-- src/entity-store/hooks/useEntityActions.ts | 4 +- src/entity-store/hooks/useEntityLookup.ts | 9 +-- src/features/account/components/header.tsx | 5 +- .../admin/hooks/useAdminNostrRelays.ts | 2 +- src/features/admin/nostr-relays.tsx | 2 +- .../components/chat-search/chat-search.tsx | 8 ++- src/features/edit-identity/index.tsx | 6 +- .../group/components/group-action-button.tsx | 4 +- src/features/group/edit-group.tsx | 6 +- .../manage-group-modal/create-group-modal.tsx | 8 +-- src/hooks/useApi.ts | 25 +++++-- src/hooks/useClient.ts | 20 ------ src/queries/accounts.ts | 6 +- src/queries/chats.ts | 65 +++++++++++-------- src/queries/embed.ts | 5 +- src/queries/relationships.ts | 4 +- src/queries/search.ts | 24 +++---- src/queries/suggestions.ts | 24 +++---- src/queries/trends.ts | 2 +- src/utils/pagination.ts | 16 +++++ 51 files changed, 247 insertions(+), 214 deletions(-) delete mode 100644 src/hooks/useClient.ts create mode 100644 src/utils/pagination.ts diff --git a/src/api/hooks/accounts/useAccount.ts b/src/api/hooks/accounts/useAccount.ts index 865b149ad..907337b8b 100644 --- a/src/api/hooks/accounts/useAccount.ts +++ b/src/api/hooks/accounts/useAccount.ts @@ -3,8 +3,7 @@ import { useHistory } from 'react-router-dom'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useFeatures, useLoggedIn } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi, useFeatures, useLoggedIn } from 'soapbox/hooks'; import { type Account, accountSchema } from 'soapbox/schemas'; import { useRelationship } from './useRelationship'; diff --git a/src/api/hooks/accounts/useAccountLookup.ts b/src/api/hooks/accounts/useAccountLookup.ts index 7aed05f98..321c8dcc1 100644 --- a/src/api/hooks/accounts/useAccountLookup.ts +++ b/src/api/hooks/accounts/useAccountLookup.ts @@ -3,8 +3,7 @@ import { useHistory } from 'react-router-dom'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntityLookup } from 'soapbox/entity-store/hooks'; -import { useFeatures, useLoggedIn } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi, useFeatures, useLoggedIn } from 'soapbox/hooks'; import { type Account, accountSchema } from 'soapbox/schemas'; import { useRelationship } from './useRelationship'; diff --git a/src/api/hooks/accounts/useFollow.ts b/src/api/hooks/accounts/useFollow.ts index 60282c4ac..a3d43905a 100644 --- a/src/api/hooks/accounts/useFollow.ts +++ b/src/api/hooks/accounts/useFollow.ts @@ -1,8 +1,7 @@ import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { useTransaction } from 'soapbox/entity-store/hooks'; -import { useAppDispatch, useLoggedIn } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi, useAppDispatch, useLoggedIn } from 'soapbox/hooks'; import { relationshipSchema } from 'soapbox/schemas'; interface FollowOpts { @@ -56,8 +55,8 @@ function useFollow() { followEffect(accountId); try { - const response = await api.post(`/api/v1/accounts/${accountId}/follow`, options); - const result = relationshipSchema.safeParse(response.data); + const response = await api.post(`/api/v1/accounts/${accountId}/follow`, { json: options }); + const result = relationshipSchema.safeParse(await response.json()); if (result.success) { dispatch(importEntities([result.data], Entities.RELATIONSHIPS)); } diff --git a/src/api/hooks/accounts/usePatronUser.ts b/src/api/hooks/accounts/usePatronUser.ts index aa78ecd39..27ff6edfe 100644 --- a/src/api/hooks/accounts/usePatronUser.ts +++ b/src/api/hooks/accounts/usePatronUser.ts @@ -1,6 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi } from 'soapbox/hooks'; import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig'; import { type PatronUser, patronUserSchema } from 'soapbox/schemas'; diff --git a/src/api/hooks/accounts/useRelationships.ts b/src/api/hooks/accounts/useRelationships.ts index 49a03f5eb..8ffdb46ef 100644 --- a/src/api/hooks/accounts/useRelationships.ts +++ b/src/api/hooks/accounts/useRelationships.ts @@ -1,7 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities'; -import { useLoggedIn } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi, useLoggedIn } from 'soapbox/hooks'; import { type Relationship, relationshipSchema } from 'soapbox/schemas'; function useRelationships(listKey: string[], ids: string[]) { diff --git a/src/api/hooks/admin/useAnnouncements.ts b/src/api/hooks/admin/useAnnouncements.ts index 5cbf70e60..706dd1595 100644 --- a/src/api/hooks/admin/useAnnouncements.ts +++ b/src/api/hooks/admin/useAnnouncements.ts @@ -6,8 +6,6 @@ import { adminAnnouncementSchema, type AdminAnnouncement } from 'soapbox/schemas import { useAnnouncements as useUserAnnouncements } from '../announcements'; -import type { AxiosResponse } from 'axios'; - interface CreateAnnouncementParams { content: string; starts_at?: string | null; @@ -24,7 +22,7 @@ const useAnnouncements = () => { const userAnnouncements = useUserAnnouncements(); const getAnnouncements = async () => { - const { data } = await api.get('/api/v1/pleroma/admin/announcements'); + const data = await api.get('/api/v1/pleroma/admin/announcements').json(); const normalizedData = data.map((announcement) => adminAnnouncementSchema.parse(announcement)); return normalizedData; @@ -40,12 +38,14 @@ const useAnnouncements = () => { mutate: createAnnouncement, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', params), + mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => [...prevResult, adminAnnouncementSchema.parse(data)], - ), + ); + }, onSettled: () => userAnnouncements.refetch(), }); @@ -53,12 +53,14 @@ const useAnnouncements = () => { mutate: updateAnnouncement, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, params), + mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => prevResult.map((announcement) => announcement.id === data.id ? adminAnnouncementSchema.parse(data) : announcement), - ), + ); + }, onSettled: () => userAnnouncements.refetch(), }); diff --git a/src/api/hooks/admin/useCreateDomain.ts b/src/api/hooks/admin/useCreateDomain.ts index 5897fda19..a4b86fba0 100644 --- a/src/api/hooks/admin/useCreateDomain.ts +++ b/src/api/hooks/admin/useCreateDomain.ts @@ -12,7 +12,7 @@ const useCreateDomain = () => { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: CreateDomainParams) => - api.post('/api/v1/pleroma/admin/domains', params), { schema: domainSchema }); + api.post('/api/v1/pleroma/admin/domains', { json: params }), { schema: domainSchema }); return { createDomain: createEntity, diff --git a/src/api/hooks/admin/useDomains.ts b/src/api/hooks/admin/useDomains.ts index aed6afe7c..28fdd8a98 100644 --- a/src/api/hooks/admin/useDomains.ts +++ b/src/api/hooks/admin/useDomains.ts @@ -4,8 +4,6 @@ import { useApi } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { domainSchema, type Domain } from 'soapbox/schemas'; -import type { AxiosResponse } from 'axios'; - interface CreateDomainParams { domain: string; public: boolean; @@ -20,7 +18,7 @@ const useDomains = () => { const api = useApi(); const getDomains = async () => { - const { data } = await api.get('/api/v1/pleroma/admin/domains'); + const data = await api.get('/api/v1/pleroma/admin/domains').json(); const normalizedData = data.map((domain) => domainSchema.parse(domain)); return normalizedData; @@ -36,24 +34,28 @@ const useDomains = () => { mutate: createDomain, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', params), + mutationFn: (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray) => [...prevResult, domainSchema.parse(data)], - ), + ); + }, }); const { mutate: updateDomain, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateDomainParams) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, params), + mutationFn: ({ id, ...params }: UpdateDomainParams) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray) => prevResult.map((domain) => domain.id === data.id ? domainSchema.parse(data) : domain), - ), + ); + }, }); const { diff --git a/src/api/hooks/admin/useManageZapSplit.ts b/src/api/hooks/admin/useManageZapSplit.ts index fecb820b4..615c818ed 100644 --- a/src/api/hooks/admin/useManageZapSplit.ts +++ b/src/api/hooks/admin/useManageZapSplit.ts @@ -32,7 +32,7 @@ export const useManageZapSplit = () => { */ const fetchZapSplitData = async () => { try { - const { data } = await api.get('/api/v1/ditto/zap_splits'); + const data = await api.get('/api/v1/ditto/zap_splits').json(); if (data) { const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit)); setFormattedData(normalizedData); @@ -134,7 +134,7 @@ export const useManageZapSplit = () => { const removeAccount = async (accountId: string) => { const isToDelete = [(formattedData.find(item => item.account.id === accountId))?.account.id]; - await api.delete('/api/v1/admin/ditto/zap_splits/', { data: isToDelete }); + await api.delete('/api/v1/admin/ditto/zap_splits/', { json: isToDelete }); await fetchZapSplitData(); }; diff --git a/src/api/hooks/admin/useModerationLog.ts b/src/api/hooks/admin/useModerationLog.ts index e87d0b1c3..5c38a66eb 100644 --- a/src/api/hooks/admin/useModerationLog.ts +++ b/src/api/hooks/admin/useModerationLog.ts @@ -14,7 +14,7 @@ const useModerationLog = () => { const api = useApi(); const getModerationLog = async (page: number): Promise => { - const { data } = await api.get('/api/v1/pleroma/admin/moderation_log', { params: { page } }); + const data = await api.get('/api/v1/pleroma/admin/moderation_log', { searchParams: { page } }).json(); const normalizedData = data.items.map((domain) => moderationLogEntrySchema.parse(domain)); diff --git a/src/api/hooks/admin/useRelays.ts b/src/api/hooks/admin/useRelays.ts index ff1d1ecce..ff6383e48 100644 --- a/src/api/hooks/admin/useRelays.ts +++ b/src/api/hooks/admin/useRelays.ts @@ -4,13 +4,11 @@ import { useApi } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { relaySchema, type Relay } from 'soapbox/schemas'; -import type { AxiosResponse } from 'axios'; - const useRelays = () => { const api = useApi(); const getRelays = async () => { - const { data } = await api.get<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay'); + const data = await api.get<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay').json(); const normalizedData = data.relays?.map((relay) => relaySchema.parse(relay)); return normalizedData; @@ -26,12 +24,14 @@ const useRelays = () => { mutate: followRelay, isPending: isPendingFollow, } = useMutation({ - mutationFn: (relayUrl: string) => api.post('/api/v1/pleroma/admin/relays', { relay_url: relayUrl }), + mutationFn: (relayUrl: string) => api.post('/api/v1/pleroma/admin/relays', { json: { relay_url: relayUrl } }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray) => [...prevResult, relaySchema.parse(data)], - ), + ); + }, }); const { @@ -39,7 +39,7 @@ const useRelays = () => { isPending: isPendingUnfollow, } = useMutation({ mutationFn: (relayUrl: string) => api.delete('/api/v1/pleroma/admin/relays', { - data: { relay_url: relayUrl }, + json: { relay_url: relayUrl }, }), retry: false, onSuccess: (_, relayUrl) => diff --git a/src/api/hooks/admin/useRules.ts b/src/api/hooks/admin/useRules.ts index 87971e8fb..b1fbd6154 100644 --- a/src/api/hooks/admin/useRules.ts +++ b/src/api/hooks/admin/useRules.ts @@ -4,8 +4,6 @@ import { useApi } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { adminRuleSchema, type AdminRule } from 'soapbox/schemas'; -import type { AxiosResponse } from 'axios'; - interface CreateRuleParams { priority?: number; text: string; @@ -23,7 +21,7 @@ const useRules = () => { const api = useApi(); const getRules = async () => { - const { data } = await api.get('/api/v1/pleroma/admin/rules'); + const data = await api.get('/api/v1/pleroma/admin/rules').json(); const normalizedData = data.map((rule) => adminRuleSchema.parse(rule)); return normalizedData; @@ -39,24 +37,28 @@ const useRules = () => { mutate: createRule, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateRuleParams) => api.post('/api/v1/pleroma/admin/rules', params), + mutationFn: (params: CreateRuleParams) => api.post('/api/v1/pleroma/admin/rules', { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray) => [...prevResult, adminRuleSchema.parse(data)], - ), + ); + }, }); const { mutate: updateRule, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateRuleParams) => api.patch(`/api/v1/pleroma/admin/rules/${id}`, params), + mutationFn: ({ id, ...params }: UpdateRuleParams) => api.patch(`/api/v1/pleroma/admin/rules/${id}`, { json: params }), retry: false, - onSuccess: ({ data }: AxiosResponse) => - queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray) => + onSuccess: async (response: Response) => { + const data = await response.json(); + return queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray) => prevResult.map((rule) => rule.id === data.id ? adminRuleSchema.parse(data) : rule), - ), + ); + }, }); const { diff --git a/src/api/hooks/admin/useSuggest.ts b/src/api/hooks/admin/useSuggest.ts index b20bc5308..82aa3adcf 100644 --- a/src/api/hooks/admin/useSuggest.ts +++ b/src/api/hooks/admin/useSuggest.ts @@ -29,7 +29,7 @@ function useSuggest() { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, true); try { - await api.patch('/api/v1/pleroma/admin/users/suggest', { nicknames: accts }); + await api.patch('/api/v1/pleroma/admin/users/suggest', { json: { nicknames: accts } }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); @@ -41,7 +41,7 @@ function useSuggest() { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, false); try { - await api.patch('/api/v1/pleroma/admin/users/unsuggest', { nicknames: accts }); + await api.patch('/api/v1/pleroma/admin/users/unsuggest', { json: { nicknames: accts } }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); diff --git a/src/api/hooks/admin/useUpdateDomain.ts b/src/api/hooks/admin/useUpdateDomain.ts index 8ca27ba97..f278441fb 100644 --- a/src/api/hooks/admin/useUpdateDomain.ts +++ b/src/api/hooks/admin/useUpdateDomain.ts @@ -9,7 +9,7 @@ const useUpdateDomain = (id: string) => { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: Omit) => - api.patch(`/api/v1/pleroma/admin/domains/${id}`, params), { schema: domainSchema }); + api.patch(`/api/v1/pleroma/admin/domains/${id}`, { json: params }), { schema: domainSchema }); return { updateDomain: createEntity, diff --git a/src/api/hooks/admin/useVerify.ts b/src/api/hooks/admin/useVerify.ts index 090e1bc43..ed4141082 100644 --- a/src/api/hooks/admin/useVerify.ts +++ b/src/api/hooks/admin/useVerify.ts @@ -34,7 +34,7 @@ function useVerify() { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, true); try { - await api.put('/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] }); + await api.put('/api/v1/pleroma/admin/users/tag', { json: { nicknames: accts, tags: ['verified'] } }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); @@ -46,7 +46,7 @@ function useVerify() { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, false); try { - await api.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames: accts, tags: ['verified'] } }); + await api.delete('/api/v1/pleroma/admin/users/tag', { json: { nicknames: accts, tags: ['verified'] } }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); diff --git a/src/api/hooks/announcements/useAnnouncements.ts b/src/api/hooks/announcements/useAnnouncements.ts index d8f4f0476..d5b24dec7 100644 --- a/src/api/hooks/announcements/useAnnouncements.ts +++ b/src/api/hooks/announcements/useAnnouncements.ts @@ -24,7 +24,7 @@ const useAnnouncements = () => { const api = useApi(); const getAnnouncements = async () => { - const { data } = await api.get('/api/v1/announcements'); + const data = await api.get('/api/v1/announcements').json(); const normalizedData = data?.map((announcement) => announcementSchema.parse(announcement)); return normalizedData; diff --git a/src/api/hooks/groups/useCreateGroup.ts b/src/api/hooks/groups/useCreateGroup.ts index e80417856..1d3a18107 100644 --- a/src/api/hooks/groups/useCreateGroup.ts +++ b/src/api/hooks/groups/useCreateGroup.ts @@ -17,7 +17,8 @@ function useCreateGroup() { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS, 'search', ''], (params: CreateGroupParams) => { - return api.post('/api/v1/groups', params, { + return api.post('/api/v1/groups', { + json: params, headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/groups/useGroupMembershipRequests.ts b/src/api/hooks/groups/useGroupMembershipRequests.ts index 64ab26d7c..f175b21da 100644 --- a/src/api/hooks/groups/useGroupMembershipRequests.ts +++ b/src/api/hooks/groups/useGroupMembershipRequests.ts @@ -1,6 +1,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useDismissEntity, useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; +import { useApi } from 'soapbox/hooks'; import { accountSchema } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; diff --git a/src/api/hooks/groups/useGroupSearch.ts b/src/api/hooks/groups/useGroupSearch.ts index 295d63933..946e208bb 100644 --- a/src/api/hooks/groups/useGroupSearch.ts +++ b/src/api/hooks/groups/useGroupSearch.ts @@ -14,7 +14,7 @@ function useGroupSearch(search: string) { const { entities, ...result } = useEntities( [Entities.GROUPS, 'discover', 'search', search], () => api.get('/api/v1/groups/search', { - params: { + searchParams: { q: search, }, }), diff --git a/src/api/hooks/groups/useGroupValidation.ts b/src/api/hooks/groups/useGroupValidation.ts index 470be22a5..f0e69ef6c 100644 --- a/src/api/hooks/groups/useGroupValidation.ts +++ b/src/api/hooks/groups/useGroupValidation.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; +import { HTTPError } from 'ky'; import { useApi } from 'soapbox/hooks/useApi'; import { useFeatures } from 'soapbox/hooks/useFeatures'; @@ -16,19 +17,18 @@ function useGroupValidation(name: string = '') { const api = useApi(); const features = useFeatures(); - const getValidation = async() => { - const { data } = await api.get('/api/v1/groups/validate', { - params: { name }, - }) - .catch((error) => { - if (error.response.status === 422) { - return { data: error.response.data }; - } + const getValidation = async () => { + try { + return api.get('/api/v1/groups/validate', { + searchParams: { name }, + }).json(); + } catch (e) { + if (e instanceof HTTPError && e.response.status === 422) { + return e.response.json(); + } - throw error; - }); - - return data; + throw e; + } }; const queryInfo = useQuery({ diff --git a/src/api/hooks/groups/useGroups.ts b/src/api/hooks/groups/useGroups.ts index f5450bd73..eb5b4161f 100644 --- a/src/api/hooks/groups/useGroups.ts +++ b/src/api/hooks/groups/useGroups.ts @@ -12,7 +12,7 @@ function useGroups(q: string = '') { const { entities, ...result } = useEntities( [Entities.GROUPS, 'search', q], - () => api.get('/api/v1/groups', { params: { q } }), + () => api.get('/api/v1/groups', { searchParams: { q } }), { enabled: features.groups, schema: groupSchema }, ); const { relationships } = useGroupRelationships( diff --git a/src/api/hooks/groups/usePendingGroups.ts b/src/api/hooks/groups/usePendingGroups.ts index f4ea16a43..be5d8c400 100644 --- a/src/api/hooks/groups/usePendingGroups.ts +++ b/src/api/hooks/groups/usePendingGroups.ts @@ -11,7 +11,7 @@ function usePendingGroups() { const { entities, ...result } = useEntities( [Entities.GROUPS, account?.id!, 'pending'], () => api.get('/api/v1/groups', { - params: { + searchParams: { pending: true, }, }), diff --git a/src/api/hooks/groups/useUpdateGroup.ts b/src/api/hooks/groups/useUpdateGroup.ts index 129849514..ef6539167 100644 --- a/src/api/hooks/groups/useUpdateGroup.ts +++ b/src/api/hooks/groups/useUpdateGroup.ts @@ -17,7 +17,8 @@ function useUpdateGroup(groupId: string) { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS], (params: UpdateGroupParams) => { - return api.put(`/api/v1/groups/${groupId}`, params, { + return api.put(`/api/v1/groups/${groupId}`, { + json: params, headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/statuses/useCreateBookmarkFolder.ts b/src/api/hooks/statuses/useCreateBookmarkFolder.ts index ded24ff97..41ae6317d 100644 --- a/src/api/hooks/statuses/useCreateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useCreateBookmarkFolder.ts @@ -14,7 +14,8 @@ function useCreateBookmarkFolder() { const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], (params: CreateBookmarkFolderParams) => - api.post('/api/v1/pleroma/bookmark_folders', params, { + api.post('/api/v1/pleroma/bookmark_folders', { + json: params, headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts index c27dd089e..109ef2952 100644 --- a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts @@ -14,7 +14,8 @@ function useUpdateBookmarkFolder(folderId: string) { const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], (params: UpdateBookmarkFolderParams) => - api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, params, { + api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, { + json: params, headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/zap-split/useZapSplit.ts b/src/api/hooks/zap-split/useZapSplit.ts index a5cb5717d..5109fefa1 100644 --- a/src/api/hooks/zap-split/useZapSplit.ts +++ b/src/api/hooks/zap-split/useZapSplit.ts @@ -30,15 +30,15 @@ const useZapSplit = (status: StatusEntity | undefined, account: AccountEntity) = const [zapArrays, setZapArrays] = useState([]); const [zapSplitData, setZapSplitData] = useState<{splitAmount: number; receiveAmount: number; splitValues: SplitValue[]}>({ splitAmount: Number(), receiveAmount: Number(), splitValues: [] }); - const fetchZapSplit = async (id: string) => { - return await api.get(`/api/v1/ditto/${id}/zap_splits`); + const fetchZapSplit = (id: string) => { + return api.get(`/api/v1/ditto/${id}/zap_splits`); }; const loadZapSplitData = async () => { if (status) { - const data = (await fetchZapSplit(status.id)).data; + const data = await fetchZapSplit(status.id).json(); if (data) { - const normalizedData = data.map((dataSplit: ZapSplitData) => baseZapAccountSchema.parse(dataSplit)); + const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit)); setZapArrays(normalizedData); } } @@ -53,7 +53,7 @@ const useZapSplit = (status: StatusEntity | undefined, account: AccountEntity) = const receiveAmount = (zapAmount: number) => { if (zapArrays.length > 0) { const zapAmountPrincipal = zapArrays.find((zapSplit: ZapSplitData) => zapSplit.account.id === account.id); - const formattedZapAmountPrincipal = { + const formattedZapAmountPrincipal = { account: zapAmountPrincipal?.account, message: zapAmountPrincipal?.message, weight: zapArrays.filter((zapSplit: ZapSplitData) => zapSplit.account.id === account.id).reduce((acc:number, zapData: ZapSplitData) => acc + zapData.weight, 0), diff --git a/src/entity-store/hooks/types.ts b/src/entity-store/hooks/types.ts index 42b98b730..b3ec5937a 100644 --- a/src/entity-store/hooks/types.ts +++ b/src/entity-store/hooks/types.ts @@ -1,5 +1,4 @@ import type { Entity } from '../types'; -import type { AxiosResponse } from 'axios'; import type z from 'zod'; type EntitySchema = z.ZodType; @@ -35,7 +34,7 @@ interface EntityCallbacks { * Passed into hooks to make requests. * Must return an Axios response. */ -type EntityFn = (value: T) => Promise +type EntityFn = (value: T) => Promise export type { EntitySchema, diff --git a/src/entity-store/hooks/useBatchedEntities.ts b/src/entity-store/hooks/useBatchedEntities.ts index baff199a9..f6b0d9437 100644 --- a/src/entity-store/hooks/useBatchedEntities.ts +++ b/src/entity-store/hooks/useBatchedEntities.ts @@ -54,7 +54,8 @@ function useBatchedEntities( dispatch(entitiesFetchRequest(entityType, listKey)); try { const response = await entityFn(filteredIds); - const entities = filteredArray(schema).parse(response.data); + const json = await response.json(); + const entities = filteredArray(schema).parse(json); dispatch(entitiesFetchSuccess(entities, entityType, listKey, 'end', { next: undefined, prev: undefined, diff --git a/src/entity-store/hooks/useCreateEntity.ts b/src/entity-store/hooks/useCreateEntity.ts index 4607706cd..52abce119 100644 --- a/src/entity-store/hooks/useCreateEntity.ts +++ b/src/entity-store/hooks/useCreateEntity.ts @@ -1,4 +1,4 @@ -import { AxiosError } from 'axios'; +import { HTTPError } from 'ky'; import { z } from 'zod'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; @@ -25,11 +25,11 @@ function useCreateEntity( const [isSubmitting, setPromise] = useLoading(); const { entityType, listKey } = parseEntitiesPath(expandedPath); - async function createEntity(data: Data, callbacks: EntityCallbacks = {}): Promise { + async function createEntity(data: Data, callbacks: EntityCallbacks = {}): Promise { try { const result = await setPromise(entityFn(data)); const schema = opts.schema || z.custom(); - const entity = schema.parse(result.data); + const entity = schema.parse(await result.json()); // TODO: optimistic updating dispatch(importEntities([entity], entityType, listKey, 'start')); @@ -38,7 +38,7 @@ function useCreateEntity( callbacks.onSuccess(entity); } } catch (error) { - if (error instanceof AxiosError) { + if (error instanceof HTTPError) { if (callbacks.onError) { callbacks.onError(error); } diff --git a/src/entity-store/hooks/useEntities.ts b/src/entity-store/hooks/useEntities.ts index ecdc93068..45c65eb93 100644 --- a/src/entity-store/hooks/useEntities.ts +++ b/src/entity-store/hooks/useEntities.ts @@ -1,7 +1,7 @@ +import LinkHeader from 'http-link-header'; import { useEffect } from 'react'; import z from 'zod'; -import { getNextLink, getPrevLink } from 'soapbox/api'; import { useApi } from 'soapbox/hooks/useApi'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; import { useAppSelector } from 'soapbox/hooks/useAppSelector'; @@ -66,13 +66,16 @@ function useEntities( dispatch(entitiesFetchRequest(entityType, listKey)); try { const response = await req(); - const entities = filteredArray(schema).parse(response.data); - const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']); + const json = await response.json(); + const entities = filteredArray(schema).parse(json); + const parsedCount = realNumberSchema.safeParse(response.headers.get('x-total-count')); const totalCount = parsedCount.success ? parsedCount.data : undefined; + const linkHeader = response.headers.get('link'); + const links = linkHeader ? new LinkHeader(linkHeader) : undefined; dispatch(entitiesFetchSuccess(entities, entityType, listKey, pos, { - next: getNextLink(response), - prev: getPrevLink(response), + next: links?.refs.find((link) => link.rel === 'next')?.uri, + prev: links?.refs.find((link) => link.rel === 'prev')?.uri, totalCount: Number(totalCount) >= entities.length ? totalCount : undefined, fetching: false, fetched: true, diff --git a/src/entity-store/hooks/useEntity.ts b/src/entity-store/hooks/useEntity.ts index 65d143f0a..55c7821d2 100644 --- a/src/entity-store/hooks/useEntity.ts +++ b/src/entity-store/hooks/useEntity.ts @@ -1,4 +1,4 @@ -import { AxiosError } from 'axios'; +import { HTTPError } from 'ky'; import { useEffect, useState } from 'react'; import z from 'zod'; @@ -46,7 +46,8 @@ function useEntity( const fetchEntity = async () => { try { const response = await setPromise(entityFn()); - const entity = schema.parse(response.data); + const json = await response.json(); + const entity = schema.parse(json); dispatch(importEntities([entity], entityType)); } catch (e) { setError(e); @@ -67,8 +68,8 @@ function useEntity( isLoading, isLoaded, error, - isUnauthorized: error instanceof AxiosError && error.response?.status === 401, - isForbidden: error instanceof AxiosError && error.response?.status === 403, + isUnauthorized: error instanceof HTTPError && error.response?.status === 401, + isForbidden: error instanceof HTTPError && error.response?.status === 403, }; } diff --git a/src/entity-store/hooks/useEntityActions.ts b/src/entity-store/hooks/useEntityActions.ts index b038946f3..48f837c17 100644 --- a/src/entity-store/hooks/useEntityActions.ts +++ b/src/entity-store/hooks/useEntityActions.ts @@ -29,10 +29,10 @@ function useEntityActions( useDeleteEntity(entityType, (entityId) => api.delete(endpoints.delete!.replace(/:id/g, entityId))); const { createEntity, isSubmitting: createSubmitting } = - useCreateEntity(path, (data) => api.post(endpoints.post!, data), opts); + useCreateEntity(path, (data) => api.post(endpoints.post!, { json: data }), opts); const { createEntity: updateEntity, isSubmitting: updateSubmitting } = - useCreateEntity(path, (data) => api.patch(endpoints.patch!, data), opts); + useCreateEntity(path, (data) => api.patch(endpoints.patch!, { json: data }), opts); return { createEntity, diff --git a/src/entity-store/hooks/useEntityLookup.ts b/src/entity-store/hooks/useEntityLookup.ts index 3ae97b704..dc0c5d791 100644 --- a/src/entity-store/hooks/useEntityLookup.ts +++ b/src/entity-store/hooks/useEntityLookup.ts @@ -1,4 +1,4 @@ -import { AxiosError } from 'axios'; +import { HTTPError } from 'ky'; import { useEffect, useState } from 'react'; import { z } from 'zod'; @@ -36,7 +36,8 @@ function useEntityLookup( const fetchEntity = async () => { try { const response = await setPromise(entityFn()); - const entity = schema.parse(response.data); + const json = await response.json(); + const entity = schema.parse(json); setFetchedEntity(entity); dispatch(importEntities([entity], entityType)); } catch (e) { @@ -57,8 +58,8 @@ function useEntityLookup( fetchEntity, isFetching, isLoading, - isUnauthorized: error instanceof AxiosError && error.response?.status === 401, - isForbidden: error instanceof AxiosError && error.response?.status === 403, + isUnauthorized: error instanceof HTTPError && error.response?.status === 401, + isForbidden: error instanceof HTTPError && error.response?.status === 403, }; } diff --git a/src/features/account/components/header.tsx b/src/features/account/components/header.tsx index c5e361f6c..273d1f787 100644 --- a/src/features/account/components/header.tsx +++ b/src/features/account/components/header.tsx @@ -101,8 +101,9 @@ const Header: React.FC = ({ account }) => { const data = error.response?.data as any; toast.error(data?.error); }, - onSuccess: (response) => { - history.push(`/chats/${response.data.id}`); + onSuccess: async (response) => { + const data = await response.json(); + history.push(`/chats/${data.id}`); queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch(), }); diff --git a/src/features/admin/hooks/useAdminNostrRelays.ts b/src/features/admin/hooks/useAdminNostrRelays.ts index b55255135..8a4970727 100644 --- a/src/features/admin/hooks/useAdminNostrRelays.ts +++ b/src/features/admin/hooks/useAdminNostrRelays.ts @@ -14,7 +14,7 @@ export function useAdminNostrRelays() { return useQuery({ queryKey: ['NostrRelay'], queryFn: async () => { - const { data } = await api.get('/api/v1/admin/ditto/relays'); + const data = await api.get('/api/v1/admin/ditto/relays').json(); return relayEntitySchema.array().parse(data); }, }); diff --git a/src/features/admin/nostr-relays.tsx b/src/features/admin/nostr-relays.tsx index 40c7d719e..e97fa4d3b 100644 --- a/src/features/admin/nostr-relays.tsx +++ b/src/features/admin/nostr-relays.tsx @@ -20,7 +20,7 @@ const AdminNostrRelays: React.FC = () => { const [relays, setRelays] = useState(result.data ?? []); const mutation = useMutation({ - mutationFn: async () => api.put('/api/v1/admin/ditto/relays', relays), + mutationFn: async () => api.put('/api/v1/admin/ditto/relays', { json: relays }), }); const handleSubmit = () => { diff --git a/src/features/chats/components/chat-search/chat-search.tsx b/src/features/chats/components/chat-search/chat-search.tsx index 094e39d63..780b7303c 100644 --- a/src/features/chats/components/chat-search/chat-search.tsx +++ b/src/features/chats/components/chat-search/chat-search.tsx @@ -49,11 +49,13 @@ const ChatSearch = (props: IChatSearch) => { const data = error.response?.data as any; toast.error(data?.error); }, - onSuccess: (response) => { + onSuccess: async (response) => { + const data = await response.json(); + if (isMainPage) { - history.push(`/chats/${response.data.id}`); + history.push(`/chats/${data.id}`); } else { - changeScreen(ChatWidgetScreens.CHAT, response.data.id); + changeScreen(ChatWidgetScreens.CHAT, data.id); } queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() }); diff --git a/src/features/edit-identity/index.tsx b/src/features/edit-identity/index.tsx index 131dd23c7..814c526c1 100644 --- a/src/features/edit-identity/index.tsx +++ b/src/features/edit-identity/index.tsx @@ -187,7 +187,7 @@ function useRequestName() { const api = useApi(); return useMutation({ - mutationFn: (data: NameRequestData) => api.post('/api/v1/ditto/names', data), + mutationFn: (data: NameRequestData) => api.post('/api/v1/ditto/names', { json: data }), }); } @@ -197,7 +197,7 @@ function useNames() { return useQuery({ queryKey: ['names', 'approved'], queryFn: async () => { - const { data } = await api.get('/api/v1/ditto/names?approved=true'); + const data = await api.get('/api/v1/ditto/names?approved=true').json(); return adminAccountSchema.array().parse(data); }, placeholderData: [], @@ -210,7 +210,7 @@ function usePendingNames() { return useQuery({ queryKey: ['names', 'pending'], queryFn: async () => { - const { data } = await api.get('/api/v1/ditto/names?approved=false'); + const data = await api.get('/api/v1/ditto/names?approved=false').json(); return adminAccountSchema.array().parse(data); }, placeholderData: [], diff --git a/src/features/group/components/group-action-button.tsx b/src/features/group/components/group-action-button.tsx index ccc80db62..e19e3563f 100644 --- a/src/features/group/components/group-action-button.tsx +++ b/src/features/group/components/group-action-button.tsx @@ -53,8 +53,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => { : intl.formatMessage(messages.joinSuccess), ); }, - onError(error) { - const message = (error.response?.data as any).error; + async onError(error) { + const message = (await error.response.json() as any).error; if (message) { toast.error(message); } diff --git a/src/features/group/edit-group.tsx b/src/features/group/edit-group.tsx index aa80d7ce5..13f5e11b9 100644 --- a/src/features/group/edit-group.tsx +++ b/src/features/group/edit-group.tsx @@ -67,10 +67,10 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { invalidate(); toast.success(intl.formatMessage(messages.groupSaved)); }, - onError(error) { - const message = (error.response?.data as any)?.error; + async onError(error) { + const message = (await error.response.json() as any)?.error; - if (error.response?.status === 422 && typeof message !== 'undefined') { + if (error.response.status === 422 && message) { toast.error(message); } }, diff --git a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx index 11782047e..295ef612b 100644 --- a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx +++ b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx @@ -1,4 +1,4 @@ -import { AxiosError } from 'axios'; +import { HTTPError } from 'ky'; import React, { useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { z } from 'zod'; @@ -70,9 +70,9 @@ const CreateGroupModal: React.FC = ({ onClose }) => { setCurrentStep(Steps.THREE); setGroup(group); }, - onError(error) { - if (error instanceof AxiosError) { - const msg = z.object({ error: z.string() }).safeParse(error.response?.data); + async onError(error) { + if (error instanceof HTTPError) { + const msg = z.object({ error: z.string() }).safeParse(await error.response.json()); if (msg.success) { toast.error(msg.data.error); } diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts index 2e22df997..5117bffb6 100644 --- a/src/hooks/useApi.ts +++ b/src/hooks/useApi.ts @@ -1,9 +1,20 @@ -import api from 'soapbox/api'; +import ky, { KyInstance } from 'ky'; -import { useGetState } from './useGetState'; +import { useAppSelector } from './useAppSelector'; +import { useOwnAccount } from './useOwnAccount'; -/** Use stateful Axios client with auth from Redux. */ -export const useApi = () => { - const getState = useGetState(); - return api(getState); -}; +export function useApi(): KyInstance { + const { account } = useOwnAccount(); + const accessToken = useAppSelector((state) => account ? state.auth.users.get(account.url)?.access_token : undefined); + + const headers: Record = {}; + + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}`; + } + + return ky.create({ + prefixUrl: account ? new URL(account.url).origin : undefined, + headers, + }); +} \ No newline at end of file diff --git a/src/hooks/useClient.ts b/src/hooks/useClient.ts deleted file mode 100644 index e81023bc5..000000000 --- a/src/hooks/useClient.ts +++ /dev/null @@ -1,20 +0,0 @@ -import ky, { KyInstance } from 'ky'; - -import { useAppSelector } from './useAppSelector'; -import { useOwnAccount } from './useOwnAccount'; - -export function useClient(): KyInstance { - const { account } = useOwnAccount(); - const accessToken = useAppSelector((state) => account ? state.auth.users.get(account.url)?.access_token : undefined); - - const headers: Record = {}; - - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; - } - - return ky.create({ - prefixUrl: account ? new URL(account.url).origin : undefined, - headers, - }); -} \ No newline at end of file diff --git a/src/queries/accounts.ts b/src/queries/accounts.ts index 06b7cb514..77e40fb9f 100644 --- a/src/queries/accounts.ts +++ b/src/queries/accounts.ts @@ -40,15 +40,15 @@ const useUpdateCredentials = () => { const dispatch = useAppDispatch(); return useMutation({ - mutationFn: (data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', data), + mutationFn: (data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', { json: data }), onMutate(variables) { const cachedAccount = account; dispatch(patchMeSuccess({ ...account, ...variables })); return { cachedAccount }; }, - onSuccess(response) { - dispatch(patchMeSuccess(response.data)); + async onSuccess(response) { + dispatch(patchMeSuccess(await response.json())); toast.success('Chat Settings updated successfully'); }, onError(_error, _variables, context: any) { diff --git a/src/queries/chats.ts b/src/queries/chats.ts index 01d67987f..f83171523 100644 --- a/src/queries/chats.ts +++ b/src/queries/chats.ts @@ -2,7 +2,6 @@ import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery import sumBy from 'lodash/sumBy'; import { importFetchedAccount, importFetchedAccounts } from 'soapbox/actions/importer'; -import { getNextLink } from 'soapbox/api'; import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context'; import { useStatContext } from 'soapbox/contexts/stat-context'; import { useApi, useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; @@ -10,6 +9,7 @@ import { normalizeChatMessage } from 'soapbox/normalizers'; import toast from 'soapbox/toast'; import { ChatMessage } from 'soapbox/types/entities'; import { reOrderChatListItems, updateChatMessage } from 'soapbox/utils/chats'; +import { getPagination } from 'soapbox/utils/pagination'; import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries'; import { queryClient } from './client'; @@ -85,15 +85,15 @@ const useChatMessages = (chat: IChat) => { const nextPageLink = pageParam?.link; const uri = nextPageLink || `/api/v1/pleroma/chats/${chatId}/messages`; const response = await api.get(uri); - const { data } = response; + const data = await response.json(); - const link = getNextLink(response); - const hasMore = !!link; + const { next } = getPagination(response); + const hasMore = !!next; const result = data.map(normalizeChatMessage); return { result, - link, + link: next, hasMore, }; }; @@ -134,16 +134,18 @@ const useChats = (search?: string) => { const nextPageLink = pageParam?.link; const uri = nextPageLink || endpoint; const response = await api.get(uri, { - params: search ? { - search, - } : undefined, + json: { + params: search ? { + search, + } : undefined, + }, }); - const { data } = response; + const data = await response.json(); - const link = getNextLink(response); - const hasMore = !!link; + const { next } = getPagination(response); + const hasMore = !!next; - setUnreadChatsCount(Number(response.headers['x-unread-messages-count']) || sumBy(data, (chat) => chat.unread)); + setUnreadChatsCount(Number(response.headers.get('x-unread-messages-count')) || sumBy(data, (chat) => chat.unread)); // Set the relationships to these users in the redux store. fetchRelationships.mutate({ accountIds: data.map((item) => item.account.id) }); @@ -152,7 +154,7 @@ const useChats = (search?: string) => { return { result: data, hasMore, - link, + link: next, }; }; @@ -190,7 +192,7 @@ const useChat = (chatId?: string) => { const getChat = async () => { if (chatId) { - const { data } = await api.get(`/api/v1/pleroma/chats/${chatId}`); + const data = await api.get(`/api/v1/pleroma/chats/${chatId}`).json(); fetchRelationships.mutate({ accountIds: [data.account.id] }); dispatch(importFetchedAccount(data.account)); @@ -217,8 +219,9 @@ const useChatActions = (chatId: string) => { const { chat, changeScreen } = useChatContext(); const markChatAsRead = async (lastReadId: string) => { - return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { last_read_id: lastReadId }) - .then(({ data }) => { + return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { json: { last_read_id: lastReadId } }) + .then(async (response) => { + const data = await response.json(); updatePageItem(ChatKeys.chatSearch(), data, (o, n) => o.id === n.id); const queryData = queryClient.getQueryData>>(ChatKeys.chatSearch()); @@ -241,9 +244,11 @@ const useChatActions = (chatId: string) => { const createChatMessage = useMutation({ mutationFn: ({ chatId, content, mediaIds }: { chatId: string; content: string; mediaIds?: string[] }) => { return api.post(`/api/v1/pleroma/chats/${chatId}/messages`, { - content, - media_id: (mediaIds && mediaIds.length === 1) ? mediaIds[0] : undefined, // Pleroma backwards-compat - media_ids: mediaIds, + json: { + content, + media_id: (mediaIds && mediaIds.length === 1) ? mediaIds[0] : undefined, // Pleroma backwards-compat + media_ids: mediaIds, + }, }); }, retry: false, @@ -291,12 +296,13 @@ const useChatActions = (chatId: string) => { onError: (_error: any, variables, context: any) => { queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages); }, - onSuccess: (response: any, variables, context) => { - const nextChat = { ...chat, last_message: response.data }; + onSuccess: async (response, variables, context) => { + const data = await response.json(); + const nextChat = { ...chat, last_message: data }; updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id); updatePageItem( ChatKeys.chatMessages(variables.chatId), - normalizeChatMessage(response.data), + normalizeChatMessage(data), (o) => o.id === context.pendingId, ); reOrderChatListItems(); @@ -304,7 +310,7 @@ const useChatActions = (chatId: string) => { }); const updateChat = useMutation({ - mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, data), + mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, { json: data }), onMutate: async (data) => { // Cancel any outgoing refetches (so they don't overwrite our optimistic update) await queryClient.cancelQueries({ @@ -338,8 +344,9 @@ const useChatActions = (chatId: string) => { const acceptChat = useMutation({ mutationFn: () => api.post(`/api/v1/pleroma/chats/${chatId}/accept`), - onSuccess(response) { - changeScreen(ChatWidgetScreens.CHAT, response.data.id); + async onSuccess(response) { + const data = await response.json(); + changeScreen(ChatWidgetScreens.CHAT, data.id); queryClient.invalidateQueries({ queryKey: ChatKeys.chat(chatId) }); queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) }); queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() }); @@ -357,11 +364,13 @@ const useChatActions = (chatId: string) => { const createReaction = useMutation({ mutationFn: (data: CreateReactionVariables) => api.post(`/api/v1/pleroma/chats/${chatId}/messages/${data.messageId}/reactions`, { - emoji: data.emoji, + json: { + emoji: data.emoji, + }, }), // TODO: add optimistic updates - onSuccess(response) { - updateChatMessage(response.data); + async onSuccess(response) { + updateChatMessage(await response.json()); }, }); diff --git a/src/queries/embed.ts b/src/queries/embed.ts index eb32618ef..6bb739442 100644 --- a/src/queries/embed.ts +++ b/src/queries/embed.ts @@ -21,9 +21,8 @@ type Embed = { export default function useEmbed(url: string) { const api = useApi(); - const getEmbed = async() => { - const { data } = await api.get('/api/oembed', { params: { url } }); - return data; + const getEmbed = async (): Promise => { + return api.get('/api/oembed', { searchParams: { url } }).json(); }; return useQuery({ diff --git a/src/queries/relationships.ts b/src/queries/relationships.ts index 3ce486aac..80d83cda3 100644 --- a/src/queries/relationships.ts +++ b/src/queries/relationships.ts @@ -13,8 +13,8 @@ const useFetchRelationships = () => { return api.get(`/api/v1/accounts/relationships?${ids}`); }, - onSuccess(response) { - dispatch(fetchRelationshipsSuccess(response.data)); + async onSuccess(response) { + dispatch(fetchRelationshipsSuccess(await response.json())); }, onError(error) { dispatch(fetchRelationshipsFail(error)); diff --git a/src/queries/search.ts b/src/queries/search.ts index 6cbe2dfa9..e878466fa 100644 --- a/src/queries/search.ts +++ b/src/queries/search.ts @@ -1,32 +1,34 @@ import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query'; -import { getNextLink } from 'soapbox/api'; import { useApi } from 'soapbox/hooks'; import { Account } from 'soapbox/types/entities'; +import { getPagination } from 'soapbox/utils/pagination'; import { flattenPages, PaginatedResult } from 'soapbox/utils/queries'; export default function useAccountSearch(q: string) { const api = useApi(); - const getAccountSearch = async(q: string, pageParam: { link?: string }): Promise> => { + const getAccountSearch = async (q: string, pageParam: { link?: string }): Promise> => { const nextPageLink = pageParam?.link; const uri = nextPageLink || '/api/v1/accounts/search'; - const response = await api.get(uri, { - params: { - q, - limit: 10, - followers: true, + const response = await api.get(uri, { + json: { + params: { + q, + limit: 10, + followers: true, + }, }, }); - const { data } = response; + const data = await response.json(); - const link = getNextLink(response); - const hasMore = !!link; + const { next } = getPagination(response); + const hasMore = !!next; return { result: data, - link, + link: next, hasMore, }; }; diff --git a/src/queries/suggestions.ts b/src/queries/suggestions.ts index aa7fa5597..a28724312 100644 --- a/src/queries/suggestions.ts +++ b/src/queries/suggestions.ts @@ -2,8 +2,8 @@ import { useInfiniteQuery, useMutation, keepPreviousData } from '@tanstack/react import { fetchRelationships } from 'soapbox/actions/accounts'; import { importFetchedAccounts } from 'soapbox/actions/importer'; -import { getLinks } from 'soapbox/api'; import { useApi, useAppDispatch } from 'soapbox/hooks'; +import { getPagination } from 'soapbox/utils/pagination'; import { PaginatedResult, removePageItem } from '../utils/queries'; @@ -33,17 +33,18 @@ const useSuggestions = () => { const getV2Suggestions = async (pageParam: PageParam): Promise> => { const endpoint = pageParam?.link || '/api/v2/suggestions'; const response = await api.get(endpoint); - const hasMore = !!response.headers.link; - const nextLink = getLinks(response).refs.find(link => link.rel === 'next')?.uri; + const { next } = getPagination(response); + const hasMore = !!next; - const accounts = response.data.map(({ account }) => account); + const data = await response.json(); + const accounts = data.map(({ account }) => account); const accountIds = accounts.map((account) => account.id); dispatch(importFetchedAccounts(accounts)); dispatch(fetchRelationships(accountIds)); return { - result: response.data.map(x => ({ ...x, account: x.account.id })), - link: nextLink, + result: data.map(x => ({ ...x, account: x.account.id })), + link: next, hasMore, }; }; @@ -91,17 +92,18 @@ function useOnboardingSuggestions() { const getV2Suggestions = async (pageParam: any): Promise<{ data: Suggestion[]; link: string | undefined; hasMore: boolean }> => { const link = pageParam?.link || '/api/v2/suggestions'; const response = await api.get(link); - const hasMore = !!response.headers.link; - const nextLink = getLinks(response).refs.find(link => link.rel === 'next')?.uri; + const { next } = getPagination(response); + const hasMore = !!next; - const accounts = response.data.map(({ account }) => account); + const data = await response.json(); + const accounts = data.map(({ account }) => account); const accountIds = accounts.map((account) => account.id); dispatch(importFetchedAccounts(accounts)); dispatch(fetchRelationships(accountIds)); return { - data: response.data, - link: nextLink, + data: data, + link: next, hasMore, }; }; diff --git a/src/queries/trends.ts b/src/queries/trends.ts index cf46d130c..c6072325d 100644 --- a/src/queries/trends.ts +++ b/src/queries/trends.ts @@ -11,7 +11,7 @@ export default function useTrends() { const dispatch = useAppDispatch(); const getTrends = async() => { - const { data } = await api.get('/api/v1/trends'); + const data = await api.get('/api/v1/trends').json(); dispatch(fetchTrendsSuccess(data)); diff --git a/src/utils/pagination.ts b/src/utils/pagination.ts new file mode 100644 index 000000000..bf23f958f --- /dev/null +++ b/src/utils/pagination.ts @@ -0,0 +1,16 @@ +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, + }; +} From 87ee06fd1ff3f4c5344239775858893c70c4c9c2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 10 Oct 2024 22:15:21 -0500 Subject: [PATCH 3/3] Remove ky, create a custom fetch client --- package.json | 1 - src/api/HTTPError.ts | 10 ++ src/api/MastodonClient.ts | 93 +++++++++++++++++++ src/api/hooks/accounts/useFollow.ts | 2 +- src/api/hooks/admin/useAnnouncements.ts | 7 +- src/api/hooks/admin/useCreateDomain.ts | 7 +- src/api/hooks/admin/useDomains.ts | 7 +- src/api/hooks/admin/useManageZapSplit.ts | 7 +- src/api/hooks/admin/useModerationLog.ts | 3 +- src/api/hooks/admin/useRelays.ts | 13 +-- src/api/hooks/admin/useRules.ts | 7 +- src/api/hooks/admin/useSuggest.ts | 4 +- src/api/hooks/admin/useUpdateDomain.ts | 7 +- src/api/hooks/admin/useVerify.ts | 4 +- .../hooks/announcements/useAnnouncements.ts | 15 ++- src/api/hooks/captcha/useCaptcha.ts | 13 +-- src/api/hooks/groups/useCreateGroup.ts | 3 +- src/api/hooks/groups/useGroupValidation.ts | 7 +- src/api/hooks/groups/useUpdateGroup.ts | 3 +- .../hooks/statuses/useCreateBookmarkFolder.ts | 3 +- .../hooks/statuses/useUpdateBookmarkFolder.ts | 8 +- src/api/hooks/zap-split/useZapSplit.ts | 7 +- src/entity-store/hooks/useCreateEntity.ts | 2 +- src/entity-store/hooks/useEntity.ts | 6 +- src/entity-store/hooks/useEntityActions.ts | 4 +- src/entity-store/hooks/useEntityLookup.ts | 6 +- .../admin/hooks/useAdminNostrRelays.ts | 3 +- src/features/admin/nostr-relays.tsx | 2 +- src/features/edit-identity/index.tsx | 8 +- .../manage-group-modal/create-group-modal.tsx | 12 ++- src/hooks/useApi.ts | 16 +--- src/queries/accounts.ts | 2 +- src/queries/chats.ts | 42 ++++----- src/queries/embed.ts | 3 +- src/queries/search.ts | 12 +-- src/queries/suggestions.ts | 8 +- src/queries/trends.ts | 3 +- yarn.lock | 5 - 38 files changed, 230 insertions(+), 135 deletions(-) create mode 100644 src/api/HTTPError.ts create mode 100644 src/api/MastodonClient.ts diff --git a/package.json b/package.json index e13ce8c85..4574cd63d 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,6 @@ "intl-messageformat": "10.5.11", "intl-pluralrules": "^2.0.0", "isomorphic-dompurify": "^2.3.0", - "ky": "^1.7.2", "leaflet": "^1.8.0", "lexical": "^0.18.0", "line-awesome": "^1.3.0", diff --git a/src/api/HTTPError.ts b/src/api/HTTPError.ts new file mode 100644 index 000000000..4be34b343 --- /dev/null +++ b/src/api/HTTPError.ts @@ -0,0 +1,10 @@ +export class HTTPError extends Error { + + response: Response; + + constructor(response: Response) { + super(response.statusText); + this.response = response; + } + +} \ No newline at end of file diff --git a/src/api/MastodonClient.ts b/src/api/MastodonClient.ts new file mode 100644 index 000000000..22eb35457 --- /dev/null +++ b/src/api/MastodonClient.ts @@ -0,0 +1,93 @@ +import { HTTPError } from './HTTPError'; + +interface Opts { + searchParams?: Record; + headers?: Record; + signal?: AbortSignal; +} + +export class MastodonClient { + + readonly baseUrl: string; + + private fetch: typeof fetch; + private accessToken?: string; + + constructor(baseUrl: string, accessToken?: string, fetch = globalThis.fetch.bind(globalThis)) { + this.fetch = fetch; + this.baseUrl = baseUrl; + this.accessToken = accessToken; + } + + async get(path: string, opts: Opts = {}): Promise { + return this.request('GET', path, undefined, opts); + } + + async post(path: string, data?: unknown, opts: Opts = {}): Promise { + return this.request('POST', path, data, opts); + } + + async put(path: string, data?: unknown, opts: Opts = {}): Promise { + return this.request('PUT', path, data, opts); + } + + async delete(path: string, opts: Opts = {}): Promise { + return this.request('DELETE', path, undefined, opts); + } + + async patch(path: string, data: unknown, opts: Opts = {}): Promise { + return this.request('PATCH', path, data, opts); + } + + async head(path: string, opts: Opts = {}): Promise { + return this.request('HEAD', path, undefined, opts); + } + + async options(path: string, opts: Opts = {}): Promise { + return this.request('OPTIONS', path, undefined, opts); + } + + async request(method: string, path: string, data: unknown, opts: Opts = {}): Promise { + const url = new URL(path, this.baseUrl); + + if (opts.searchParams) { + const params = Object + .entries(opts.searchParams) + .map(([key, value]) => ([key, String(value)])); + + url.search = new URLSearchParams(params).toString(); + } + + const headers = new Headers(opts.headers); + + if (this.accessToken) { + headers.set('Authorization', `Bearer ${this.accessToken}`); + } + + let body: BodyInit | undefined; + + if (data instanceof FormData) { + headers.set('Content-Type', 'multipart/form-data'); + body = data; + } else if (data !== undefined) { + headers.set('Content-Type', 'application/json'); + body = JSON.stringify(data); + } + + const request = new Request(url, { + method, + headers, + signal: opts.signal, + body, + }); + + const response = await this.fetch(request); + + if (!response.ok) { + throw new HTTPError(response); + } + + return response; + } + +} \ No newline at end of file diff --git a/src/api/hooks/accounts/useFollow.ts b/src/api/hooks/accounts/useFollow.ts index a3d43905a..25be4b3bc 100644 --- a/src/api/hooks/accounts/useFollow.ts +++ b/src/api/hooks/accounts/useFollow.ts @@ -55,7 +55,7 @@ function useFollow() { followEffect(accountId); try { - const response = await api.post(`/api/v1/accounts/${accountId}/follow`, { json: options }); + const response = await api.post(`/api/v1/accounts/${accountId}/follow`, options); const result = relationshipSchema.safeParse(await response.json()); if (result.success) { dispatch(importEntities([result.data], Entities.RELATIONSHIPS)); diff --git a/src/api/hooks/admin/useAnnouncements.ts b/src/api/hooks/admin/useAnnouncements.ts index 706dd1595..2fa3f0029 100644 --- a/src/api/hooks/admin/useAnnouncements.ts +++ b/src/api/hooks/admin/useAnnouncements.ts @@ -22,7 +22,8 @@ const useAnnouncements = () => { const userAnnouncements = useUserAnnouncements(); const getAnnouncements = async () => { - const data = await api.get('/api/v1/pleroma/admin/announcements').json(); + const response = await api.get('/api/v1/pleroma/admin/announcements'); + const data: AdminAnnouncement[] = await response.json(); const normalizedData = data.map((announcement) => adminAnnouncementSchema.parse(announcement)); return normalizedData; @@ -38,7 +39,7 @@ const useAnnouncements = () => { mutate: createAnnouncement, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', { json: params }), + mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); @@ -53,7 +54,7 @@ const useAnnouncements = () => { mutate: updateAnnouncement, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, { json: params }), + mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); diff --git a/src/api/hooks/admin/useCreateDomain.ts b/src/api/hooks/admin/useCreateDomain.ts index a4b86fba0..8cbb4ae85 100644 --- a/src/api/hooks/admin/useCreateDomain.ts +++ b/src/api/hooks/admin/useCreateDomain.ts @@ -11,8 +11,11 @@ interface CreateDomainParams { const useCreateDomain = () => { const api = useApi(); - const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: CreateDomainParams) => - api.post('/api/v1/pleroma/admin/domains', { json: params }), { schema: domainSchema }); + const { createEntity, ...rest } = useCreateEntity( + [Entities.DOMAINS], + (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', params), + { schema: domainSchema }, + ); return { createDomain: createEntity, diff --git a/src/api/hooks/admin/useDomains.ts b/src/api/hooks/admin/useDomains.ts index 28fdd8a98..ff052b7ac 100644 --- a/src/api/hooks/admin/useDomains.ts +++ b/src/api/hooks/admin/useDomains.ts @@ -18,7 +18,8 @@ const useDomains = () => { const api = useApi(); const getDomains = async () => { - const data = await api.get('/api/v1/pleroma/admin/domains').json(); + const response = await api.get('/api/v1/pleroma/admin/domains'); + const data: Domain[] = await response.json(); const normalizedData = data.map((domain) => domainSchema.parse(domain)); return normalizedData; @@ -34,7 +35,7 @@ const useDomains = () => { mutate: createDomain, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', { json: params }), + mutationFn: (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); @@ -48,7 +49,7 @@ const useDomains = () => { mutate: updateDomain, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateDomainParams) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, { json: params }), + mutationFn: ({ id, ...params }: UpdateDomainParams) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); diff --git a/src/api/hooks/admin/useManageZapSplit.ts b/src/api/hooks/admin/useManageZapSplit.ts index 615c818ed..e0e0344ce 100644 --- a/src/api/hooks/admin/useManageZapSplit.ts +++ b/src/api/hooks/admin/useManageZapSplit.ts @@ -32,7 +32,8 @@ export const useManageZapSplit = () => { */ const fetchZapSplitData = async () => { try { - const data = await api.get('/api/v1/ditto/zap_splits').json(); + const response = await api.get('/api/v1/ditto/zap_splits'); + const data: ZapSplitData[] = await response.json(); if (data) { const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit)); setFormattedData(normalizedData); @@ -132,9 +133,7 @@ export const useManageZapSplit = () => { * @param accountId - The ID of the account to be removed. */ const removeAccount = async (accountId: string) => { - const isToDelete = [(formattedData.find(item => item.account.id === accountId))?.account.id]; - - await api.delete('/api/v1/admin/ditto/zap_splits/', { json: isToDelete }); + await api.request('DELETE', '/api/v1/admin/ditto/zap_splits', [accountId]); await fetchZapSplitData(); }; diff --git a/src/api/hooks/admin/useModerationLog.ts b/src/api/hooks/admin/useModerationLog.ts index 5c38a66eb..2153deab8 100644 --- a/src/api/hooks/admin/useModerationLog.ts +++ b/src/api/hooks/admin/useModerationLog.ts @@ -14,7 +14,8 @@ const useModerationLog = () => { const api = useApi(); const getModerationLog = async (page: number): Promise => { - const data = await api.get('/api/v1/pleroma/admin/moderation_log', { searchParams: { page } }).json(); + const response = await api.get('/api/v1/pleroma/admin/moderation_log', { searchParams: { page } }); + const data: ModerationLogResult = await response.json(); const normalizedData = data.items.map((domain) => moderationLogEntrySchema.parse(domain)); diff --git a/src/api/hooks/admin/useRelays.ts b/src/api/hooks/admin/useRelays.ts index ff6383e48..d27b6d043 100644 --- a/src/api/hooks/admin/useRelays.ts +++ b/src/api/hooks/admin/useRelays.ts @@ -8,9 +8,10 @@ const useRelays = () => { const api = useApi(); const getRelays = async () => { - const data = await api.get<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay').json(); + const response = await api.get('/api/v1/pleroma/admin/relay'); + const relays: Relay[] = await response.json(); - const normalizedData = data.relays?.map((relay) => relaySchema.parse(relay)); + const normalizedData = relays?.map((relay) => relaySchema.parse(relay)); return normalizedData; }; @@ -24,7 +25,7 @@ const useRelays = () => { mutate: followRelay, isPending: isPendingFollow, } = useMutation({ - mutationFn: (relayUrl: string) => api.post('/api/v1/pleroma/admin/relays', { json: { relay_url: relayUrl } }), + mutationFn: (relayUrl: string) => api.post('/api/v1/pleroma/admin/relays', { relay_url: relayUrl }), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); @@ -38,9 +39,9 @@ const useRelays = () => { mutate: unfollowRelay, isPending: isPendingUnfollow, } = useMutation({ - mutationFn: (relayUrl: string) => api.delete('/api/v1/pleroma/admin/relays', { - json: { relay_url: relayUrl }, - }), + mutationFn: async (relayUrl: string) => { + await api.request('DELETE', '/api/v1/pleroma/admin/relays', { relay_url: relayUrl }); + }, retry: false, onSuccess: (_, relayUrl) => queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray) => diff --git a/src/api/hooks/admin/useRules.ts b/src/api/hooks/admin/useRules.ts index b1fbd6154..8fed91fa0 100644 --- a/src/api/hooks/admin/useRules.ts +++ b/src/api/hooks/admin/useRules.ts @@ -21,7 +21,8 @@ const useRules = () => { const api = useApi(); const getRules = async () => { - const data = await api.get('/api/v1/pleroma/admin/rules').json(); + const response = await api.get('/api/v1/pleroma/admin/rules'); + const data: AdminRule[] = await response.json(); const normalizedData = data.map((rule) => adminRuleSchema.parse(rule)); return normalizedData; @@ -37,7 +38,7 @@ const useRules = () => { mutate: createRule, isPending: isCreating, } = useMutation({ - mutationFn: (params: CreateRuleParams) => api.post('/api/v1/pleroma/admin/rules', { json: params }), + mutationFn: (params: CreateRuleParams) => api.post('/api/v1/pleroma/admin/rules', params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); @@ -51,7 +52,7 @@ const useRules = () => { mutate: updateRule, isPending: isUpdating, } = useMutation({ - mutationFn: ({ id, ...params }: UpdateRuleParams) => api.patch(`/api/v1/pleroma/admin/rules/${id}`, { json: params }), + mutationFn: ({ id, ...params }: UpdateRuleParams) => api.patch(`/api/v1/pleroma/admin/rules/${id}`, params), retry: false, onSuccess: async (response: Response) => { const data = await response.json(); diff --git a/src/api/hooks/admin/useSuggest.ts b/src/api/hooks/admin/useSuggest.ts index 82aa3adcf..b20bc5308 100644 --- a/src/api/hooks/admin/useSuggest.ts +++ b/src/api/hooks/admin/useSuggest.ts @@ -29,7 +29,7 @@ function useSuggest() { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, true); try { - await api.patch('/api/v1/pleroma/admin/users/suggest', { json: { nicknames: accts } }); + await api.patch('/api/v1/pleroma/admin/users/suggest', { nicknames: accts }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); @@ -41,7 +41,7 @@ function useSuggest() { const accts = accountIdsToAccts(getState(), accountIds); suggestEffect(accountIds, false); try { - await api.patch('/api/v1/pleroma/admin/users/unsuggest', { json: { nicknames: accts } }); + await api.patch('/api/v1/pleroma/admin/users/unsuggest', { nicknames: accts }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); diff --git a/src/api/hooks/admin/useUpdateDomain.ts b/src/api/hooks/admin/useUpdateDomain.ts index f278441fb..8488d6916 100644 --- a/src/api/hooks/admin/useUpdateDomain.ts +++ b/src/api/hooks/admin/useUpdateDomain.ts @@ -8,8 +8,11 @@ import type { CreateDomainParams } from './useCreateDomain'; const useUpdateDomain = (id: string) => { const api = useApi(); - const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: Omit) => - api.patch(`/api/v1/pleroma/admin/domains/${id}`, { json: params }), { schema: domainSchema }); + const { createEntity, ...rest } = useCreateEntity( + [Entities.DOMAINS], + (params: Omit) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, params), + { schema: domainSchema }, + ); return { updateDomain: createEntity, diff --git a/src/api/hooks/admin/useVerify.ts b/src/api/hooks/admin/useVerify.ts index ed4141082..9312d609e 100644 --- a/src/api/hooks/admin/useVerify.ts +++ b/src/api/hooks/admin/useVerify.ts @@ -34,7 +34,7 @@ function useVerify() { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, true); try { - await api.put('/api/v1/pleroma/admin/users/tag', { json: { nicknames: accts, tags: ['verified'] } }); + await api.put('/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); @@ -46,7 +46,7 @@ function useVerify() { const accts = accountIdsToAccts(getState(), accountIds); verifyEffect(accountIds, false); try { - await api.delete('/api/v1/pleroma/admin/users/tag', { json: { nicknames: accts, tags: ['verified'] } }); + await api.request('DELETE', '/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); diff --git a/src/api/hooks/announcements/useAnnouncements.ts b/src/api/hooks/announcements/useAnnouncements.ts index d5b24dec7..af4e36a2c 100644 --- a/src/api/hooks/announcements/useAnnouncements.ts +++ b/src/api/hooks/announcements/useAnnouncements.ts @@ -24,7 +24,8 @@ const useAnnouncements = () => { const api = useApi(); const getAnnouncements = async () => { - const data = await api.get('/api/v1/announcements').json(); + const response = await api.get('/api/v1/announcements'); + const data: Announcement[] = await response.json(); const normalizedData = data?.map((announcement) => announcementSchema.parse(announcement)); return normalizedData; @@ -39,8 +40,10 @@ const useAnnouncements = () => { const { mutate: addReaction, } = useMutation({ - mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) => - api.put(`/api/v1/announcements/${announcementId}/reactions/${name}`), + mutationFn: async ({ announcementId, name }: { announcementId: string; name: string }): Promise => { + const response = await api.put(`/api/v1/announcements/${announcementId}/reactions/${name}`); + return response.json(); + }, retry: false, onMutate: ({ announcementId: id, name }) => { queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => @@ -63,8 +66,10 @@ const useAnnouncements = () => { const { mutate: removeReaction, } = useMutation({ - mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) => - api.delete(`/api/v1/announcements/${announcementId}/reactions/${name}`), + mutationFn: async ({ announcementId, name }: { announcementId: string; name: string }): Promise => { + const response = await api.delete(`/api/v1/announcements/${announcementId}/reactions/${name}`); + return response.json(); + }, retry: false, onMutate: ({ announcementId: id, name }) => { queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => diff --git a/src/api/hooks/captcha/useCaptcha.ts b/src/api/hooks/captcha/useCaptcha.ts index 8085376db..92f3115f7 100644 --- a/src/api/hooks/captcha/useCaptcha.ts +++ b/src/api/hooks/captcha/useCaptcha.ts @@ -35,14 +35,11 @@ const useCaptcha = () => { try { const topI = getRandomNumber(0, (356 - 61)); const leftI = getRandomNumber(0, (330 - 61)); - const { data } = await api.get('/api/v1/ditto/captcha'); - if (data) { - const normalizedData = captchaSchema.parse(data); - setCaptcha(normalizedData); - setYPosition(topI); - setXPosition(leftI); - } - + const response = await api.get('/api/v1/ditto/captcha'); + const data = captchaSchema.parse(await response.json()); + setCaptcha(data); + setYPosition(topI); + setXPosition(leftI); } catch (error) { toast.error('Error loading captcha:'); } diff --git a/src/api/hooks/groups/useCreateGroup.ts b/src/api/hooks/groups/useCreateGroup.ts index 1d3a18107..e80417856 100644 --- a/src/api/hooks/groups/useCreateGroup.ts +++ b/src/api/hooks/groups/useCreateGroup.ts @@ -17,8 +17,7 @@ function useCreateGroup() { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS, 'search', ''], (params: CreateGroupParams) => { - return api.post('/api/v1/groups', { - json: params, + return api.post('/api/v1/groups', params, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/groups/useGroupValidation.ts b/src/api/hooks/groups/useGroupValidation.ts index f0e69ef6c..1b008b0b1 100644 --- a/src/api/hooks/groups/useGroupValidation.ts +++ b/src/api/hooks/groups/useGroupValidation.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { HTTPError } from 'ky'; +import { HTTPError } from 'soapbox/api/HTTPError'; import { useApi } from 'soapbox/hooks/useApi'; import { useFeatures } from 'soapbox/hooks/useFeatures'; @@ -19,9 +19,8 @@ function useGroupValidation(name: string = '') { const getValidation = async () => { try { - return api.get('/api/v1/groups/validate', { - searchParams: { name }, - }).json(); + const response = await api.get('/api/v1/groups/validate', { searchParams: { name } }); + return response.json(); } catch (e) { if (e instanceof HTTPError && e.response.status === 422) { return e.response.json(); diff --git a/src/api/hooks/groups/useUpdateGroup.ts b/src/api/hooks/groups/useUpdateGroup.ts index ef6539167..129849514 100644 --- a/src/api/hooks/groups/useUpdateGroup.ts +++ b/src/api/hooks/groups/useUpdateGroup.ts @@ -17,8 +17,7 @@ function useUpdateGroup(groupId: string) { const api = useApi(); const { createEntity, ...rest } = useCreateEntity([Entities.GROUPS], (params: UpdateGroupParams) => { - return api.put(`/api/v1/groups/${groupId}`, { - json: params, + return api.put(`/api/v1/groups/${groupId}`, params, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/statuses/useCreateBookmarkFolder.ts b/src/api/hooks/statuses/useCreateBookmarkFolder.ts index 41ae6317d..ded24ff97 100644 --- a/src/api/hooks/statuses/useCreateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useCreateBookmarkFolder.ts @@ -14,8 +14,7 @@ function useCreateBookmarkFolder() { const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], (params: CreateBookmarkFolderParams) => - api.post('/api/v1/pleroma/bookmark_folders', { - json: params, + api.post('/api/v1/pleroma/bookmark_folders', params, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts index 109ef2952..2e023b04b 100644 --- a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts @@ -13,13 +13,7 @@ function useUpdateBookmarkFolder(folderId: string) { const { createEntity, ...rest } = useCreateEntity( [Entities.BOOKMARK_FOLDERS], - (params: UpdateBookmarkFolderParams) => - api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, { - json: params, - headers: { - 'Content-Type': 'multipart/form-data', - }, - }), + (params: UpdateBookmarkFolderParams) => api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, params), { schema: bookmarkFolderSchema }, ); diff --git a/src/api/hooks/zap-split/useZapSplit.ts b/src/api/hooks/zap-split/useZapSplit.ts index 5109fefa1..f77b79c71 100644 --- a/src/api/hooks/zap-split/useZapSplit.ts +++ b/src/api/hooks/zap-split/useZapSplit.ts @@ -30,13 +30,12 @@ const useZapSplit = (status: StatusEntity | undefined, account: AccountEntity) = const [zapArrays, setZapArrays] = useState([]); const [zapSplitData, setZapSplitData] = useState<{splitAmount: number; receiveAmount: number; splitValues: SplitValue[]}>({ splitAmount: Number(), receiveAmount: Number(), splitValues: [] }); - const fetchZapSplit = (id: string) => { - return api.get(`/api/v1/ditto/${id}/zap_splits`); - }; + const fetchZapSplit = (id: string) => api.get(`/api/v1/ditto/${id}/zap_splits`); const loadZapSplitData = async () => { if (status) { - const data = await fetchZapSplit(status.id).json(); + const response = await fetchZapSplit(status.id); + const data: ZapSplitData[] = await response.json(); if (data) { const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit)); setZapArrays(normalizedData); diff --git a/src/entity-store/hooks/useCreateEntity.ts b/src/entity-store/hooks/useCreateEntity.ts index 52abce119..77eea017c 100644 --- a/src/entity-store/hooks/useCreateEntity.ts +++ b/src/entity-store/hooks/useCreateEntity.ts @@ -1,6 +1,6 @@ -import { HTTPError } from 'ky'; import { z } from 'zod'; +import { HTTPError } from 'soapbox/api/HTTPError'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; import { useLoading } from 'soapbox/hooks/useLoading'; diff --git a/src/entity-store/hooks/useEntity.ts b/src/entity-store/hooks/useEntity.ts index 55c7821d2..2e6734f9c 100644 --- a/src/entity-store/hooks/useEntity.ts +++ b/src/entity-store/hooks/useEntity.ts @@ -1,7 +1,7 @@ -import { HTTPError } from 'ky'; import { useEffect, useState } from 'react'; import z from 'zod'; +import { HTTPError } from 'soapbox/api/HTTPError'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; import { useAppSelector } from 'soapbox/hooks/useAppSelector'; import { useLoading } from 'soapbox/hooks/useLoading'; @@ -68,8 +68,8 @@ function useEntity( isLoading, isLoaded, error, - isUnauthorized: error instanceof HTTPError && error.response?.status === 401, - isForbidden: error instanceof HTTPError && error.response?.status === 403, + isUnauthorized: error instanceof HTTPError && error.response.status === 401, + isForbidden: error instanceof HTTPError && error.response.status === 403, }; } diff --git a/src/entity-store/hooks/useEntityActions.ts b/src/entity-store/hooks/useEntityActions.ts index 48f837c17..b038946f3 100644 --- a/src/entity-store/hooks/useEntityActions.ts +++ b/src/entity-store/hooks/useEntityActions.ts @@ -29,10 +29,10 @@ function useEntityActions( useDeleteEntity(entityType, (entityId) => api.delete(endpoints.delete!.replace(/:id/g, entityId))); const { createEntity, isSubmitting: createSubmitting } = - useCreateEntity(path, (data) => api.post(endpoints.post!, { json: data }), opts); + useCreateEntity(path, (data) => api.post(endpoints.post!, data), opts); const { createEntity: updateEntity, isSubmitting: updateSubmitting } = - useCreateEntity(path, (data) => api.patch(endpoints.patch!, { json: data }), opts); + useCreateEntity(path, (data) => api.patch(endpoints.patch!, data), opts); return { createEntity, diff --git a/src/entity-store/hooks/useEntityLookup.ts b/src/entity-store/hooks/useEntityLookup.ts index dc0c5d791..da3553281 100644 --- a/src/entity-store/hooks/useEntityLookup.ts +++ b/src/entity-store/hooks/useEntityLookup.ts @@ -1,7 +1,7 @@ -import { HTTPError } from 'ky'; import { useEffect, useState } from 'react'; import { z } from 'zod'; +import { HTTPError } from 'soapbox/api/HTTPError'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; import { useAppSelector } from 'soapbox/hooks/useAppSelector'; import { useLoading } from 'soapbox/hooks/useLoading'; @@ -58,8 +58,8 @@ function useEntityLookup( fetchEntity, isFetching, isLoading, - isUnauthorized: error instanceof HTTPError && error.response?.status === 401, - isForbidden: error instanceof HTTPError && error.response?.status === 403, + isUnauthorized: error instanceof HTTPError && error.response.status === 401, + isForbidden: error instanceof HTTPError && error.response.status === 403, }; } diff --git a/src/features/admin/hooks/useAdminNostrRelays.ts b/src/features/admin/hooks/useAdminNostrRelays.ts index 8a4970727..576f12351 100644 --- a/src/features/admin/hooks/useAdminNostrRelays.ts +++ b/src/features/admin/hooks/useAdminNostrRelays.ts @@ -14,7 +14,8 @@ export function useAdminNostrRelays() { return useQuery({ queryKey: ['NostrRelay'], queryFn: async () => { - const data = await api.get('/api/v1/admin/ditto/relays').json(); + const response = await api.get('/api/v1/admin/ditto/relays'); + const data = await response.json(); return relayEntitySchema.array().parse(data); }, }); diff --git a/src/features/admin/nostr-relays.tsx b/src/features/admin/nostr-relays.tsx index e97fa4d3b..40c7d719e 100644 --- a/src/features/admin/nostr-relays.tsx +++ b/src/features/admin/nostr-relays.tsx @@ -20,7 +20,7 @@ const AdminNostrRelays: React.FC = () => { const [relays, setRelays] = useState(result.data ?? []); const mutation = useMutation({ - mutationFn: async () => api.put('/api/v1/admin/ditto/relays', { json: relays }), + mutationFn: async () => api.put('/api/v1/admin/ditto/relays', relays), }); const handleSubmit = () => { diff --git a/src/features/edit-identity/index.tsx b/src/features/edit-identity/index.tsx index 814c526c1..4c8b0ff60 100644 --- a/src/features/edit-identity/index.tsx +++ b/src/features/edit-identity/index.tsx @@ -187,7 +187,7 @@ function useRequestName() { const api = useApi(); return useMutation({ - mutationFn: (data: NameRequestData) => api.post('/api/v1/ditto/names', { json: data }), + mutationFn: (data: NameRequestData) => api.post('/api/v1/ditto/names', data), }); } @@ -197,7 +197,8 @@ function useNames() { return useQuery({ queryKey: ['names', 'approved'], queryFn: async () => { - const data = await api.get('/api/v1/ditto/names?approved=true').json(); + const response = await api.get('/api/v1/ditto/names?approved=true'); + const data = await response.json(); return adminAccountSchema.array().parse(data); }, placeholderData: [], @@ -210,7 +211,8 @@ function usePendingNames() { return useQuery({ queryKey: ['names', 'pending'], queryFn: async () => { - const data = await api.get('/api/v1/ditto/names?approved=false').json(); + const response = await api.get('/api/v1/ditto/names?approved=false'); + const data = await response.json(); return adminAccountSchema.array().parse(data); }, placeholderData: [], diff --git a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx index 295ef612b..bef60fda4 100644 --- a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx +++ b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx @@ -1,8 +1,8 @@ -import { HTTPError } from 'ky'; import React, { useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { z } from 'zod'; +import { HTTPError } from 'soapbox/api/HTTPError'; import { useCreateGroup, useGroupValidation, type CreateGroupParams } from 'soapbox/api/hooks'; import { Modal, Stack } from 'soapbox/components/ui'; import { useDebounce } from 'soapbox/hooks'; @@ -72,9 +72,13 @@ const CreateGroupModal: React.FC = ({ onClose }) => { }, async onError(error) { if (error instanceof HTTPError) { - const msg = z.object({ error: z.string() }).safeParse(await error.response.json()); - if (msg.success) { - toast.error(msg.data.error); + try { + const data = await error.response.json(); + const msg = z.object({ error: z.string() }).parse(data); + toast.error(msg.error); + + } catch { + // Do nothing } } }, diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts index 5117bffb6..5a4babb02 100644 --- a/src/hooks/useApi.ts +++ b/src/hooks/useApi.ts @@ -1,20 +1,12 @@ -import ky, { KyInstance } from 'ky'; +import { MastodonClient } from 'soapbox/api/MastodonClient'; import { useAppSelector } from './useAppSelector'; import { useOwnAccount } from './useOwnAccount'; -export function useApi(): KyInstance { +export function useApi(): MastodonClient { const { account } = useOwnAccount(); const accessToken = useAppSelector((state) => account ? state.auth.users.get(account.url)?.access_token : undefined); + const baseUrl = account ? new URL(account.url).origin : location.origin; - const headers: Record = {}; - - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; - } - - return ky.create({ - prefixUrl: account ? new URL(account.url).origin : undefined, - headers, - }); + return new MastodonClient(baseUrl, accessToken); } \ No newline at end of file diff --git a/src/queries/accounts.ts b/src/queries/accounts.ts index 77e40fb9f..762649e4c 100644 --- a/src/queries/accounts.ts +++ b/src/queries/accounts.ts @@ -40,7 +40,7 @@ const useUpdateCredentials = () => { const dispatch = useAppDispatch(); return useMutation({ - mutationFn: (data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', { json: data }), + mutationFn: (data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', data), onMutate(variables) { const cachedAccount = account; dispatch(patchMeSuccess({ ...account, ...variables })); diff --git a/src/queries/chats.ts b/src/queries/chats.ts index f83171523..9eb1f10ee 100644 --- a/src/queries/chats.ts +++ b/src/queries/chats.ts @@ -84,7 +84,7 @@ const useChatMessages = (chat: IChat) => { const getChatMessages = async (chatId: string, pageParam?: any): Promise> => { const nextPageLink = pageParam?.link; const uri = nextPageLink || `/api/v1/pleroma/chats/${chatId}/messages`; - const response = await api.get(uri); + const response = await api.get(uri); const data = await response.json(); const { next } = getPagination(response); @@ -133,14 +133,12 @@ const useChats = (search?: string) => { const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats'; const nextPageLink = pageParam?.link; const uri = nextPageLink || endpoint; - const response = await api.get(uri, { - json: { - params: search ? { - search, - } : undefined, - }, + const response = await api.get(uri, { + searchParams: search ? { + search, + } : undefined, }); - const data = await response.json(); + const data: IChat[] = await response.json(); const { next } = getPagination(response); const hasMore = !!next; @@ -180,7 +178,7 @@ const useChats = (search?: string) => { data, }; - const getOrCreateChatByAccountId = (accountId: string) => api.post(`/api/v1/pleroma/chats/by-account-id/${accountId}`); + const getOrCreateChatByAccountId = (accountId: string) => api.post(`/api/v1/pleroma/chats/by-account-id/${accountId}`); return { chatsQuery, getOrCreateChatByAccountId }; }; @@ -192,7 +190,8 @@ const useChat = (chatId?: string) => { const getChat = async () => { if (chatId) { - const data = await api.get(`/api/v1/pleroma/chats/${chatId}`).json(); + const response = await api.get(`/api/v1/pleroma/chats/${chatId}`); + const data: IChat = await response.json(); fetchRelationships.mutate({ accountIds: [data.account.id] }); dispatch(importFetchedAccount(data.account)); @@ -219,7 +218,7 @@ const useChatActions = (chatId: string) => { const { chat, changeScreen } = useChatContext(); const markChatAsRead = async (lastReadId: string) => { - return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { json: { last_read_id: lastReadId } }) + return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { last_read_id: lastReadId }) .then(async (response) => { const data = await response.json(); updatePageItem(ChatKeys.chatSearch(), data, (o, n) => o.id === n.id); @@ -242,14 +241,13 @@ const useChatActions = (chatId: string) => { }; const createChatMessage = useMutation({ - mutationFn: ({ chatId, content, mediaIds }: { chatId: string; content: string; mediaIds?: string[] }) => { - return api.post(`/api/v1/pleroma/chats/${chatId}/messages`, { - json: { - content, - media_id: (mediaIds && mediaIds.length === 1) ? mediaIds[0] : undefined, // Pleroma backwards-compat - media_ids: mediaIds, - }, + mutationFn: async ({ chatId, content, mediaIds }: { chatId: string; content: string; mediaIds?: string[] }) => { + const response = await api.post(`/api/v1/pleroma/chats/${chatId}/messages`, { + content, + media_id: (mediaIds && mediaIds.length === 1) ? mediaIds[0] : undefined, // Pleroma backwards-compat + media_ids: mediaIds, }); + return response.json(); }, retry: false, onMutate: async (variables) => { @@ -310,7 +308,7 @@ const useChatActions = (chatId: string) => { }); const updateChat = useMutation({ - mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, { json: data }), + mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, data), onMutate: async (data) => { // Cancel any outgoing refetches (so they don't overwrite our optimistic update) await queryClient.cancelQueries({ @@ -340,10 +338,10 @@ const useChatActions = (chatId: string) => { }, }); - const deleteChatMessage = (chatMessageId: string) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`); + const deleteChatMessage = (chatMessageId: string) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`); const acceptChat = useMutation({ - mutationFn: () => api.post(`/api/v1/pleroma/chats/${chatId}/accept`), + mutationFn: () => api.post(`/api/v1/pleroma/chats/${chatId}/accept`), async onSuccess(response) { const data = await response.json(); changeScreen(ChatWidgetScreens.CHAT, data.id); @@ -354,7 +352,7 @@ const useChatActions = (chatId: string) => { }); const deleteChat = useMutation({ - mutationFn: () => api.delete(`/api/v1/pleroma/chats/${chatId}`), + mutationFn: () => api.delete(`/api/v1/pleroma/chats/${chatId}`), onSuccess() { changeScreen(ChatWidgetScreens.INBOX); queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) }); diff --git a/src/queries/embed.ts b/src/queries/embed.ts index 6bb739442..b2b8a6771 100644 --- a/src/queries/embed.ts +++ b/src/queries/embed.ts @@ -22,7 +22,8 @@ export default function useEmbed(url: string) { const api = useApi(); const getEmbed = async (): Promise => { - return api.get('/api/oembed', { searchParams: { url } }).json(); + const response = await api.get('/api/oembed', { searchParams: { url } }); + return response.json(); }; return useQuery({ diff --git a/src/queries/search.ts b/src/queries/search.ts index e878466fa..e750ea780 100644 --- a/src/queries/search.ts +++ b/src/queries/search.ts @@ -12,13 +12,11 @@ export default function useAccountSearch(q: string) { const nextPageLink = pageParam?.link; const uri = nextPageLink || '/api/v1/accounts/search'; - const response = await api.get(uri, { - json: { - params: { - q, - limit: 10, - followers: true, - }, + const response = await api.get(uri, { + searchParams: { + q, + limit: 10, + followers: true, }, }); const data = await response.json(); diff --git a/src/queries/suggestions.ts b/src/queries/suggestions.ts index a28724312..5874109be 100644 --- a/src/queries/suggestions.ts +++ b/src/queries/suggestions.ts @@ -32,11 +32,11 @@ const useSuggestions = () => { const getV2Suggestions = async (pageParam: PageParam): Promise> => { const endpoint = pageParam?.link || '/api/v2/suggestions'; - const response = await api.get(endpoint); + const response = await api.get(endpoint); const { next } = getPagination(response); const hasMore = !!next; - const data = await response.json(); + const data: Suggestion[] = await response.json(); const accounts = data.map(({ account }) => account); const accountIds = accounts.map((account) => account.id); dispatch(importFetchedAccounts(accounts)); @@ -91,11 +91,11 @@ function useOnboardingSuggestions() { const getV2Suggestions = async (pageParam: any): Promise<{ data: Suggestion[]; link: string | undefined; hasMore: boolean }> => { const link = pageParam?.link || '/api/v2/suggestions'; - const response = await api.get(link); + const response = await api.get(link); const { next } = getPagination(response); const hasMore = !!next; - const data = await response.json(); + const data: Suggestion[] = await response.json(); const accounts = data.map(({ account }) => account); const accountIds = accounts.map((account) => account.id); dispatch(importFetchedAccounts(accounts)); diff --git a/src/queries/trends.ts b/src/queries/trends.ts index c6072325d..b34500db6 100644 --- a/src/queries/trends.ts +++ b/src/queries/trends.ts @@ -11,7 +11,8 @@ export default function useTrends() { const dispatch = useAppDispatch(); const getTrends = async() => { - const data = await api.get('/api/v1/trends').json(); + const response = await api.get('/api/v1/trends'); + const data: Tag[] = await response.json(); dispatch(fetchTrendsSuccess(data)); diff --git a/yarn.lock b/yarn.lock index 0d9f68792..2c6d98610 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5956,11 +5956,6 @@ known-css-properties@^0.29.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f" integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== -ky@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/ky/-/ky-1.7.2.tgz#b97d9b997ba51ff1e152f0815d3d27b86513eb1c" - integrity sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg== - kysely@^0.27.3: version "0.27.3" resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276"